VisualStudio Debug Visualizer für Dictionary

Published Freitag, 9. Juli 2010 14:04
Dieser Artikel wurde zum 1. Mal am 09.07.2010 upgedatet

Ich habe nun sehr oft das Problem, dass ich in VisualStudio in einer Debug Session den Inhalt eines Dictionaries mir anzeigen, oder gar verändern möchte. Dictionaries sind bei mir recht häufig als Übergabeparameter definiert.

Was sind "Debug Visualizer"?

Debug Visualizer kennt jeder (aber wahrscheinlich nicht mit dem Begriff ;-). Ein berühmtes Beispiel ist der String-Visualizer:



Ich habe nun für VisualStudio 2005 und 2010 einen eigenen Visualizer für Dictionaries geschrieben.

Dictionary Visualizer Solution Aufbau:


Ein eigner Visualizer ist grundsätzlich vom Aufbau her immer gleich:
- Der Visualizer ist eine Class Library
- Es existiert eine Einstiegsklasse, die vom VS Debugger aufgerufen werden kann
- Es gibt ein Datenobjekt, das der Debugger zum (de-)serialisieren verwendet
- In der AssemblyInfo stehen die Informationen, damit VS weiß, welche Objekte mit dem Visualizer angezeigt werden können

In meinem Beispielprojekt ist das so aufgeteilt:
- DictionaryVisualizer.cs => Einstiegsklasse, die von der abstrakten Klasse DialogDebuggerVisualizer abgeleitet ist
- DictionarySource.cs => Die Datenklasse, von VisualizerObjectSource abgeleitet
- frmDictionaryVisualizer.cs => Das anzuzeigende Dialogfenster (äußerst trivial gehalten ;-)
- DataTableConverter.cs => Hilfsklasse, die Konvertierungen von und zu DataTables realisiert

Beschreibung der einzelnen Bestandteile:

DictionaryVisualizer.cs:

    /// <summary>
    /// Visualisiert ein Dictionary in einer Debug Session.
    /// </summary>
    public class DictionaryVisualizer : DialogDebuggerVisualizer
    {
        /// <summary>
        /// Zeigt einen modalen Dialog, das den Inhalt des Dictionaries visualisiert.
        /// </summary>
        /// <param name="windowService">Der Windows Context, in dem der Debugger läuft.</param>
        /// <param name="objectProvider">Das Dictionary-Objekt, das visualisiert werden soll.</param>
        protected override void Show(IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider)
        {
            IDictionary dictToShow = null;
            
            // Versuchen, das Objekt aus dem Debugger zu extrahieren
            try
            {
                dictToShow = objectProvider.GetObject() as IDictionary;
            }
            catch (Exception ex)
            {
                MessageBox.Show(
                    String.Format("Folgender Fehler ist aufgetreten:\n{0}\nStack:\n{1}", ex.Message, ex.StackTrace),
                    "Fehler",
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Error);

                // Fehler werden ausgegebenund weitere Schritte werden ignoriert
                dictToShow = null;
            }

            // Dictionary anzeigen lassen
            if (dictToShow != null)
            {
                using (frmDictionaryVisualizer dialog = new frmDictionaryVisualizer())
                {
                    dialog.Data = DataTableConverter.ConvertFromDictionary(dictToShow);
                    windowService.ShowDialog(dialog);
                    objectProvider.ReplaceObject(DataTableConverter.ConvertToIDictionary(dialog.Data, dictToShow.GetType()));
                }
            }
        }
    }

Die Methode Show wird vom Debugger ausgeführt, wenn auf die Lupe gedrückt wird (siehe TextVisualizer-Bild oben). Der Ablauf ist Straight-Forward:
- Zunächst wird versucht das eingehende Objekt aus dem objectProvider in ein IDictionary Objekt zu casten
- Sollte das geklappt haben, so wird aus dem Objekt ein DataTable Objekt konvertiert und dem Dialog übergeben
- Ist der Dialog zurückgekehrt, so wird dessen DataTable in ein neues Dictionary des gleichen Typs gewandelt und dem Debugger zurückgegeben

DictionarySource.cs:

    public class DictionarySource : VisualizerObjectSource
    {
        public override void GetData(object target, System.IO.Stream outgoingData)
        {
            IDictionary dictToSerialize = target as IDictionary;
            if (dictToSerialize != null)
            {
                BinaryFormatter formatter = new BinaryFormatter();
                formatter.Serialize(outgoingData, dictToSerialize);
            }
        }
    }

Ebenfalls sehr einfach gestrickt. Hier wird die Methode GetData überschrieben, damit eine eigene Serialisierung des IDictionary Objektes stattfinden kann. Das ist quasi nur ein BinaryFormatter Wrapper.

AssemblyInfo.cs:

Hier ist es wichtig, dass über Attribute definiert wird, welche Objekte für einen Visualizer überhaupt in Frage kommen. Das definiert sich folgendermaßen:
[assembly: System.Diagnostics.DebuggerVisualizer(typeof(Rehl.DebuggingTools.DictionaryVisualizer.DictionaryVisualizer),
           typeof(Rehl.DebuggingTools.DictionaryVisualizer.DictionarySource),
           Target = typeof(System.Collections.DictionaryBase),
           Description = "Visualize IDictionary")]
[assembly: System.Diagnostics.DebuggerVisualizer(typeof(Rehl.DebuggingTools.DictionaryVisualizer.DictionaryVisualizer),
           typeof(Rehl.DebuggingTools.DictionaryVisualizer.DictionarySource),
           Target = typeof(System.Collections.Hashtable),
           Description = "Visualize IDictionary")]
