Tips for writing Debugger Visualizers in VS.NET 2005
VS.NET 2005 comes with a new feature, called Debugger Visualizers. A debugger visualizer is a small piece of code which visualizes the object currently hovered by the mouse in a debug session, of course after you hit a breakpoint. While the VS.NET 2005 debugger is a great improvement over its slow little brother in VS.NET 2003, sometimes you still want to look at the data inside an object in a more convenient way than the debugger shows you. With visualizers you can.
Out of the box, VS.NET comes with a couple of basic visualizers, for example for text, HTML, XML and DataSet/Table/View objects. These visualizers all view the data in a more convenient way which more suits the nature of the data. For example the visualizer for a DataSet shows the DataSet in a DataGridView object, which is more convenient than clicking through the object graph in the debugger itself.
You can easily create your own debugger visualizers, for existing .NET classes or for your own classes. First, it's recommended to read the documentation about Debugger Visualizers, as I won't rehash that here. It's not much to read up, you'll be done in a minute. In the VS.NET help, browse to: "Development Tools and Languages - Visual Studio - Integrated Development Environment for Visual Studio - Building, Debugging and Testing - Debugging in Visual Studio - Debugger Roadmap - Viewing data in the debugger - Visualizers".
Below I've compiled a small list of tips you should keep in mind while writing your own debugger visualizer. They can help you create better visualizers and also make maintenance more easier.
- Debugger visualizers aren't JIT-ed. A debugger visualizer is run un-jitted and in debug mode. This means that the code in your debugger visualizer will not be running at top speed when it's run. Also the controls used on the form which views the actual data aren't jitted, which means that it's key to use as less controls and the most fastest controls you can find.
- Use the .NET 2.0 DataGrid instead of the DataGridView. The DataGridView control is nice, but it's very slow in un-JIT-ed form. To illustrate how slow, visualize a DataSet in the VS.NET debugger using the DataSet visualizer and resize the window. .NET 2.0 comes with a .NET 2.0 version of the known DataGrid control for winforms and it's faster than the DataGridView control so pick that one instead. You have to manually add it to the Toolbox first.
- Debugger visualizers won't work on interface types. If you want to create a visualizer for a type which is an interface, it won't work. Debugger visualizers only work on classes.
- Place all your debugger visualizers and supporting classes in a separate assembly. A debugger visualizer project requires a reference to Microsoft.VisualStudio.DebuggerVisualizers. You probably don't want that reference in your assembly which contains the classes you want to visualize at debug time. To solve this, create a separate project/assembly and add the debugger visualizers to that project. This has also the advantage that you de-couple the debugger visualizer from the implementation of the class you want to visualize: if you want to update the debugger visualizer, you don't have to re-release the assembly with the actual classes you want to visualize, just the assembly which contains the visualizers.
- Bind a visualizer to a type using Assembly attributes. Because you've placed your debugger visualizers in a separate assembly, how to bind a visualizer to a class so VS.NET knows which visualizer to view? You'll use assembly attributes, like the following example, and place them in a code file in the assembly project which contains the debugger visualizers:
[assembly: DebuggerVisualizer( typeof( SD.LLBLGen.Pro.DebugVisualizers.PredicateVisualizer ), typeof( VisualizerObjectSource ), Target = typeof( SD.LLBLGen.Pro.ORMSupportClasses.Predicate ), Description = "Predicate Visualizer" )]
- Bind a common base class to a visualizer instead of all kinds of subclasses. If you have a common base class A, which has derived classes B, C and D, it's easier to bind a visualizer to A than to B, C and D, of course, if you're using the same visualizer for all these types. VS.NET understands class hierarchies, so if you define a visualizer for A, and you hover over an instance of C, you can select the visualizer defined for A.
- All data visualized is serialized / deserialized first. The debugger visualizer receives the data to visualize in a separate appdomain. This means that the data is first serialized and then deserialized which can have implications on which types you can write visualizers for: if the class isn't serializable, or you do extensive optimizations during serialization / deserialization, the data arriving at the debugger visualizer can be crippled or not arrive at all. Furthermore, be aware that even though the BinaryFormatter is fast, pushing thousands of objects through the formatter can still take some time.
- Beware for generic classes. If you want to write a debugger visualizer for a generic class, be aware that you can't define a debugger visualizer for a specific specification of a generic type, but only for an open type. Say you want to write a debugger visualizer for List<int>. This isn't possible, you can only define a debugger visualizer for the open type List<>. Furthermore, the data arriving at your debugger visualizer object is of type object. To utilize it in a generic form again, like List<T>, you don't know the type of T, unless you reflect over the object returned, as generic debugger visualizers aren't supported.
- All referenced assemblies from the debugger visualizer assembly have to be reachable by VS.NET. If you place all your debugger visualizers in the assembly MyVisualizers.dll, and you reference a special assembly with utility functions in that assembly, MyUtils.dll, be sure to place MyUtils.dll in the debugger visualizer folder of choice (Install path\Microsoft Visual Studio 8\Common7\Packages\Debugger\Visualizers or My Documents\Visual Studio 2005\Visualizers) so VS.NET can find that assembly when loading the debugger visualizer. You can also opt for the GAC, though use the GAC as a last resort.
- Don't re-do the debugger tree view. It's tempting to add all kinds of property grids/viewers to a debugger visualizer. Be aware that in general that's not that useful: a debugger visualizer should offer a different way of viewing the same data also reachable in the debugger tree view, but more suitable for the type of data. Simply adding all kinds of things also available in the debugger tree view isn't going to help much, instead invest time to make the data viewing experience really useful, so the developer can see in one clear overview what's the state of the object.
I'm sure there are more tips and tricks to share. If you have some great debugger visualizer tips, post them below!