247

Is there a method built into .NET that can write all the properties and such of an object to the console?

One could make use of reflection of course, but I'm curious if this already exists...especially since you can do it in Visual Studio in the Immediate Window. There you can type an object name (while in debug mode), press enter, and it is printed fairly prettily with all its stuff.

Does a method like this exist?

janw
  • 8,758
  • 11
  • 40
  • 62
Svish
  • 152,914
  • 173
  • 462
  • 620
  • 8
    Answers to this question are better than on [What is the best way to dump entire objects to a log in C#?](http://stackoverflow.com/q/360277) – Michael Freidgeim Feb 23 '16 at 05:36
  • 3
    The top answer in the 'original' question points to this question. The order is wrong. – mafu Feb 23 '16 at 11:02
  • 1
    What's with object dumpers and other reflection answers here...wouldnt a serializer achieve this straightforward? – nawfal Jun 28 '16 at 13:25

9 Answers9

379

You can use the TypeDescriptor class to do this:

foreach(PropertyDescriptor descriptor in TypeDescriptor.GetProperties(obj))
{
    string name = descriptor.Name;
    object value = descriptor.GetValue(obj);
    Console.WriteLine("{0}={1}", name, value);
}

TypeDescriptor lives in the System.ComponentModel namespace and is the API that Visual Studio uses to display your object in its property browser. It's ultimately based on reflection (as any solution would be), but it provides a pretty good level of abstraction from the reflection API.

janw
  • 8,758
  • 11
  • 40
  • 62
Sean
  • 60,939
  • 11
  • 97
  • 136
  • Cool! Didn't know about that. How is using this PropertyDescriptor and GetValue, compared to using obj.GetType().GetProperties() and GetValue and SetValue? Is it kind of the same just a different "interface"? – Svish May 12 '09 at 11:55
  • It's a high level API over the reflection API. It's geared towards displaying properties in a user-friendly manner. The PropertyDescriptor class has various methods to allow you to easily edit, change and reset the property value, if you wan to. – Sean May 12 '09 at 12:02
  • If you want to filter property with null value and order it alphabetically var type = obj.GetType(); var props = type.GetProperties(); var pairs = props.Where(x => x.GetValue(obj, null) != null) //Not include property with null value .Select(x => x.Name + ": " + x.GetValue(obj, null).ToString().Trim()) .OrderBy(x => x, StringComparer.Ordinal).ToArray(); //Order by alphabet var qs = string.Join(", ", pairs); return qs.ToString(); – Ray Krungkaew Sep 19 '16 at 07:22
  • 16
    Good job mentioning the namespace in your answer! – Lawyerson Nov 24 '16 at 16:13
  • 1
    I have modified above answer to support nested properties: http://stackoverflow.com/questions/360277/what-is-the-best-way-to-dump-entire-objects-to-a-log-in-c/42264037#answer-42264037 – engineforce Feb 16 '17 at 03:13
  • 5
    @Best_Where_Gives - So you could extend the code to handle this, at engineforce has done. Sometimes you've got to write a bit of code yourself..! – Sean Feb 07 '18 at 09:14
116

Based on the ObjectDumper of the LINQ samples I created a version that dumps each of the properties on its own line.

This Class Sample

namespace MyNamespace
{
    public class User
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public Address Address { get; set; }
        public IList<Hobby> Hobbies { get; set; }
    }

    public class Hobby
    {
        public string Name { get; set; }
    }

    public class Address
    {
        public string Street { get; set; }
        public int ZipCode { get; set; }
        public string City { get; set; }    
    }
}

has an output of

{MyNamespace.User}
  FirstName: "Arnold"
  LastName: "Schwarzenegger"
  Address: { }
    {MyNamespace.Address}
      Street: "6834 Hollywood Blvd"
      ZipCode: 90028
      City: "Hollywood"
  Hobbies: ...
    {MyNamespace.Hobby}
      Name: "body building"

Here is the code.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Text;

public class ObjectDumper
{
    private int _level;
    private readonly int _indentSize;
    private readonly StringBuilder _stringBuilder;
    private readonly List<int> _hashListOfFoundElements;

    private ObjectDumper(int indentSize)
    {
        _indentSize = indentSize;
        _stringBuilder = new StringBuilder();
        _hashListOfFoundElements = new List<int>();
    }

