Created
March 4, 2015 09:08
-
-
Save shaybensasson/aa17ebdd3e62665d19be to your computer and use it in GitHub Desktop.
ObjectDumper
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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