Introduction
This is a quick variable dump.
It shows types and values of all properties and works its way recursively avoiding variables that have already been dumped.
Background
This is based on this blog.
Using the Code
A simple line to call the static
method...
Utils.Dump(requestObject);
...will display the output (as an example):
1 PublicProperties (Middleware.Contracts.ReturnPeriodicMeterDataRequestRequestMessage)
2 | ExtensionData (System.Runtime.Serialization.ExtensionDataObject)
3 | Header (Middleware.Contracts.HeaderType)
4 | | ExtensionData (System.Runtime.Serialization.ExtensionDataObject)
5 | | MessageId = "0847c4b1-7f1c-4cf2-a011-09626d181234" (System.String)
6 | | CorrelationId = "1" (System.String)
7 | | Segment = 1739 (System.Int32)
8 | | SegmentCount = 1753 (System.Int32)
9 | | LastSegment = null (System.Nullable`1[System.Boolean])
10 | MeterData (Middleware.Contracts.MeterDataType)
11 | | ExtensionData (System.Runtime.Serialization.ExtensionDataObject)
12 | | MeterSerialNumber = "00000000123450083" (System.String)
13 | | RegisterList (System.Collections.Generic.List`1[Middleware.Contracts.RegisterType])
14 | | | Capacity = 4 (System.Int32)
15 | | | Count = 1 (System.Int32)
16 | | RegisterList[0] (Middleware.Contracts.RegisterType)
17 | | | ExtensionData (System.Runtime.Serialization.ExtensionDataObject)
18 | | | RegisterId = "2" (System.String)
19 | | | Unit = "Wh" (System.String)
20 | | | MeterReadingList (System.Collections.Generic.List`1[Middleware.Contracts.MeterReadingType])
21 | | | | Capacity = 32 (System.Int32)
22 | | | | Count = 20 (System.Int32)
23 | | | MeterReadingList[0] (Middleware.Contracts.MeterReadingType)
24 | | | | ExtensionData (System.Runtime.Serialization.ExtensionDataObject)
25 | | | | Reading = 17540 (System.Double)
26 | | | | ReadingDate = 21/12/2011 04:00:00 (System.DateTime)
27 | | | | Status = "2000" (System.String)
28 | | | MeterReadingList[1] (Middleware.Contracts.MeterReadingType)
29 | | | | ExtensionData (System.Runtime.Serialization.ExtensionDataObject)
30 | | | | Reading = 17540 (System.Double)
31 | | | | ReadingDate = 21/12/2011 05:00:00 (System.DateTime)
32 | | | | Status = "2000" (System.String)
...
118 | | | MeterReadingList[19] (Middleware.Contracts.MeterReadingType)
119 | | | | ExtensionData (System.Runtime.Serialization.ExtensionDataObject)
120 | | | | Reading = 17540 (System.Double)
121 | | | | ReadingDate = 21/12/2011 23:00:00 (System.DateTime)
122 | | | | Status = "2000" (System.String)
You can also dump private
members if you prefer:
Utils.Dump(yourObject, Utils.DumpStyle.PrivateFields);
In this mode, we dump all fields rather than public
properties.
Complete Code
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Text;
class Utils
{
public enum DumpStyle
{
PublicProperties,
PrivateFields
}
public static void Dump(object obj)
{
Dump(obj, DumpStyle.PublicProperties, null);
}
public static void Dump(object obj, DumpStyle dumpStyle)
{
var varDumper1 = new VarDumper();
varDumper1.Dump(obj, dumpStyle, null);
}
public static void Dump(object obj, DumpStyle dumpStyle, Action<string> action)
{
if (action == null) action = (s) => Trace.WriteLine(s);
var varDumper1 = new VarDumper();
varDumper1.Dump(obj, dumpStyle, action);
}
public static IEnumerable<string> HexDump(byte[] bytes, int bytesPerLine)
{
if (bytes == null)
{
yield return "<null>";
yield break;
}
int bytesLength = bytes.Length;
char[] HexChars = "0123456789ABCDEF".ToCharArray();
int firstHexColumn =
8
+ 3;
int firstCharColumn = firstHexColumn
+ bytesPerLine * 3
+ (bytesPerLine - 1) / 8
+ 2;
int lineLength = firstCharColumn
+ bytesPerLine;
char[] line = (new String(' ', lineLength)).ToCharArray();
int expectedLines = (bytesLength + bytesPerLine - 1) / bytesPerLine;
for (int i = 0; i < bytesLength; i += bytesPerLine)
{
line[0] = HexChars[(i >> 28) & 0xF];
line[1] = HexChars[(i >> 24) & 0xF];
line[2] = HexChars[(i >> 20) & 0xF];
line[3] = HexChars[(i >> 16) & 0xF];
line[4] = HexChars[(i >> 12) & 0xF];
line[5] = HexChars[(i >> 8) & 0xF];
line[6] = HexChars[(i >> 4) & 0xF];
line[7] = HexChars[(i >> 0) & 0xF];
int hexColumn = firstHexColumn;
int charColumn = firstCharColumn;
for (int j = 0; j < bytesPerLine; j++)
{
if (j > 0 && (j & 7) == 0) hexColumn++;
if (i + j >= bytesLength)
{
line[hexColumn] = ' ';
line[hexColumn + 1] = ' ';
line[charColumn] = ' ';
}
else
{
byte b = bytes[i + j];
line[hexColumn] = HexChars[(b >> 4) & 0xF];
line[hexColumn + 1] = HexChars[b & 0xF];
line[charColumn] = (b < 32 || b >= 128 ? '.' : (char)b);
}
hexColumn += 3;
charColumn++;
}
yield return new String(line);
}
}
private class VarDumper
{
int maxRecursion = 10;
private DumpStyle mDumpStyle;
private int mLineCounter;
private Dictionary<Object, int>
seenObjects = new Dictionary<Object, int>();
private static HashSet<Type> SimpleTypes = new HashSet<Type>() {
typeof(UInt16 ), typeof(UInt32), typeof(UInt64),
typeof(Int16 ), typeof(Int32), typeof(Int64),
typeof(Byte), typeof(DateTime),
typeof(Single), typeof(Double), typeof(Decimal),
typeof(Boolean),
typeof(Char), typeof(String),
typeof(string)};
internal void Dump<T>(T obj, DumpStyle dumpStyle, Action<string> writeLine)
{
mDumpStyle = dumpStyle;
DumpObjectMember(0, "", dumpStyle.ToString(), typeof(T), obj, writeLine);
}
private string IndentedLine(string format, params object[] args)
{
mLineCounter++;
string line;
if (args != null && args.Length > 0) line = string.Format(format, args);
else line = format;
line = mLineCounter.ToString("000") + " " + line;
return line;
}
private bool DumpObjectMembers(int recursion, string indent, object obj, Action<string> writeLine)
{
if (obj == null) return false;
if (seenObjects.ContainsKey(obj))
{
writeLine(IndentedLine("{0}<already dumped on line {1}>", indent, seenObjects[obj]));
return false;
}
else
{
Type t = obj.GetType();
if (SimpleTypes.Contains(t) || obj is Type)
{
return false;
}
if (obj is Byte[])
{
Byte[] arr = obj as Byte[];
int lineNo = 0;
foreach (var line in Utils.HexDump(arr, 16))
{
if (lineNo > 100)
{
writeLine(IndentedLine("{0}...({1} bytes)", indent, arr.Length));
break;
}
writeLine(IndentedLine("{0}{1}", indent, line));
lineNo++;
}
return true;
}
if (recursion >= maxRecursion)
{
writeLine(IndentedLine("{0}<maximum recursion of {1} reached>", indent, maxRecursion));
}
seenObjects.Add(obj, mLineCounter);
bool classDisplayed = true;
int propertiesFound = 0;
while (t != null)
{
switch (mDumpStyle)
{
case DumpStyle.PublicProperties:
PropertyInfo[] properties = t.GetProperties
(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly);
foreach (PropertyInfo property in properties)
{
string l = CheckClassIsDisplayed(ref classDisplayed, indent, t);
if (l != null) writeLine(l);
Exception ex = null;
object value = null;
try
{
if (property.GetIndexParameters().Length > 0) continue;
propertiesFound++;
value = property.GetValue(obj, null);
}
catch (Exception ex0)
{
ex = ex0;
}
if (ex == null)
{
DumpObjectMember
(recursion, indent, property.Name, property.PropertyType, value, writeLine);
}
if (ex != null)
writeLine(IndentedLine(@"{0}{1} = {2} ({3})", indent, property.Name, ex.Message, ex.GetType()));
}
break;
case DumpStyle.PrivateFields:
FieldInfo[] fields = t.GetFields(BindingFlags.Instance |
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly);
foreach (FieldInfo field in fields)
{
string l = CheckClassIsDisplayed(ref classDisplayed, indent, t);
if (l != null) writeLine(l);
object value = field.GetValue(obj);
propertiesFound++;
DumpObjectMember
(recursion, indent, field.Name, field.FieldType, value, writeLine);
}
break;
}
t = t.BaseType;
classDisplayed = false;
}
if (propertiesFound == 0)
{
seenObjects.Remove(obj);
}
}
return true;
}
private string CheckClassIsDisplayed(ref bool classDisplayed, string indent, Type t)
{
if (classDisplayed == false)
{
classDisplayed = true;
return IndentedLine("{0}(inherited from {1})", indent, t);
}
else return null;
}
private void DumpObjectMember(int recursion,
string indent, string propertyName, Type propertyType, object value,
Action<string> writeLine)
{
if (value == null)
{
writeLine(IndentedLine("{0}{1} = null ({2})",
indent, propertyName, FriendlyTypeName(propertyType)));
}
else
{
string displayValue = value.ToString();
if (value is string) displayValue = String.Concat("\"",
displayValue.Replace("\"", "\"\""), '"');
string valueType = value.GetType().ToString();
if (displayValue.Contains("\r\n"))
{
writeLine(IndentedLine("{0}{1} = ({2})",
indent, propertyName, FriendlyTypeName(value.GetType())));
int lineNo = 0;
string[] lines = displayValue.Split('\r');
foreach (var line in lines)
{
if (lineNo > 100)
{
writeLine(IndentedLine("{0} ... ({1} lines)", indent, lines.Length));
break;
}
displayValue = line.TrimStart('\n');
if (lineNo >= (lines.Length - 1) && displayValue.Length == 0) break;
if (displayValue.Length >= 200) displayValue =
string.Format("{0}... (length: {1})",
displayValue.Substring(0, 200), displayValue.Length);
writeLine(IndentedLine("{0} | : {1}", indent, displayValue));
lineNo++;
}
}
else
{
if (displayValue.Length >= 200) displayValue =
string.Format("{0}... (length: {1})",
displayValue.Substring(0, 200), displayValue.Length);
if (displayValue == valueType)
{
writeLine(IndentedLine("{0}{1} ({2})",
indent, propertyName, FriendlyTypeName(value.GetType())));
}
else
{
writeLine(IndentedLine("{0}{1} = {2} ({3})",
indent, propertyName, displayValue, FriendlyTypeName(value.GetType())));
}
}
if (DumpObjectMembers(recursion + 1, indent + " | ", value, writeLine)
&& value is IEnumerable && !(value is string) && !(value is Byte[]))
{
int elementCount = 0;
Type elementType = typeof(object);
Exception ex = null;
IEnumerator enumerator = (value as IEnumerable).GetEnumerator();
bool next = false;
object element = null;
while (true)
{
try
{
next = enumerator.MoveNext();
if (next) element = enumerator.Current;
}
catch (Exception ex0)
{
ex = ex0;
}
if (!next || ex != null) break;
DumpObjectMember(recursion + 1,
indent,
string.Format("{0}[{1}]", propertyName, elementCount),
elementType, element, writeLine);
elementCount++;
}
if (ex != null) writeLine(IndentedLine("{0} * An error occured while enumerating {1} : {2}",
indent, propertyName, ex.Message));
}
}
}
private object FriendlyTypeName(Type type)
{
if (!type.IsGenericType)
{
if (type.IsPrimitive || type.Namespace == "System")
{
return type.Name;
}
else
{
return type.FullName.Replace('+', ' ');
}
}
var name = type.Name;
var index = name.IndexOf("`");
name = name.Substring(0, index);
var args = type.GetGenericArguments();
if (type.GetGenericTypeDefinition() ==
typeof(Nullable<>) && args[0].IsPrimitive)
{
return args[0].Name + '?';
}
else
{
var builder = new System.Text.StringBuilder();
builder.Append(type.Namespace);
builder.Append('.');
builder.Append(name);
builder.Append('<');
var first = true;
foreach (var arg in args)
{
if (first) first = false;
else builder.Append(',');
builder.Append(FriendlyTypeName(arg));
}
builder.Append('>');
return builder.ToString();
}
}
}
public static object DumpToString(object obj, DumpStyle dumpStyle)
{
var varDumper1 = new VarDumper();
StringBuilder result = new StringBuilder();
varDumper1.Dump(obj, dumpStyle, (s) => result.Append(s));
return result.ToString();
}
}
Points of Interest
Nothing fancy. It dumps the public
properties of the object. The main difference with Ruud version is that I keep a HashSet
of dumped objects. This way you can dump trees that point to themselves or parents and it won't show much duplication.
I added the line counter to point to objects that have already been dumped, in practice it is also useful to locate when objects start and stop.
History
- 2013-06-26 (PG) Improve array rendering (now shows at higher level) and do not rely so much on yield
- 2012-06-01 (PG) Dump byte arrays, display generics types with C# syntax.
- 2012-05-22 (PG) Display exceptions and inherited members a bit better. Also line numbers for cycling references.
- 2012-05-18 (PG) Moved the
StringBuilder
and most of the code to a class - 2009-07-28 First version from Ruud.Tech (no license attached)
I am a French programmer.
These days I spend most of my time with the .NET framework, JavaScript and html.