    public static string Dump(object element)
    {
        return Dump(element, 2);
    }

    public static string Dump(object element, int indentSize)
    {
        var instance = new ObjectDumper(indentSize);
        return instance.DumpElement(element);
    }

    private string DumpElement(object element)
    {
        if (element == null || element is ValueType || element is string)
        {
            Write(FormatValue(element));
        }
        else
        {
            var objectType = element.GetType();
            if (!typeof(IEnumerable).IsAssignableFrom(objectType))
            {
                Write("{{{0}}}", objectType.FullName);
                _hashListOfFoundElements.Add(element.GetHashCode());
                _level++;
            }

            var enumerableElement = element as IEnumerable;
            if (enumerableElement != null)
            {
                foreach (object item in enumerableElement)
                {
                    if (item is IEnumerable && !(item is string))
                    {
                        _level++;
                        DumpElement(item);
                        _level--;
                    }
                    else
                    {
                        if (!AlreadyTouched(item))
                            DumpElement(item);
                        else
                            Write("{{{0}}} <-- bidirectional reference found", item.GetType().FullName);
                    }
                }
            }
            else
            {
                MemberInfo[] members = element.GetType().GetMembers(BindingFlags.Public | BindingFlags.Instance);
                foreach (var memberInfo in members)
                {
                    var fieldInfo = memberInfo as FieldInfo;
                    var propertyInfo = memberInfo as PropertyInfo;

                    if (fieldInfo == null && propertyInfo == null)
                        continue;

                    var type = fieldInfo != null ? fieldInfo.FieldType : propertyInfo.PropertyType;
                    object value = fieldInfo != null
                                       ? fieldInfo.GetValue(element)
                                       : propertyInfo.GetValue(element, null);

                    if (type.IsValueType || type == typeof(string))
                    {
                        Write("{0}: {1}", memberInfo.Name, FormatValue(value));
                    }
                    else
                    {
                        var isEnumerable = typeof(IEnumerable).IsAssignableFrom(type);
                        Write("{0}: {1}", memberInfo.Name, isEnumerable ? "..." : "{ }");

                        var alreadyTouched = !isEnumerable && AlreadyTouched(value);
                        _level++;
                        if (!alreadyTouched)
                            DumpElement(value);
                        else
                            Write("{{{0}}} <-- bidirectional reference found", value.GetType().FullName);
                        _level--;
                    }
                }
            }

            if (!typeof(IEnumerable).IsAssignableFrom(objectType))
            {
                _level--;
            }
        }

        return _stringBuilder.ToString();
    }

    private bool AlreadyTouched(object value)
    {
        if (value == null)
            return false;

        var hash = value.GetHashCode();
        for (var i = 0; i < _hashListOfFoundElements.Count; i++)
        {
            if (_hashListOfFoundElements[i] == hash)
                return true;
        }
        return false;
    }

    private void Write(string value, params object[] args)
    {
        var space = new string(' ', _level * _indentSize);

        if (args != null)
            value = string.Format(value, args);

        _stringBuilder.AppendLine(space + value);
    }

    private string FormatValue(object o)
    {
        if (o == null)
            return ("null");

        if (o is DateTime)
            return (((DateTime)o).ToShortDateString());

        if (o is string)
            return string.Format("\"{0}\"", o);

        if (o is char && (char)o == '\0') 
            return string.Empty; 

        if (o is ValueType)
            return (o.ToString());

        if (o is IEnumerable)
            return ("...");

        return ("{ }");
    }
}

and you can use it like that:

var dump = ObjectDumper.Dump(user);

Edit

  • Bi - directional references are now stopped. Therefore the HashCode of an object is stored in a list.
  • AlreadyTouched fixed (see comments)
  • FormatValue fixed (see comments)
