Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Dumping .NET Classes to Debug Output

0.00/5 (No votes)
22 Apr 2010 1  
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:

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:

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

to produce:

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:

Console.WriteLine(Dump.ToDump(  
        from d in DriveInfo.GetDrives()
        where d.DriveType==DriveType.CDRom
        select d));
//----------------------------------
(WhereArrayIterator) { /* #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) "" /* 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) "" /* 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 has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here