[assembly: System.Diagnostics.DebuggerVisualizer(typeof(Rehl.DebuggingTools.DictionaryVisualizer.DictionaryVisualizer),
           typeof(Rehl.DebuggingTools.DictionaryVisualizer.DictionarySource),
           Target = typeof(System.Collections.Specialized.ListDictionary),
           Description = "Visualize IDictionary")]
[assembly: System.Diagnostics.DebuggerVisualizer(typeof(Rehl.DebuggingTools.DictionaryVisualizer.DictionaryVisualizer),
           typeof(Rehl.DebuggingTools.DictionaryVisualizer.DictionarySource),
           Target = typeof(System.Collections.Specialized.HybridDictionary),
           Description = "Visualize IDictionary")]
[assembly: System.Diagnostics.DebuggerVisualizer(typeof(Rehl.DebuggingTools.DictionaryVisualizer.DictionaryVisualizer),
           typeof(Rehl.DebuggingTools.DictionaryVisualizer.DictionarySource),
           Target = typeof(System.Collections.Specialized.OrderedDictionary),
           Description = "Visualize IDictionary")]
[assembly: 
System.Diagnostics.DebuggerVisualizer(typeof(Rehl.DebuggingTools.DictionaryVisualizer.DictionaryVisualizer),
          
 typeof(Rehl.DebuggingTools.DictionaryVisualizer.DictionarySource),
          
 Target = typeof(System.Collections.Generic.Dictionary<,>),
          
 Description = "Visualize IDictionary")]

Der Aufbau ist fast selbsterklärend. Der erste Parameter einer jeden Visualizer Definition ist der entsprechend aufzurufende Visualizer. In meiner ClassLibrary gibt es nur den einen DictionaryVisualizer. Der zweite Parameter bestimmt das Objekt, das zum (de-)serialisieren verwendet wird. Hier also auch immer wieder nur eines, nämlich das DictionarySource. Das Target definiert das Objekt, für welches der Visualizer gelten soll. In meiner Sammlung werden also DictionaryBase, Hashtable, ListDictionary, HybridDictionary und OrderedDictionary unterstützt.

Leider habe ich bis jetzt keine Möglichkeit gefunden generische Objekte hierfür definieren zu können. Wenn hier einer mehr weiß, dann kann er mir die Info gerne zukommen lassen, das wüde ich auch mit einbauen. Als Workaround kann man im Debugger ohne Probleme ein generisches Dictionary zu einem statischen casten und dnn die Visualisierung darauf laufen lassen.
Update: Inzwischen habe ich es herausgefunden, das war zu einfach ;-)

frmDictionaryVisualizer.cs und DataTableConverter.cs:
Auf diese beiden Bestandteile gehe ich jetzt nicht im Detail ein, das kann jeder nach Interesse selbst in der Solution erforschen.

Installation des Visualizers:
1. Die Solution mit VisualStudio öffnen und einmal als Release durchkompilieren lassen.
2. Dann VisualStudio schließen
3. Die Rehl.DebuggingTools.DictionaryVisualizer.dll aus dem bin/release Ordner in das entsprechende VisualStudio Verzeichnis kopieren, bei mir wäre das z.B. D:\Program Files\Microsoft Visual Studio 10.0\Common7\Packages\Debugger\Visualizers
4. VisualStudio neu starten

Anwendung des Visualizers:
Sollte das Kompilieren und Kopieren abgeschlossen sein, so befindet sich in meiner Solution ein Test Konsolenprojekt, das eine eigene HashTable befüllt und an eine Testmethode übergibt.
Es sollte ein Breakpoint an der Stelle
string s = "OnlyToStopDebugger!";
gesetzt werden und dann Debugging des Projektes gestartet werden (wieder auf Debug umstellen nach einer Installation!).
Jetzt kann, wenn das Programm angahalten hat mit der Maus über den Übergabeparameter IDictionary data gefahren werden, und die Lupe erscheint. Sollte keine Lupe zu sehen sein, dann hat etwas mit der Installation nicht geklappt.
Ein Klick auf diese Lupe lässt das Dialogfenster erscheinen:


Jetzt lässt sich der Inhalt sortieren, und auch editieren.

Besonderes zu beachten:
Sollten eigene Dictionary Implementierungen verwendet werden, dann empfiehlt es sich von einem oben genannten Objekt abzuleiten. Weiterhin muss beachtet werden, dass das Serializable Attribut und eine Implentierung des Sonderkonstruktors für ISerializable vorhanden sin. In meiner Testapplikation wird das realisiert:
    [Serializable]
    public class InheritanceTestClass : Hashtable, ISerializable
    {
        public InheritanceTestClass()
            : base()
        {
        }

        public InheritanceTestClass(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
        }
    }


Downlaod:
VS2005 Solution
VS2010 Solution

Kommentare

# Stephan Heyne said on Dienstag, 25. Januar 2011 16:33

Netter Visualizer!

Allerdings funktioniert er nicht in Silverlight :-(

In Silverlight muss man Klassen nämlich anderes serialisieren (mit [DataContract] statt [Serializable])

Es wäre sehr schön, wenn Du noch eine Silverlight-Version rausbringen würdest ... das wäre sehr hilfreich!

Besten Dank im Vorraus,

Stephan

# Timo Rehl said on Montag, 14. Februar 2011 09:22

Hallo Stephan,

danke für Dein Feedback. Silverlight war definitiv nicht in meinem Fokus. Ich werde mir das mal anschauen, ich habe noch keine Silverlight Anwendungen geschrieben, was aber nicht heißt, dass ich icht mal mit der Thematik anfangen sollte ;-)

Also gib mir ein wenig Zeit dafür...

Grüße

Timo

Kommentar abgeben

(verpflichtend) 
(verpflichtend) 
(optional)
(verpflichtend)