Click here to Skip to main content
12,455,517 members (65,412 online)
Click here to Skip to main content
Add your own
alternative version

Tagged as

Stats

23K views
39 bookmarked
Posted

Simple Variable Dump

, 25 Jun 2013 CPOL
Rate this:
Please Sign up or sign in to vote.
This class dumps any .NET object properties to your output window.

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                   // 8 characters for the address
            + 3;                  // 3 spaces

        int firstCharColumn = firstHexColumn
            + bytesPerLine * 3       // - 2 digit for the hexadecimal value and 1 space
            + (bytesPerLine - 1) / 8 // - 1 extra space every 8 characters from the 9th
            + 2;                  // 2 spaces

        int lineLength = firstCharColumn
            + bytesPerLine;           // - characters to show the ASCII value

        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;
            // Protect the method against endless recursion
            if (seenObjects.ContainsKey(obj))
            {
                writeLine(IndentedLine("{0}<already dumped on line {1}>", indent, seenObjects[obj]));
                return false;
            }
            else
            {
                // Determine object type
                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:
                            // Get array with properties for this object
                            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;
                                    // Get the property value
                                    propertiesFound++;
                                    value = property.GetValue(obj, null);
                                }
                                catch (Exception ex0)
                                {
                                    // You can't yield in a catch block
                                    ex = ex0;
                                }
                                if (ex == null)
                                {
                                    // Create indenting string to put
                                    // in front of properties of a deeper level
                                    // We'll need this when we display the property name and value
                                    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:
                            // Get array with properties for this object
                            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);
                                // Get the property value
                                object value = field.GetValue(obj);

                                // Create indenting string to put
                                // in front of properties of a deeper level
                                // We'll need this when we display the property name and value
                                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 the value is a string, add quotation marks
                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)
                    {
                        // The display value is identical to the type (no need to add it)
                        writeLine(IndentedLine("{0}{1} ({2})",
                            indent, propertyName, FriendlyTypeName(value.GetType())));
                    }
                    else
                    {
                        // Add property name and value to return string
                        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[]))
                {
                    // 2009-07-29: added support for collections
                    // The value is a collection (eg. it's an arraylist or generic list)
                    // so loop through its elements and dump their properties
                    int elementCount = 0;
                    Type elementType = typeof(object);
                    Exception ex = null;
                    IEnumerator enumerator = (value as IEnumerable).GetEnumerator();
                    bool next = false;
                    object element = null;
                    while (true)
                    {
                        // You can't yield in a try catch this is why
                        // I am breaking the loop with an explicit enumerator
                        try
                        {
                            next = enumerator.MoveNext();
                            if (next) element = enumerator.Current;
                        }
                        catch (Exception ex0)
                        {
                            // You can't yield in a catch block
                            ex = ex0;
                        }
                        if (!next || ex != null) break;
                        // Display the collection element name and type
                        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)

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Pascal Ganaye
Software Developer (Senior)
France France
I am a French programmer.
These days I spend most of my time with the .NET framework, JavaScript and html.

You may also be interested in...

Pro
Pro

Comments and Discussions

 
SuggestionEarlier framework versions Pin
Brad Bruce29-Jun-13 4:52
memberBrad Bruce29-Jun-13 4:52 
GeneralRe: Earlier framework versions Pin
Pascal Ganaye2-Jul-13 4:29
memberPascal Ganaye2-Jul-13 4:29 
QuestionExample? Pin
Member 45586628-Jun-13 2:49
memberMember 45586628-Jun-13 2:49 
AnswerRe: Example? Pin
Pascal Ganaye28-Jun-13 7:40
memberPascal Ganaye28-Jun-13 7:40 
GeneralRe: Example? Pin
Member 45586628-Jun-13 9:52
memberMember 45586628-Jun-13 9:52 
GeneralRe: Example? Pin
Brad Bruce29-Jun-13 4:47
memberBrad Bruce29-Jun-13 4:47 
GeneralRe: Example? Pin
Member 4558661-Jul-13 8:38
memberMember 4558661-Jul-13 8:38 
GeneralMy vote of 5 Pin
Prasad Khandekar25-Jun-13 23:31
professionalPrasad Khandekar25-Jun-13 23:31 
GeneralRe: My vote of 5 Pin
Pascal Ganaye26-Jun-13 8:41
memberPascal Ganaye26-Jun-13 8:41 
SuggestionMy vote of 5 Pin
Pascal Ganaye31-May-12 21:53
memberPascal Ganaye31-May-12 21:53 
Question5 Pin
beleshi28-May-12 21:32
memberbeleshi28-May-12 21:32 
AnswerRe: 5 Pin
Pascal Ganaye28-May-12 21:59
memberPascal Ganaye28-May-12 21:59 
BugSlight Typo Correction, But Still a Good Tip Pin
Hexxellor19-May-12 9:15
memberHexxellor19-May-12 9:15 
GeneralRe: Slight Typo Correction, But Still a Good Tip Pin
Pascal Ganaye19-May-12 9:54
memberPascal Ganaye19-May-12 9:54 
GeneralThoughts Pin
PIEBALDconsult18-May-12 5:03
memberPIEBALDconsult18-May-12 5:03 
GeneralFurther thoughts... Pin
Andrew Rissing18-May-12 5:21
memberAndrew Rissing18-May-12 5:21 
GeneralRe: Further thoughts... Pin
PIEBALDconsult18-May-12 5:29
memberPIEBALDconsult18-May-12 5:29 
GeneralRe: Further thoughts... Pin
Andrew Rissing21-May-12 4:45
memberAndrew Rissing21-May-12 4:45 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.160826.1 | Last Updated 26 Jun 2013
Article Copyright 2012 by Pascal Ganaye
Everything else Copyright © CodeProject, 1999-2016
Layout: fixed | fluid