Does anyone know of a way to pretty-print .NET objects? We've covered before how .NET objects don't stringify into anything good, so I'm looking for a pretty-printer to aid in debugging. .NET has such good reflection that I'm surprised I can't find a pre-existing library or built-in .NET facility. Do I have to take a half hour and write my own pretty-printer?
Update (2:29pm): There we go, this only took me about 20 minutes:
using System;
using System.Text;
using System.Reflection;
class foobar{
string foo, bar, baz;
bar b = new bar();
}
class bar{
string roar, blah;
}
namespace PrettyPrinter{
public class PrettyPrinter{
public static void Main(string[] args){
foobar f = new foobar();
Console.WriteLine(pp(f));
int i = 2;
Console.WriteLine(pp(i));
}
public static string pp(object o){
StringBuilder sb = new StringBuilder();
pp(sb, o, 0);
return sb.ToString();
}
public static void pp(StringBuilder sb, object o, int level){
Type t = o.GetType();
FieldInfo[] members = t.GetFields(
BindingFlags.Public
| BindingFlags.NonPublic
| BindingFlags.Instance
);
foreach(FieldInfo info in members){
object val = info.GetValue(o);
sb.Append('\t',level);
sb.Append(info.ToString()).Append(": ");
if(val == null){
sb.AppendLine("null");
}else if(info.FieldType.IsClass){
sb.AppendLine("{");
pp(sb, val, level+1);
sb.Append('\t',level).AppendLine("}");
}else{
sb.AppendLine(o.ToString());
}
}
}
}
}
Output:
C:\DEV\Projects\test\bin\Debug>test
System.String foo: null
System.String bar: null
System.String baz: null
bar b: {
System.String roar: null
System.String blah: null
}
Int32 m_value: 2
Update (4:57pm): Since of course that was a first pass, here's a more correct version that handles circular references, and for Keith also dumps XML if you happen to pass it an XmlNode 
using System;
using System.Xml;
using System.Text;
using System.Reflection;
using System.Collections.Generic;
using pp = PrettyPrinter.PrettyPrinter;
namespace PrettyPrinter{
public class PrettyPrinter{
private static Type str = typeof(string);
private static Type xml = typeof(XmlNode);
public static string pp(object o){
if(o == null)
return "null";
StringBuilder sb = new StringBuilder();
sb.Append(o.GetType()).Append(": ");
pp(new Dictionary<object, bool>(), sb, o, 0);
return sb.ToString();
}
public static void pp(
Dictionary<object, bool> seen_objects,
StringBuilder sb, object o, int level
){
Type t = o.GetType();
if(t == str || t.IsValueType){
sb.AppendLine(o.ToString());
}else if(xml.IsInstanceOfType(o)){
sb.AppendLine(((XmlNode)o).OuterXml);
}else if(t.IsClass){
if(seen_objects.ContainsKey(o)){
sb.AppendLine("RECURSION!");
return;
}
seen_objects.Add(o, true);
FieldInfo[] fields = t.GetFields(
BindingFlags.Public
| BindingFlags.NonPublic
| BindingFlags.Instance
);
sb.AppendLine("{");
foreach(FieldInfo info in fields){
object val = info.GetValue(o);
sb.Append('\t',level+1).Append(info).Append(": ");
if(val == null){
sb.AppendLine("null");
}else{
pp(seen_objects, sb, val, level+1);
}
}
sb.Append('\t',level).AppendLine("}");
}
}
}
}
class foobar{
string foo, bar, baz = "roar";
public testing.bar b = new testing.bar();
}
namespace testing{
class bar{
string roar, blah;
public foobar f;
}
class baz{
public baz b = null;
}
}
public class Test{
public static void Main(string[] args){
foobar f = new foobar();
f.b.f = f;
Console.WriteLine(pp.pp(f));
int i = 2;
Console.WriteLine(pp.pp(i));
XmlDocument doc = new XmlDocument();
doc.LoadXml(@"<?xml version=""1.0""?>
<foo><bar><baz>roar</baz></bar></foo>"
);
Console.WriteLine(pp.pp(doc.ChildNodes[1]));
testing.baz b = new testing.baz();
b.b = b;
Console.WriteLine(pp.pp(b));
}
}
Output:
C:\DEV\Projects\test\bin\Debug>test
foobar: {
System.String foo: null
System.String bar: null
System.String baz: roar
testing.bar b: {
System.String roar: null
System.String blah: null
foobar f: RECURSION!
}
}
System.Int32: 2
System.Xml.XmlElement: <foo><bar><baz>roar</baz></bar></foo>
testing.baz: {
testing.baz b: RECURSION!
}
(code reformatted to not break my layout)
Don't forget to check the object graph for loops.