Skip to content

Instantly share code, notes, and snippets.

@shaybensasson
Created March 4, 2015 09:08
Show Gist options
  • Select an option

  • Save shaybensasson/aa17ebdd3e62665d19be to your computer and use it in GitHub Desktop.

Select an option

Save shaybensasson/aa17ebdd3e62665d19be to your computer and use it in GitHub Desktop.
ObjectDumper
using System;
using System.Collections;
using System.IO;
using System.Reflection;
using System.Text;
using IDictionary=System.Collections.IDictionary;
#region Class: ObjectDumper
/// <summary>
/// Dumps Object Hierarchy To String Or Console
/// </summary>
public class ObjectDumper
{
#region Fields
/// <summary>
/// Default String Use When Printing A Null Value
/// </summary>
private static string DEFAULT_NULL_STRING = "<null>";
/// <summary>
/// These Types Are Treated As Primitives - Dumped Using ToString()
/// </summary>
private static readonly Type[] __DiscardedCompositeTypes = new Type[] {
typeof(Decimal),
typeof(Enum),
typeof(DateTime),
typeof(TimeSpan),
typeof(System.Globalization.CultureInfo),
typeof(Uri),
typeof(Delegate) /*,
typeof(System.Drawing.Point),
typeof(System.Drawing.Size)*/
};
/// <summary>
/// Determines The Current Line Position
/// </summary>
private int m_Position;
/// <summary>
/// Determines The Current Level In Depth
/// </summary>
private int m_Level;
#endregion
#region Properties
/// <summary>
/// Gets The TextWriter
/// </summary>
public TextWriter Writer { get; private set; }
/// <summary>
/// Is It A Console Output?
/// </summary>
public bool IsConsole { get; private set; }
/// <summary>
/// What's The Depth That We Should Inspect?
/// </summary>
public int Depth { get; private set; }
/// <summary>
/// The Filter To Apply Displayed Data
/// </summary>
public Filters Filter { get; private set; }
#endregion
#region Methods
#region Write()
/// <summary>
/// Writes An Object (Depth=0) To Console
/// </summary>
/// <param name="element"></param>
public static void Write(object element)
{
Write(element, 0, Filters.Default);
}
/// <summary>
/// Writes An Object And It's Descendants (depth) To Console
/// </summary>
/// <param name="element"></param>
/// <param name="depth"></param>
public static void Write(object element, int depth)
{
Write(element, depth, Console.Out, Filters.Default);
}
/// <summary>
/// Writes An Object (Depth=0) To Console
/// </summary>
/// <param name="element"></param>
/// <param name="filters"></param>
public static void Write(object element, Filters filters)
{
Write(element, 0, filters);
}
/// <summary>
/// Writes An Object And It's Descendants (depth) To Console
/// </summary>
/// <param name="element"></param>
/// <param name="depth"></param>
/// <param name="filters"></param>
public static void Write(object element, int depth, Filters filters)
{
Write(element, depth, Console.Out, filters);
}
#endregion
#region WriteToString()
/// <summary>
/// Writes An Object To String
/// </summary>
/// <param name="element"></param>
/// <returns></returns>
public static string WriteToString(object element)
{
return WriteToString(element, 0, Filters.Default);
}
/// <summary>
/// Writes An Object And It's Descendants (depth) To String
/// </summary>
/// <param name="element"></param>
/// <param name="depth"></param>
/// <returns></returns>
public static string WriteToString(object element, int depth)
{
return WriteToString(element, depth, Filters.Default);
}
/// <summary>
/// Writes An Object To String
/// </summary>
/// <param name="element"></param>
/// <param name="filter"></param>
/// <returns></returns>
public static string WriteToString(object element, Filters filter)
{
return WriteToString(element, 0, filter);
}
/// <summary>
/// Writes An Object And It's Descendants (depth) To String
/// </summary>
/// <param name="element"></param>
/// <param name="depth"></param>
/// <param name="filter"></param>
/// <returns></returns>
public static string WriteToString(object element, int depth, Filters filter)
{
StringBuilder sb = new StringBuilder();
using (StringWriter sw = new StringWriter(sb))
{
Write(element, depth, sw, filter);
}
return sb.ToString();
}
#endregion
/// <summary>
/// Writes An Object And It's Descendants (depth) To TextWriter
/// </summary>
/// <param name="element"></param>
/// <param name="depth"></param>
/// <param name="log"></param>
/// <param name="filter"></param>
public static void Write(object element, int depth, TextWriter log, Filters filter)
{
try
{
var dumper = new ObjectDumper(depth, log, filter);
dumper.WriteObject(null, element);
}
catch (Exception ex)
{
throw new ApplicationException("ObjectDumper.Write() Failed!", ex);
/*try
{
static_log.Fatal("ObjectDumper.Write() Failed!", ex);
}
// ReSharper disable EmptyGeneralCatchClause
catch () { }
// ReSharper restore EmptyGeneralCatchClause*/
}
}
#endregion
#region Constructors & Destructors
/// <summary>
/// Constructs New Object Dumper
/// </summary>
/// <param name="depth"></param>
/// <param name="log"></param>
/// <param name="filter"></param>
private ObjectDumper(int depth, TextWriter log, Filters filter)
{
this.Filter = filter;
this.Depth = depth;
this.Writer = log;
this.IsConsole = (log == Console.Out);
}
#endregion
#region Helpers
/// <summary>
/// Writes String To Writer
/// </summary>
/// <param name="s"></param>
private void Write(string s)
{
if (s != null)
{
Writer.Write(s);
m_Position += s.Length; //Update Writer Position (For Tab Calculation Later)
}
}
/// <summary>
/// Writes Indents According To Level
/// </summary>
private void WriteIndent()
{
for (int i = 0; i < m_Level; i++) Writer.Write(" ");
}
/// <summary>
/// Write New Line And Resets Position
/// </summary>
private void WriteLine()
{
Writer.WriteLine();
m_Position = 0;
}
/// <summary>
/// Writes Tab For Indention
/// </summary>
private void WriteTab()
{
Write(" ");
while (m_Position % 8 != 0) Write(" ");
}
/// <summary>
/// Writes Object (And It's Descendants)
/// </summary>
/// <param name="prefix">Should Be Null If Root Level Or The Collection Name Or Collection[Key] Identifier</param>
/// <param name="element"></param>
private void WriteObject(string prefix, object element)
{
#region Primitive & Null
if (element == null || __IsDumpAsPrimitive(element.GetType()))
{
WriteIndent();
Write(prefix); //Write Collection Prefix Or Null
WriteValue(element);
WriteLine();
return; //Primitive & Null
}
#endregion
#region IDictionary
System.Collections.IDictionary dicElement = element as IDictionary;
if (dicElement != null)
{
//Dump Dictionary
__DumpDictionay(prefix, dicElement);
return; //IDictionary
}
#endregion
#region IEnumerable
System.Collections.IEnumerable enumerableElement = element as IEnumerable;
if (enumerableElement != null)
{
//Go Thru Each Item In element
foreach (object item in enumerableElement)
{
if (item is IEnumerable && !(item is string)) //Is The Item Enumerable (Except Strings)
{
//Let's Dump It's Members
WriteIndent();
Write(prefix);
Write("...");
WriteLine();
if (m_Level < Depth)
{
m_Level++;
WriteObject(prefix, item);
m_Level--;
}
}
else //Item Is Not Enumerable
{
//Write The Object With The Prefix Of The Container element
WriteObject(prefix, item);
}
}
return; //IEnumerable
}
#endregion
#region Other Types
MemberInfo[] members = element.GetType().GetMembers(BindingFlags.Public | BindingFlags.Instance);
//Determines If There's Already A Property Of This Type Written
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) //Was A Property Of This Element Already Writen?
{
WriteTab();
}
else //First Property (Write Without Indention)
{
propWritten = true;
//First Time (Write Prefix) - Only If There Are Members To Be Writen We Write Prefix
WriteIndent();
Write(prefix);
}
Write(m.Name);
Write("=");
Type t = f != null ? f.FieldType : p.PropertyType;
var value = f != null ? f.GetValue(element) : p.GetValue(element, null);
if ((value == null) || //Handle Null Values Here (Of Complex And Primitive Types)
(__IsDumpAsPrimitive(t))) //Dump Any Primitives
{
WriteValue(value);
}
else //Not null Or Primitive (We Should Dive Into It)
{
//Determines whether an instance of the current Type can be assigned from an instance of the specified Type.
if (typeof (IEnumerable).IsAssignableFrom(t))
{
Write("...");
}
else
{
Write("{ }");
}
}
}
}
if (propWritten) //Were There Any Proprties Writen? If So Added New Line
WriteLine();
if (m_Level < Depth) //Are We Allowed To Go To This Level Deep?
{
//Go Deeper, Let's Dump All Non Primitive Types
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;
if (!__IsDumpAsPrimitive(t)) //Non Primitive
{
object value = f != null ? f.GetValue(element) : p.GetValue(element, null);
if (value != null) //We Have A Complex Value
{
m_Level++; //Increase Level
//Dump Object With Prefix
WriteObject(m.Name + ": ", value);
m_Level--; //Decrease Level
}
}
}
}
}
#endregion
}
/// <summary>
/// Dumps A Dictionary
/// </summary>
/// <param name="prefix"></param>
/// <param name="dicElement"></param>
private void __DumpDictionay(string prefix, IDictionary dicElement)
{
//Go Thru Each Item In element
foreach (object oKey in dicElement.Keys)
{
var oValue = dicElement[oKey];
if (oValue is IEnumerable && !(oValue is string)) //Is The Item Enumerable (Except Strings)
{
//Let's Dump It's Members
WriteIndent();
//Write The A Dictionary Key Prefix
__WriteDictionaryPrefix(prefix, oKey);
Write("...");
WriteLine();
if (m_Level < Depth)
{
m_Level++;
//Write The Key's Value Without The Already Written Prefix
WriteObject(null, oValue);
m_Level--;
}
}
else //Item Is Not Enumerable
{
//Write The A Dictionary Key Prefix
__WriteDictionaryPrefix(prefix, oKey);
//Write The Key's Value Without The Already Written Prefix
WriteObject(null, oValue);
}
}
}
/// <summary>
/// Writes A Dictionary Key Prefix
/// </summary>
/// <param name="prefix"></param>
/// <param name="oKey"></param>
private void __WriteDictionaryPrefix(string prefix, object oKey)
{
if (string.IsNullOrEmpty(prefix))
{ //Root Is Dictionary
Write("Dic");
Write("[");
Write(oKey != null ? oKey.ToString() : null);
Write("]");
Write(": ");
}
else //Not At Root
{
Write(prefix.Substring(0, prefix.Length - 2));
Write("[");
Write(oKey != null ? oKey.ToString() : null);
Write("]");
Write(": ");
}
}
/// <summary>
/// Writes An Object
/// </summary>
/// <param name="o"></param>
private void WriteValue(object o)
{
if (o == null)
{
Write(DEFAULT_NULL_STRING);
}
else if (__IsDumpAsPrimitive(o.GetType()))
{
Write(__FormatPrimitive(o));
}
else if (o is IEnumerable)
{
Write("...");
}
else
{
Write("{ }");
}
}
/// <summary>
/// Formats A Primitive Object (Can Be Non-Primitive, Ex. __DiscardedCompositeTypes)
/// </summary>
/// <param name="o"></param>
/// <returns>The Formatted String</returns>
static string __FormatPrimitive(object o)
{
const int DIGITS_AFTER_POINT = 4;
if (o == null)
return string.Empty;
if (!(o is Enum))
{
switch (Type.GetTypeCode(o.GetType()))
{
case TypeCode.Decimal:
case TypeCode.Double:
case TypeCode.Single:
return string.Format("{0:N" + DIGITS_AFTER_POINT.ToString() + "}", o);
case TypeCode.DateTime:
return string.Format("{0:g}", o);
case TypeCode.Int16:
case TypeCode.Int32:
case TypeCode.Int64:
case TypeCode.UInt16:
case TypeCode.UInt32:
case TypeCode.UInt64:
case TypeCode.SByte:
case TypeCode.Byte:
return string.Format("{0:N0}", o);
}
}
return o.ToString();
}
/// <summary>
/// Should We Treat This Type As Primitive (Dump It Using ToString() Only)
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
bool __IsDumpAsPrimitive(Type type)
{
if (type.IsPrimitive || (type == typeof(string)))
return true;
if (type.BaseType == typeof(ValueType) && Type.GetTypeCode(type) != TypeCode.Object) //All Nullables (ValueTypes) Should Be Treated As Primitives, Except Structs
return true;
foreach (Type t in __DiscardedCompositeTypes)
{
if ((type == t) || type.IsSubclassOf(t))
return true;
}
if ((this.Filter & Filters.DumpToStringOverridesDelcaredOnly) == Filters.DumpToStringOverridesDelcaredOnly) //Emitted 'FullDumpOnToStringOverrides' Flag
{
MethodInfo mi = type.GetMethod("ToString", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly, null, new Type[0], null);
if (mi != null)
return true;
}
else //Does Not Have 'DumpToStringOverridesDelcaredOnly' Flag
{
if ((this.Filter & Filters.DumpToStringOverridesHierarchy) == Filters.DumpToStringOverridesHierarchy) //Emitted 'FullDumpOnToStringOverrides' Flag
{
MethodInfo mi = type.GetMethod("ToString", BindingFlags.Public | BindingFlags.Instance, null, new Type[0], null);
if (mi != null)
return true;
}
}
return false;
}
#endregion
#region Filters Enum
///<summary>
/// ObjectDumper Filtering
///</summary>
[Flags]
public enum Filters
{
/// <summary>
/// Exclude All Other Include Filtes
/// </summary>
Default,
/*
/// <summary>
/// Include Private Members
/// </summary>
PrivateMembers = 0x0001,
*/
/// <summary>
/// If Emitted, Composite Types That Override ToString() Will Not Serialize Fully
/// </summary>
DumpToStringOverridesDelcaredOnly = 0x0002,
/// <summary>
/// If Emitted, Composite Types That Somewhere In Their Hierachy Override ToString() Will Not Serialize Fully
/// </summary>
/// <remarks>Works Only If You Omit 'DumpToStringOverridesDelcaredOnly'</remarks>
DumpToStringOverridesHierarchy = 0x0004
/*
/// <summary>
/// All Included
/// </summary>
//AllIncluded = PrivateMembers
*/
}
#endregion
}
#endregion
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment