次の方法で共有


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