Building a debugger visualizer for generic types

Share on:

You can find the source code relating to this posthere.**

Since Visual Studio 2005 we have been able to write debugger visualizers to help us look at data in a more convenient way whilst debugging.

A while ago I needed to write a visualizer for LIFTI - the problem that I had was that the type I wanted to visualize was generic, and it’s not straightforward to write a visualizer for a generic type. I thought it would be useful to explain my approach to this solution for others to follow.

Lets start with an example class that we can debug. It’ll be a generic class containing an item of some type, and an associated score, between 0 and 99. (Yes, it’s contrived, but it keeps it simple!)

1
2[DebuggerVisualizer(typeof(ItemScoreDebuggerVisualizer))]
3[Serializable]
4public class ItemScore<TItem>
5{
6    public TItem Item { get; set;}
7    public int Score { get; set; }
8}

Note that the class is marked as Serializable. This is a requirement of any object that is going to be passed to a debugger visualizer because the visualizers are hosted in a separate AppDomain. Objects have to be migrated from their domain to the debugger domain, and this is accomplished using serialization.

The class is also marked as having a DebuggerVisualizer of type ItemScoreDebuggerVisualizer. This is defined as:

 1
 2public class ItemScoreDebuggerVisualizer : DialogDebuggerVisualizer
 3{
 4    protected override void Show(IDialogVisualizerService windowService, 
 5        IVisualizerObjectProvider objectProvider)
 6    {
 7        object rawData = objectProvider.GetObject();
 8        var objectType = rawData.GetType();
 9        var method = this.GetType().GetMethod(
10            "ShowItemScoreVisualizer", 
11            BindingFlags.Instance | BindingFlags.NonPublic);
12        method = method.MakeGenericMethod(objectType.GetGenericArguments());
13        method.Invoke(this, new object[] { rawData });
14    }
15
16    private void ShowItemScoreVisualizer<TItem>(ItemScore<TItem> itemScore)
17    {
18        var window = new VisualizerWindow();
19        window.ShowItemScore(itemScore);
20        window.ShowDialog();
21    }
22}

Ok, this is obviously where the important stuff is, so let’s break it down.

The Show method is called by Visual Studio in order to display the visualizer:

  1. Get the object being visualized from the provided IVisualizerObjectProvider instance. Note that this is returned as an instance of type System.Object - we don’t know what generic type is being used at this point.
  2. We get the type information for the visualized object.
  3. We get the method information for ShowItemScoreVisualizer, a generic method that takes an instance of ItemScore as its parameter.
  4. At this point in time, the ShowItemScoreVisualizer method information doesn’t have a concrete type associated to it, so we use MakeGenericMethod to build a MethodInfo up with a specific type. The key here is that we are passing the generic arguments of the visualized object type to the MakeGenericMethod call.
  5. We invoke the method.

The ShowItemScoreVisualizer implementation is very simple - we now can refer to itemScore.Item as we would in any generic method. You might want to use generic constraints on your implementation, but for this example being able to use ToString is enough.

I won’t go into the implementation of VisualizerWindow as it’s largely irrelevant for this post. As a side note, the visualizer window is a WPF window, which is why I’m not calling IDialogVisualizerService.ShowDialog, as this requires an instance of a classic Windows Form. I’m not sure how supported this approach is, so take that part of the code a pinch of salt!

Now when you debug into the code and get hover over a variable containing an instance of ItemScore, you’ll see the little magnifying glass next to the variable data:

image

Clicking on the magnifying glass will call into the Show method, ultimately resulting in our visualizer window being displayed:

image

As always, let me know in the comments if you find this useful!