ObjectDumper LINQ To Export Collection Via .NET Reflection

ObjectDumper dumps out a collection to the console or to a file via reflection

When developing C# programs in the functional programming style, or with LINQ, you often need to dump out a collection to the console or to a file. Object dumper is a great tool to use for this. It is a sample from the C# samples that install with Visual Studio 2008 and usually can be found at C:\Program Files\Microsoft Visual Studio 9.0\Samples\1033\CSharpSamples.zip. It also comes with the Visual Studio 2010 Samples.

ObjectDumper, as it is currently written, has some bugs. It cannot get the values of objects like NumberFormatInfo without throwing an exception and stopping dead in it's tracks, so I've made some modifications to the class in order to allow it to continue without "blowing up". Here is my revised ObjectDumper Class:

/* USAGE:

try
{
StringBuilder sb = new StringBuilder();
StringWriter sw = new StringWriter(sb);
ObjectDumper.Write(yourObject, 5, sw);
File.WriteAllText(@"C:\temp\object.txt", sb.ToString());
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message + ex.StackTrace);
}
*/

//Copyright (C) Microsoft Corporation. All rights reserved.

using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;

namespace LinqUtils
{
public class ObjectDumper
{

public static void Write( object element )
{
Write(element, 0);
}

public static void Write( object element, int depth )
{
Write(element, depth, Console.Out);
}

public static void Write( object element, int depth, TextWriter log )
{
ObjectDumper dumper = new ObjectDumper(depth);
dumper.writer = log;
dumper.WriteObject(null, element);
}

TextWriter writer;
int pos;
int level;
int depth;

private ObjectDumper( int depth )
{
this.depth = depth;
}

private void Write( string s )
{
if (s != null)
{
writer.Write(s);
pos += s.Length;
}
}

private void WriteIndent()
{
for (int i = 0; i < level; i++) writer.Write(" ");
}

private void WriteLine()
{
writer.WriteLine();
pos = 0;
}

private void WriteTab()
{
Write(" ");
while (pos % 8 != 0) Write(" ");
}

private void WriteObject( string prefix, object element )
{
if (element == null || element is ValueType || element is string)
{
WriteIndent();
Write(prefix);
WriteValue(element);
WriteLine();
}
else
{
IEnumerable enumerableElement = element as IEnumerable;
if (enumerableElement != null)
{
foreach (object item in enumerableElement)
{
if (item is IEnumerable && !(item is string))
{
WriteIndent();
Write(prefix);
Write("...");
WriteLine();
if (level < depth)
{
level++;
WriteObject(prefix, item);
level--;
}
}
else
{
WriteObject(prefix, item);
}
}
}
else
{
MemberInfo[] members = element.GetType().GetMembers(BindingFlags.Public | BindingFlags.Instance /*| BindingFlags.NonPublic */);
Console.WriteLine(members.Length + " Members.");
WriteIndent();
Write(prefix);
bool propWritten = false;
foreach (MemberInfo m in members)
{
FieldInfo f = m as FieldInfo;
PropertyInfo p = m as PropertyInfo;
if (f != null || p != null)
{
if (propWritten)
{
WriteTab();
}
else
{
propWritten = true;
}
Write(m.Name);
Write("=");
Type t = f != null ? f.FieldType : p.PropertyType;
object o = null;
if (t.IsValueType || t == typeof(string))
{
try
{

o = f != null ? f.GetValue(element) : p.GetValue(element, null);
WriteValue(o);
}
catch (Exception ex)
{
// Had to do this because exceptions are thrown on NumberFormatInfo and other types
// at high depth levels, even though there is a legitimate value obtained.
Console.WriteLine(ex.Message);
WriteValue(o);
}
}
else
{
if (typeof(IEnumerable).IsAssignableFrom(t))
{
Write("...");
}
else
{
Write("{ }");
}
}
}
}
if (propWritten) WriteLine();
if (level < depth)
{
foreach (MemberInfo m in members)
{
FieldInfo f = m as FieldInfo;
PropertyInfo p = m as PropertyInfo;
if (f != null || p != null)
{
Type t = f != null ? f.FieldType : p.PropertyType;
object value = null;
if (!(t.IsValueType || t == typeof(string)))
{
try
{
value = f != null ? f.GetValue(element) : p.GetValue(element, null);
}
catch
{
}

if (value != null)
{
level++;
WriteObject(m.Name + ": ", value);
level--;
}
}
}
}
}
}
}
}

private void WriteValue( object o )
{
if (o == null)
{
Write("null");
}
else if (o is DateTime)
{
Write(((DateTime)o).ToShortDateString());
}
else if (o is ValueType || o is string)
{
Write(o.ToString());
}
else if (o is IEnumerable)
{
Write("...");
}
else
{
Write("{ }");
}
}
}
}

The downloadable sample test app creates a simple DataSet containing a single DataTable with two columns and one row and runs it through ObjectDumper to save to a text file:

using System.Text;
using LinqUtils;
using System.Data;
using System.IO;


namespace Test
{
class Program
{
static void Main( string[] args )
{
DataSet ds = new DataSet();
DataTable dt = new DataTable();
dt.Columns.Add("Name", typeof (string));
dt.Columns.Add("Age", typeof(int));
DataRow row = dt.NewRow();
row.ItemArray = new Object[] {"George Washington", 44};
dt.Rows.Add(row);
ds.Tables.Add(dt);
try
{
StringBuilder sb = new StringBuilder();
StringWriter sw = new StringWriter(sb);
ObjectDumper.Write(ds, 4, sw);
File.WriteAllText(@"C:\temp\object.txt", sb.ToString());
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message + ex.StackTrace);
}
}
}
}

You can experiment with this at different depth levels (I'm using 4 in the sample). Just using a 4 depth level outputs a 49K Text file for the DataSet, showing properties you'd never think were in this class. Increasing the depth level can create text files up to 16,000K! If you want to see private members, just uncomment the /*| BindingFlags.NonPublic */ -- then you'll see even more cool stuff in the output.

I have found ObjectDumper to be useful especially dealing with the results of complex LINQ queries. You can download the sample with the revised class here.


By Peter Bromberg   Popularity  (3185 Views)