ms007
  • 4,661
  • 3
  • 28
  • 31
  • 1
    Be careful with this, if you have bi-directional object references you can hit a stackoverflow exception – reustmd Nov 20 '12 at 16:51
  • why use a hash? Wouldn't referential integrity be enough? – reustmd Dec 02 '12 at 01:18
  • may want to set a max level (going 10 objects deep is probably not desirable) and if the element is a stream this will throw an exception – Mario Feb 13 '13 at 22:59
  • 3
    `AlreadyTouched` throws an exception if the object is null. You'll want to add `if (value == null) return false;` to the beginning of this method. – Benbob Jun 04 '13 at 04:52
  • You might want to add this in the FormatValue after the string check: if (o is char && (char)o == '\0') return ""; This fixes the issue with char's default equal to '\0'. which truncates the string effectively in Windows. – Ilya Chernomordik Jan 20 '14 at 16:07
  • I have the following property (public Dictionary> MyDictionary { get; set; }) that it show in the dump string as MyDictionary : ... ["string1", System.Collections.Generic.List`1[MyClass]] It's not possible to get out the List in the Dictionary ? – Nicola C. Jun 04 '14 at 16:11
  • 23
    I used your code with some changes and put it up on https://github.com/mcshaz/BlowTrial/blob/master/GenericToDataFile/ObjectDumper.cs - this handles more complex objects, including recursion depth, skips indexers and properties without getters, and avoids unnecessary calls to StringBuiler ToString. It also changes the 'bidirectional reference' message to 'reference already dumped', as a reference found in the hashtable is not always bidirectional (eg a list with the same instance repeated). – Brent Sep 15 '14 at 04:15
  • Works for me, thanks! – GTCrais Dec 27 '15 at 21:32
  • What is a bidirectional reference? – sliders_alpha Nov 02 '16 at 13:12
  • @sliders_alpha a bidirectional reference can happen when you include an object (foo) which references an other object (bar) that itself references back to an object of the original type (foo) – ms007 Nov 03 '16 at 11:58
  • For those using this for debugging purposes and wish to see private fields and properties search for 'BindingFlags' and add BindingFlags.NonPublic to the or'ed list. – PeterVermont Nov 04 '16 at 15:07
  • Based on @Brent, I've add some modification to support UWP. https://gist.github.com/syaifulnizamyahya/e9b7d935f14940b236445508aab0320f – Syaiful Nizam Yahya Nov 09 '16 at 09:53
  • Thank you so much to the original author and all contributors. Would like to notify that I'm using this code as well. – jmdev Oct 14 '17 at 16:45
  • Amazing, thank you very much for this ! – Best_Where_Gives Feb 07 '18 at 08:24
  • Thank you! Would you mind to add logic to print class member description, please? – NoWar Apr 21 '21 at 01:08
73

The ObjectDumper class has been known to do that. I've never confirmed, but I've always suspected that the immediate window uses that.

EDIT: I just realized, that the code for ObjectDumper is actually on your machine. Go to:

C:/Program Files/Microsoft Visual Studio 9.0/Samples/1033/CSharpSamples.zip

This will unzip to a folder called LinqSamples. In there, there's a project called ObjectDumper. Use that.

Robert Harvey
  • 178,213
  • 47
  • 333
  • 501
BFree
  • 102,548
  • 21
  • 159
  • 201
  • Woah, that totally worked. All though some depth control would have been nice to have, haha. Thanks for great tip! =) – Svish May 12 '09 at 11:23
  • See my edit. The one in the samples actually has an overload that takes depth. – BFree May 12 '09 at 11:28
  • Hm, is it just me, or is this outputting everything on a single line? – Svish May 12 '09 at 12:39
  • Hm, no it is outputting each object on one line it seems... So all the properties come on one line. Hmm... – Svish May 12 '09 at 12:45
  • Sadly, one object per line == chaos when your object has a few large `byte[]`s in it ;-) – mpontillo Mar 03 '11 at 20:02
  • 6
    This is probably obvious but VS2010 users will (most likely) find it here: C:\Program Files (x86)\Microsoft Visual Studio 10.0\Samples\1033 – Lee Oades Jul 19 '11 at 12:27
  • I found it here. http://code.msdn.microsoft.com/LINQ-Sample-Queries-13a42a54 – Narayana Dec 30 '13 at 09:07
  • 9
    [nuget package](http://www.nuget.org/packages/ObjectDumper/) for ObjectDumper is now available. It also provides an extension method `DumpToString` and `Dump` to `Object` class. Handy. – IsmailS Jun 17 '15 at 09:43
  • `w3wp.exe` crashes when I attempt to use `ObjectDumper` like `Request.DumpToString("aaa");` – Paul May 02 '16 at 15:41
  • Here's another Link examples download, and includes ObjectDumper, including support for depth (looks like an upgrade?): https://code.msdn.microsoft.com/MSDN-108-C-LINQ-Samples-52207c43#content – jrypkahauer Jul 05 '18 at 18:37
35

Maybe via JavaScriptSerializer.Serialize?

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • 1
    Interesting... how would you use that? – Svish May 12 '09 at 11:00
  • You'd need to tidy the JSON blob up so its presentable.. and i'd say it would take as many lines doing that as writing your own reflection code. But that's my 2c. – Matt Kocaj May 12 '09 at 11:04
  • good point cottsak. figured out how to use it now, and allthough all the data seems to be there, it was not very readable out of the box =) – Svish May 12 '09 at 11:08
  • 3
    This works great; I use a [JSON formatter](http://jsonformatter.curiousconcept.com/) to make it readable. – phloopy Nov 07 '11 at 20:37
  • This is the best answer.Use a proven framework instead of rolling out your own code. – Jason Turan Jun 16 '16 at 14:18
  • 1
    Add Reference in Solution Explorer: `System.Web.Extentions` Then, in the code: `using System.Web.Script.Serialization;` ... `private static JavaScriptSerializer jss = new JavaScriptSerializer();` ... `Console.Out.WriteLine(jss.Serialize(someObject));` – Andrew May 18 '18 at 19:05
  • 2
    I like this approach. It's quite light and the output is clear. In my case, I go with [Newtonsoft's JSON.net](https://www.newtonsoft.com/json) like below, `var respJson = JsonConvert.SerializeObject(objResponse, Formatting.Indented); Console.WriteLine(respJson); ` – Sphinx Dec 15 '21 at 15:16
22

Following snippet will do the desired function:

Type t = obj.GetType(); // Where obj is object whose properties you need.
PropertyInfo [] pi = t.GetProperties();
foreach (PropertyInfo p in pi)
{
    System.Console.WriteLine(p.Name + " : " + p.GetValue(obj));
}

I think if you write this as extension method you could use it on all type of objects.

Ajeet Eppakayala
  • 1,186
  • 1
  • 10
  • 17
TheVillageIdiot
  • 40,053
  • 20
  • 133
  • 188
  • 3
    This wouldn't handle objects consisting of other objects though. It also does not output the values of the properties. Only the names. And I already know those :P – Svish May 12 '09 at 11:19
  • 2
    @Svish but your question doesnt give that idea at all. Kindly edit. – nawfal Jun 14 '13 at 08:24
  • 3
    @nawfal Others seemed to get the idea fine. – Svish Jun 15 '13 at 17:08
  • 1
    Type t = typeof(T); foreach (var p in t.GetProperties()) { System.Console.WriteLine(p.Name + " " + p.GetType().ToString()); } – boctulus Apr 14 '15 at 20:35
8

Regarding TypeDescriptor from Sean's reply (I can't comment because I have a bad reputation)... one advantage to using TypeDescriptor over GetProperties() is that TypeDescriptor has a mechanism for dynamically attaching properties to objects at runtime and normal reflection will miss these.

For example, when working with PowerShell's PSObject, which can have properties and methods added at runtime, they implemented a custom TypeDescriptor which merges these members in with the standard member set. By using TypeDescriptor, your code doesn't need to be aware of that fact.

Components, controls, and I think maybe DataSets also make use of this API.

Josh
  • 68,005
  • 14
  • 144
  • 156
2

This is exactly what reflection is for. I don't think there's a simpler solution, but reflection isn't that code intensive anyway.

Jon B
  • 51,025
  • 31
  • 133
  • 161
1

Don't think so. I've always had to write them or use someone else's work to get that info. Has to be reflection as far as i'm aware.

EDIT:
Check this out. I was investigating some debugging on long object graphs and noticed this when i Add Watches, VS throws in this class: Mscorlib_CollectionDebugView<>. It's an internal type for displaying collections nicely for viewing in the watch windows/code debug modes. Now coz it's internal you can reference it, but u can use Reflector to copy (from mscorlib) the code and have your own (the link above has a copy/paste example). Looks really useful.

Matt Kocaj
  • 11,278
  • 6
  • 51
  • 79
1

Any other solution/library is in the end going to use reflection to introspect the type...

mP.
  • 18,002
  • 10
  • 71
  • 105