I stumbled upon a problem where I wanted to copy a WCF response and share it with someone, sounds easy enough but when you want to whole hierarchy of an objects properties in a clear view it can get tricky. Just like the WCF Test Client I want to get all properties and copy them but you can’t do that from the WCF Test Client unfortunately. So the best way I found is to utilize the ObjectDumper class we will create here.
So let’s say you have a bunch of classes like this
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; } } }
Then you’ll get a text like this basicly descripting the object
{MyNamespace.User} FirstName: "Arnold" LastName: "Schwarzenegger" Address: { } {MyNamespace.Address} Street: "6834 Hollywood Blvd" ZipCode: 90028 City: "Hollywood" Hobbies: ... {MyNamespace.Hobby} Name: "body building"
You will get “…” telling that it is a list-object, otherwise simply the the object { } characters indicating it is indeed a single object, then it says what class it is.
The code you need:
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 ("{ }"); } }
Usage
var dump = ObjectDumper.Dump(user);
Credit goes to ms007 for finishing this great method. So imagine you have a User object which contains a bunch of properties just like above, you will then pass the object into the Dump method then what you get is a string which describes the object structure, and with this I like to save into a text file since you’ll have \r and \n characters for tab and line breaks. Note that you will need to import System.IO to use the File methods.
var dump = ObjectDumper.Dump(proj); // Create a file to write to string createText = dump; File.WriteAllText("C:\\thing.txt", createText);
And there you go, now you have saved a text file of the object structure and their values in your C disk drive called thing.