Click here to Skip to main content
15,881,882 members
Articles / Web Development / ASP.NET

Dumping .NET Classes to Debug Output

Rate me:
Please Sign up or sign in to vote.
4.92/5 (31 votes)
22 Apr 2010CPOL3 min read 44.6K   984   55   5
Class to convert .NET classes into readable debug output with less effort
dump1.png

Introduction

The good old problem: how to find a variable value during code execution? Debuggers with breakpoints and watch windows are great, but only if the program can be stopped in the right moment in the right spot. Unfortunately, conditions are not always that ideal and dumping needed values to a log file or console, for future analysis, is a more realistic option.

Another common scenario is a simple debugging or run-and-forget utility that needs to produce human readable output with minimal coding. Such a tool is not intended for non-technical users and beautification of the output is not a priority, yet time spent on producing useful output is.

In either of these cases, I often find myself writing code like:

C#
class C { 
    public int X; 
    public string Y; 
    public DirectoryInfo Child; 
    public int Z; 
}
...
C c=...

// Oops, that prints just "MyNamespace.C", hardly useful
// Debug.WriteLine("c="+c);

Debug.WriteLine("--- Value of c ----");
Debug.WriteLine("c.X="+c.X);
Debug.WriteLine("c.Y="+c.X); // Oops, dumping the wrong field
Debug.WriteLine("c.Child=FileInfo 
    { Name="+c.Child.Name +"}"); // This will sometimes throw NullReference exception
Debug.WriteLine("-------"); // Forgot to dump Z field :(

Not only this is verbose and annoying to write, the first iteration of such code is often useless. Either the object being dumped does not override its ToString method, or output is confusing because of typos, or useful fields are not included, or exceptions are thrown, or multiple threads at a time execute the code and Debug.WriteLine output is mixed together. Things get even worse when there are anonymous objects, arrays, enumerations, lists, references to other objects (with loops) in the properties, that need to go to the log file as well.

There is a better way: a generic debug writer that uses reflection to display all object fields and properties, walks the object graph if necessary, and produces something human readable and with enough information for analysis. This is hardly a new idea, CodeProject already has an article on the topic, and some other solutions can be found on the Internet, but I wanted something more flexible, simple, generic and that is a single .cs file without any dependencies, so can be added to any project. And during development of XSharper framework/scripting language Dump class was created.

Using the Code

Include the Dump.cs file to your project, or link with complete XSharper.Core assembly. Now the buggy piece of dumping code above can be replaced with:

C#
Debug.WriteLine(Dump.ToDump(c),"c")

to produce:

C#
c = (C) { /* #1, 03553390 */
  X = (int)  5 (0x5)
  Y = (string) "Hello"
  Child = (DirectoryInfo) { /* #2, 01fed012 */
    Name = (string) "C:\"
    Parent = (DirectoryInfo) "<null>" /* ToString */
    Exists = (bool) true
    Root = (DirectoryInfo) "C:\" /* ToString */
    FullName = (string) "C:\"
    Extension = (string) ""
    CreationTime = (DateTime) 2009-03-03T08:30:17.8593750-05:00 (Local)
    CreationTimeUtc = (DateTime) 2009-03-03T13:30:17.8593750Z (Utc)
    LastAccessTime = (DateTime) 2010-04-14T00:38:01.6864128-04:00 (Local)
    LastAccessTimeUtc = (DateTime) 2010-04-14T04:38:01.6864128Z (Utc)
    LastWriteTime = (DateTime) 2010-04-13T12:22:52.3763914-04:00 (Local)
    LastWriteTimeUtc = (DateTime) 2010-04-13T16:22:52.3763914Z (Utc)
    Attributes = (FileAttributes) [Hidden, System, Directory] /* 0x00000016 */
  }
  Z = (int)  0 (0x0)
}

Object ID and hash code go to the output as well, to simplify tracing of complex object graphs with loops. For example, /* #2, 01fed012 */ above means object #2 with GetHashCode()=0x01fed012.

Also it's possible to control depth of the dumped object tree, maximum number of array elements displayed and some other little things. See the source code and attached sample for details.

Special Types and Properties

Some classes have properties with side effects. For example, properties that take a long time to be retrieved or that invalidate internal state of the object. To prevent Dump from accessing such properties, the properties should be registered with Dump.AddHiddenProperty() static method.

In addition, some properties and types cause too much bloat when dumped normally:

  • "Bloat" types are types that are not important to the application being debugged. For example, System.Type, System.Reflection.Assembly and many other runtime classes have dozens of public internal properties that are of little use to most application developers.
  • "Bloat" properties silently create a copy of the parent object when accessed. One example of this design are Parent and Root properties of System.IO.DirectoryInfo class. W/o special treatment dumping new DirectoryInfo("C:\") would produce an long tree of nested objects, as Root property of "C:\" returns a new copy of itself.

To avoid these problems, some known "bloat" types and properties are written to dump using ToString() method, and display /* ToString */ in the output. Additional "bloat" types and properties may be registered via Dump.AddBloatProperty() and Dump.AddBloatType() methods.

It's a good idea to register all known special types/properties at the program startup, to avoid threading issues.

Another Example

Dump details about installed CDROM drives to console:

C#
Console.WriteLine(Dump.ToDump(  
        from d in DriveInfo.GetDrives()
        where d.DriveType==DriveType.CDRom
        select d));
//----------------------------------
C#
(WhereArrayIterator<driveinfo />) { /* #1, 02b89eaa */ 
  [0] = (DriveInfo) { /* #2, 0273e403 */ 
    Name = (string) "D:\"
    DriveType = (DriveType) [CDRom] /* 0x00000005 */
    DriveFormat = (string) "UDF"
    IsReady = (bool) true
    AvailableFreeSpace = (long)  0 (0x0)
    TotalFreeSpace = (long)  0 (0x0)
    TotalSize = (long)  6394689536 (0x17d273800)
    RootDirectory = (DirectoryInfo) { /* #3, 025f14a9 */ 
      Name = (string) "D:\"
      Parent = (DirectoryInfo) "<null />" /* ToString */
      Exists = (bool) true
      Root = (DirectoryInfo) "D:\" /* ToString */
      FullName = (string) "D:\"
      Extension = (string) ""
      CreationTime = (DateTime) 2008-04-30T16:36:29.2960000-04:00 (Local)
      CreationTimeUtc = (DateTime) 2008-04-30T20:36:29.2960000Z (Utc)
      LastAccessTime = (DateTime) 2008-04-30T16:43:12.8430000-04:00 (Local)
      LastAccessTimeUtc = (DateTime) 2008-04-30T20:43:12.8430000Z (Utc)
      LastWriteTime = (DateTime) 2008-04-30T16:43:09.2810000-04:00 (Local)
      LastWriteTimeUtc = (DateTime) 2008-04-30T20:43:09.2810000Z (Utc)
      Attributes = (FileAttributes) [ReadOnly, Directory] /* 0x00000011 */
    }
    VolumeLabel = (string) "OS_4455.01"
  }
  [1] = (DriveInfo) { /* #4, 02b6a1ca */ 
    Name = (string) "V:\"
    DriveType = (DriveType) [CDRom] /* 0x00000005 */
    DriveFormat = (string) "UDF"
    IsReady = (bool) true
    AvailableFreeSpace = (long)  0 (0x0)
    TotalFreeSpace = (long)  0 (0x0)
    TotalSize = (long)  2501894144 (0x951fe000)
    RootDirectory = (DirectoryInfo) { /* #5, 021a7086 */ 
      Name = (string) "V:\"
      Parent = (DirectoryInfo) "<null />" /* ToString */
      Exists = (bool) true
      Root = (DirectoryInfo) "V:\" /* ToString */
      FullName = (string) "V:\"
      Extension = (string) ""
      CreationTime = (DateTime) 2009-07-14T05:26:40.0000000-04:00 (Local)
      CreationTimeUtc = (DateTime) 2009-07-14T09:26:40.0000000Z (Utc)
      LastAccessTime = (DateTime) 2009-07-14T05:26:40.0000000-04:00 (Local)
      LastAccessTimeUtc = (DateTime) 2009-07-14T09:26:40.0000000Z (Utc)
      LastWriteTime = (DateTime) 2009-07-14T05:26:40.0000000-04:00 (Local)
      LastWriteTimeUtc = (DateTime) 2009-07-14T09:26:40.0000000Z (Utc)
      Attributes = (FileAttributes) [ReadOnly, Directory] /* 0x00000011 */
    }
    VolumeLabel = (string) "GRMCPRFRER_EN_DVD"
  }
}

History

The latest version can be downloaded from Google code.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer http://xsharper.com
Canada Canada
Putting code between curly braces for centuries. Lately, between curly and angle braces too, on http://xsharper.com .

Comments and Discussions

 
GeneralMy vote of 5 Pin
verm-luh11-Aug-11 4:40
verm-luh11-Aug-11 4:40 
QuestionMy vote of 5 Pin
Filip D'haene6-Aug-11 5:43
Filip D'haene6-Aug-11 5:43 
GeneralMy vote of 5 Pin
Syed Saqib Ali Tipu20-Sep-10 22:30
Syed Saqib Ali Tipu20-Sep-10 22:30 
GeneralAnother way is to use Xml Serialization Pin
Hardy Wang20-May-10 2:49
Hardy Wang20-May-10 2:49 
GeneralRe: Another way is to use Xml Serialization Pin
TobiasP8-Aug-10 2:32
TobiasP8-Aug-10 2:32 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.