Click here to Skip to main content
15,881,709 members
Articles / Programming Languages / C#
Tip/Trick

Simple Variable Dump

Rate me:
Please Sign up or sign in to vote.
4.89/5 (12 votes)
25 Jun 2013CPOL1 min read 39.5K   39   18
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...

C#
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:

C#
Utils.Dump(yourObject, Utils.DumpStyle.PrivateFields);  

In this mode, we dump all fields rather than public properties.

Complete Code

C#
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)


Written By
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.

Comments and Discussions

 
SuggestionEarlier framework versions Pin
Brad Bruce29-Jun-13 4:52
Brad Bruce29-Jun-13 4:52 
GeneralRe: Earlier framework versions Pin
Pascal Ganaye2-Jul-13 4:29
Pascal Ganaye2-Jul-13 4:29 
QuestionExample? Pin
RAND 45586628-Jun-13 2:49
RAND 45586628-Jun-13 2:49 
AnswerRe: Example? Pin
Pascal Ganaye28-Jun-13 7:40
Pascal Ganaye28-Jun-13 7:40 
GeneralRe: Example? Pin
RAND 45586628-Jun-13 9:52
RAND 45586628-Jun-13 9:52 
GeneralRe: Example? Pin
Brad Bruce29-Jun-13 4:47
Brad Bruce29-Jun-13 4:47 
GeneralRe: Example? Pin
RAND 4558661-Jul-13 8:38
RAND 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
Pascal Ganaye26-Jun-13 8:41 
SuggestionMy vote of 5 Pin
Pascal Ganaye31-May-12 21:53
Pascal Ganaye31-May-12 21:53 
Question5 Pin
beleshi28-May-12 21:32
beleshi28-May-12 21:32 
AnswerRe: 5 Pin
Pascal Ganaye28-May-12 21:59
Pascal Ganaye28-May-12 21:59 
BugSlight Typo Correction, But Still a Good Tip Pin
Hexxellor19-May-12 9:15
Hexxellor19-May-12 9:15 
GeneralRe: Slight Typo Correction, But Still a Good Tip Pin
Pascal Ganaye19-May-12 9:54
Pascal Ganaye19-May-12 9:54 
GeneralThoughts Pin
PIEBALDconsult18-May-12 5:03
mvePIEBALDconsult18-May-12 5:03 
GeneralFurther thoughts... Pin
Andrew Rissing18-May-12 5:21
Andrew Rissing18-May-12 5:21 
GeneralRe: Further thoughts... Pin
PIEBALDconsult18-May-12 5:29
mvePIEBALDconsult18-May-12 5:29 
GeneralRe: Further thoughts... Pin
Andrew Rissing21-May-12 4:45
Andrew 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.