Customize the display of types in the Debugger using Extension Methods and DebuggerDisplay Attribute
I was writing some code using System.Text.StringBuilder. :
Dim sb As New StringBuilder("Init SB String")
At a breakpoint the debugger Locals Window shows
+ sb {System.Text.StringBuilder} System.Text.StringBuilder
If I expand by clicking on the "+", I get:
- sb {System.Text.StringBuilder} System.Text.StringBuilder
Capacity 32 Integer
Chars In order to evaluate an indexed property, the property must be qualified and the arguments must be explicitly supplied by the user. Char
Length 24 Integer
MaxCapacity 2147483647 Integer
It's hard to see the actual String Value. I wanted to see "Init SB String" in the debugger automatically, without having to do anything: it should be right there in the debugger Watch/Locals/Auto Value column and in a HoverTip.
You could, of course, put "sb.ToString" in the Watch Window to see the value, but you would have to do it for every StringBuilder instance: if it were passed to another method, the name might be sbParam or something. Also, not all "ToString" methods return what you want to see.
There must be an easier way to customize the debugger display of your data. For C and C++ programs, I wrote Customize the VS debugger display of your data which allows you to customize any native (non-managed (not .Net)) type in the debugger. I wanted the same level of customization for .Net types.
It seemed like DebuggerDisplayAttribute should be helpful here, so I searched for that and found this recent post by JaredPar: Customizing Displays in the Debugger for System Types Jared describes a way to use DebuggerDisplayAttribute to call "ToString" on a Guid to show the actual Guid in the debugger windows. So I could use the same technique to add "ToString" for the StringBuilder type. (Jared is right around the corner from me, so I talked to him about this: he also thought about using Extension Methods to do this.)
However, I wanted to execute my own custom code that would return a string to display in the debugger. My code would work on a type that I couldn't change and to which I didn't have source code.
One way that occurred to me was to subclass it and use DebuggerDisplayAttribute. However, StringBuilder is NotInheritable .Another way was to write a wrapper container class, but then methods that returned that type would not return the wrapper class.
Hmm… I wanted to Extend a type to add my own method: … extend … add my own method….smells like Extension Methods.
Here's a way using VS 2008 Extension Methods to write custom code to output a string right into the debugger window: The Extension Methods extend the type passed as the first parameter, adding a new custom method that returns the string to display.
Start VS 2008->File->New->Project->ClassLilbrary. Call it AutoExpVB.vb
Remove the Class/End Class in the code and add these 2 lines of code, which are just assembly attributes:
<Assembly: DebuggerDisplay("SBStr= {MySBExtMeth}", Target:=GetType(System.Text.StringBuilder), Type:="MyType=System.Text.StringBuilder")>
<Assembly: DebuggerDisplay("{MyProcExtMeth}", Target:=GetType(System.Diagnostics.Process), Type:="MyType=System.Diagnostics.Process")>
(Looks like we're going to customize System.Diagnostics.Process too!)
That's all that goes in that project: just 2 lines.
Make the class library output to the Visualizers folder for all Configurations: In the Project->Properties->Compile page, change Configuarattion to All Configurations, then change the Build output path and navigate to My Documents\Visual Studio 2008\Visaulizers (VS already installs AutoExp.CS there, which has the DebuggerDisplay Attribute for many types already)
Let's add a sample application to the solution: in Solution Explorer, right click on the solution and choose Add->New Project->VB->Windows->Windows FormsApplication
I named mine AutoExpVBTest
Double click the form, remove all code, then paste in the VB code below (not the C# code!).
Put a breakpoint on the PutATracePointHere line, then hit F5 to build and run everything. (be sure that AutoExpVBTest project is the Startup Project). Voila!
+ MyStringBuilder SBStr= "MySBExtMeth: Init SB String more text" MyType=System.Text.StringBuilder
+ MyName="MyStrClass Name!!!" MyVal="new mystr val" MyType="AutoExpVBTest.Form1+MyStrClass"
+ proc "MyProcExtMeth: ID=2352 MainMod=iexplore.exe Process Class (System.Diagnostics) - Windows Internet Explorer" MyType=System.Diagnostics.Process
My custom code produced strings for the debugger! Also, if I wanted to drill down into the types members, clicking the "+" worked just as before.
Try using TracePoints (Right Click the Breakpoint, choose BreakPoint->When Hit) with this expression: Process is {proc}. When I run the code, in the debug output window I see dozens of lines similar to these
Process is "MyProcExtMeth: ID=5036 MainMod=WINWORD.EXE Customize the display of types in the Debugger using Extension Methods and DebuggerDisplay Attribute - Message"
Process is "MyProcExtMeth: ID=5608 MainMod=VFP9.exe Visual FoxPro 09.00.0000.5526 for Windows CALVINH1 [Sep 17 2007 17:30:09] Product ID xxxxx-xxx-xxxxxxx-xxxxx"
Process is "MyProcExtMeth: ID=4016 MainMod=devenv.exe AutoExpVB (Running) - Microsoft Visual Studio"
Notes:
You can use this technique for C# too: there already is an AutoExp.cs in the Visualizers folder: add this line and build:
[assembly: DebuggerDisplay(@"\{SB = {MyExt::MySB}}", Target = typeof(System.Text.StringBuilder))]
See the C# sample below. You may wonder which extension method will be called: VB or C# ? You might be surprised: try it out and see!
This will not work in VS 2005.
What do you think you'll get if you put a call to MsgBox in your evaluator?
If it takes too long to evaluate, you might get:
+ MyStringBuilder SBStr= Evaluation of expression or statement timed out. MyType=System.Text.StringBuilder
Changing what's displayed in the "Name" column makes debugging really confusing! ("om" is displayed as MyName="MyStrClass Name!!!")
To avoid duplicating the extension methods in each VB project, I tried putting them in the AutoExpVB assembly and load them using this code, but it didn't seem to work.
#If DEBUG Then
Dim sVisualizer = System.Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + _
"\Visual Studio 2008\Visualizers\AutoExpVB.dll"
Dim vis = System.Reflection.Assembly.LoadFrom(sVisualizer)
#End If
Start of VB Code
Public Class Form1
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Dim om As New MyStrClass("new mystr val")
Dim MyStringBuilder As New System.Text.StringBuilder("Init SB String")
MyStringBuilder.Append(" more text")
For Each proc In Diagnostics.Process.GetProcesses
Dim PutATracePointHere = _
"Right Click Breakpoint, choose BreakPoint->When Hit: 'Process is {proc}'"
Next
Stop
End Sub
' if the type you want to display in debugger is Yours, you can use DebuggerDisplay Attribute:
<DebuggerDisplay("MyVal={GetTheVal(1)}", Name:="MyName={GetTheVal(2)}", Type:="MyType={GetTheVal(3)}")> _
Class MyStrClass
Dim sPrivate As String = "Priv string"
Sub New(ByVal ss As String)
Me.sPrivate = ss
End Sub
Function GetTheVal(ByVal nMode As Integer) ' used by DebuggerDisplay
Select Case nMode
Case 1
Return Me.sPrivate
Case 2
Return "MyStrClass Name!!!"
Case 3
Return Me.GetType().ToString
End Select
Return "??"
End Function
End Class
End Class
#If DEBUG Then
Module MyModule
<Runtime.CompilerServices.Extension()> _
Public Function MySBExtMeth(ByVal sb As System.Text.StringBuilder) As String
Dim ss As String = ""
For i = 1 To sb.Length
ss = ss + sb.Chars(i - 1)
Next
Return "MySBExtMeth: " + ss
End Function
<Runtime.CompilerServices.Extension()> _
Public Function MyProcExtMeth(ByVal proc As Diagnostics.Process) As String
Return "MyProcExtMeth: " + _
String.Format("ID={0} MainMod={1} {2}", _
proc.Id, _
proc.MainModule.ModuleName, _
proc.MainWindowTitle) ' customize this to show what you want
End Function
End Module
#End If
End of VB Code
Start of C# Code
using System;
using System.Text;
using System.Windows.Forms;
namespace csWindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
StringBuilder sb= new StringBuilder("initial text");
sb.Append(" more");
}
}
}
#if DEBUG
public static class MyExt
{
public static String MySB(this StringBuilder sb)
{
String ss = "";
for (var i = 1; i < 10; i++)
{
ss += sb[i];
}
return ss;
}
}
#endif
End of C# Code
Comments
Anonymous
October 02, 2007
PingBack from http://www.artofbam.com/wordpress/?p=4769Anonymous
October 02, 2007
Hi And Thanks, See My Post at Microsoft Connections; http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=299856Anonymous
October 02, 2007
PingBack from http://msdnrss.thecoderblogs.com/2007/10/03/customize-the-display-of-types-in-the-debugger-using-extension-methods-and-debuggerdisplay-attribute/Anonymous
February 05, 2008
xnmXqF <a href="http://djngggedfvug.com/">djngggedfvug</a>, [url=http://efgkfzhvddyu.com/]efgkfzhvddyu[/url], [link=http://olhbcjcrzsdq.com/]olhbcjcrzsdq[/link], http://bjregzyeqmou.com/Anonymous
June 03, 2008
PingBack from http://dominicplace.45x.com/inordertoevaluateanindexedpropertythepropertymustbequalifiedandthearguments.htmlAnonymous
December 03, 2008
Much of my time is spent using the Visual Studio debugger examining code to figure out how it works and