View Single Post
 
Old 07-01-2007, 09:45 PM
AtmaWeapon's Avatar
AtmaWeapon AtmaWeapon is offline
Fabulous Florist

Forum Leader
* Guru *
 
Join Date: Feb 2004
Location: Austin, TX
Posts: 9,500
Default Debugger Visualizers

The debugger is something that a programmer should never have to use. When everything is planned properly, the bugs fix themselves. This is an ideal, however, and even if your project has the luxury of time to plan things in great detail, changing customer requirements can reduce the plans to nothing.

If you're like me, you are quite familiar with the debugger, and possibly spend more time in it than in code. I'm often guilty of putting breakpoints down as I write code, just so I can watch the tricky areas the first couple of times through. There's always those classes that are tricky to watch, though. This image illustrates what I mean. To see a property that is an array here, I have to go two or three levels deep into the popups. Worse, the only help I get from the initial popup is the name of the class! There's a lot we can do to fix that, and I'm here to show you how to make the debugger show you what you want instead of the other way around.

First, let's explore the example project. The important class to look at here is DebuggableType. Pay no attention to the attributes on it, just look at it and get a feel for what it is. The class has three members: an id represented by an integer, a name represented by a string, and a collection of strings that represent hilarious objects. The id and name are set by the constructor, and the collection of hilarious objects is randomly generated by the constructor.

The main form is the perfect example of a test harness for this instance; all it does is create an instance of the class and break immediately so you can see what the debugger has to say.

As it stands, we have a fairly simple class but as the image above shows it's a little annoying to keep track of the hilarious objects, and you have to drill down into the popups to get anything useful like the id or the name. So how do we fix this?

I'll start with the easy part.

The DebuggerDisplay Attribute
You can read all about the DebuggerDisplayAttribute class on MSDN, I'm not here to explain it in excruciating detail. Basically this guy lives in System.Diagnostics and controls the text in the initial popup the debugger displays. The expression you use for the display has access to properties, variables, and (possibly!) functions; MSDN is only really explicit about C#'s support of the attribute. The format is simple. You type the string you want to be displayed, and anywhere you want to display a property/member/function you put {name}. As you can see, I have added the following to the DebuggableType class:
Code:
 DebuggerDisplay("Id: {Id} Name: {Name}")
This should make the initial debugger popup (and any watch windows) display a string like "Id: 2 Name: Don Juan". Indeed it does!
If you think that's the only debugger trick there is, you're wrong. The next part is a little more involved, but can pay off big when you are trying to debug something that is difficult to see (like hashtables).

The Debugger Visualizer
This is a bit more involved, and may seem like a lot of work for not much of a payoff. I agree with that to an extent; there's some features of this debugger extension that I'd love to have, but when it comes down to it if you've ever had to debug one of the more complicated collections like a HashTable then you've been fairly frustrated with poor debugger support for them. This can help.

Basically, a Debugger Visualizer is a Form you create to display the contents of an object in a meaningful way. I stumbled upon Debugger Visualizers when I found this CodeProject article, and I was intrigued by the concept. It takes a bit of setup work and (ironically) it can be somewhat difficult to debug, but once you do a few on your own it comes naturally.

MSDN talks a lot about "the debugger side" and "the debuggee side" and all those terms do is confuse me. Some examples name their classes after these terms and I think this is not only confusing but stupid. You don't need to think of those terms again, if you think about it from the perspective of the steps it takes to build them then you'll have no problems.

Annoying Prerequisite
The class you write a visualizer for must be marked by the Serializable attribute. This could interfere with your design and force you to write serialization logic if your class manages difficult-to-serialize resources. If this happens, you have to choose between easier debugging and reduced risk of defects.

Step 0: Get your references set up
Debugger Visualizers rely on code in the Microsoft.VisualStudio.DebuggerVisualizers namespace. You'll need to add a project reference to Microsoft.VisualStudio.DebuggerVisualizers, and then use Imports statements as you see fit.

Step 1: Create a Form that can display the class
The form you will display needs to satisfy two characteristics:
  • It needs to store the information from the class it represents
  • External callers need a way to communicate this information with the Form.
The easiest way to implement this is to store a reference to the object as a private member and to have the constructor require that instance. Take a look at DebuggableTypeVisualizerForm and you'll see what I mean. It has a reference to a DebuggableType, it takes that reference through the constructor, and it uses a special method to display the reference on its UI.

Step 2: Create the visualizer class
This is simple. Create a class that Inherits from DialogDebuggerVisualizer. There is a single method to override, Show, that is called when the debugger wants to display the visualizer. The arguments to this method are scary looking, but not that bad. windowService only has one method. It's the reference to the debugger service that can display windows and controls so you will use it to display the form you made in step 1. objectProvider is what provides information about the object the debugger is concerned with, and you will mostly be concerned with its GetObject() method, since that's how you get the instance you want to send to the Form you made in Step 1.

Implementation is easier than explanation. Take a look at the DebuggableTypeVisualizer class. It obtains the reference to the DebuggableType through objectProvider, then it instantiates a DebuggableTypeVisualizerForm with that instance, then it uses windowService to display the Form. Now it's time to hook it up so it will actually work!

Step 3: Use attributes to install the visualizer
Now the only thing we have left to do is let Visual Studio know that our visualizer classes should be used when debugging our target type. We do this by applying a DebuggerVisualizer attribute to the DebuggableType class. In this case, the only required argument to the attribute is the type of the class that will be used as the visualizer, which happens to be the class we made in step 2. Take a look at DebuggableType again and notice how the DebuggerVisualizer attribute is set up:
Code:
 DebuggerVisualizer(GetType(DebuggableTypeVisualizer), Description:="Visualizer for DebuggableType")
I added a description, but it is completely optional.

With that done, run the project and push the button. The debugger should break at the breakpoint, and when you hover your mouse over the instance of DebuggableType you'll see a little magnifying glass that will launch the visualizer when clicked. When you click the glass, the Form will display with the values in the object.

Side Note: The C# example, and all of my tests in C#, used assembly-level attributes with no problem. When I tried using assembly-level attributes in VB .NET I could never get the visualizer to show up. If you manage to get this project working with an assembly-level DebuggerVisualizer attribute I would really like to know how you do it!

What now?
Well there's pros and cons to this approach. Remember how I said there was some functionality that I wished was there? The Form must be displayed modally, you can't debug anything else while using the visualizer. There appears to be some way to update the object from the visualizer (similar to changing values in the watch or immediate windows) but when I looked it over it seemed complicated enough to be outside of the scope of this tutorial (also I haven't spent the couple of days of playing with it to be sure).

On the plus side, this can allow all kinds of neat ways to debug things that used to be hard to debug. I hope you find Debugger Visualizers as useful as I have so far.
Attached Files
File Type: zip DebuggerVisualizerTutorial.zip (4.3 KB, 33 views)
__________________
.NET Resources
My FAQ threads | Tutor's Corner | Code Library
I would bet money 2/3 of .NET questions are already answered in one of these three places.

Last edited by OnErr0r; 08-19-2007 at 02:33 PM. Reason: Fixed font tag
Reply With Quote