Skip to content

Instantly share code, notes, and snippets.

@MerlinTwi
Created March 26, 2018 13:14
Show Gist options
  • Save MerlinTwi/a3177e44f48f51d65336f58a79824351 to your computer and use it in GitHub Desktop.
Save MerlinTwi/a3177e44f48f51d65336f58a79824351 to your computer and use it in GitHub Desktop.
C# Dump all properties and fields of the object to string
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using UnityEngine;
/// <summary>
/// Dump configuration
/// </summary>
public class DumpSettings {
/// <summary>Maximum number of list or IEnumerable items to display</summary>
public int MaxItems;
/// <summary>Limit tree depth to this (default 5)</summary>
public int MaxDepth;
/// <summary>true = display private members of the class/struct</summary>
public bool DisplayPrivate;
/// <summary>Use full class names</summary>
public bool UseFullClassNames;
public DumpSettings() : this(50, 5, false, false) {}
public DumpSettings(int maxItems, int maxDepth, bool displayPrivate, bool useFullClassNames) {
MaxItems = maxItems;
DisplayPrivate = displayPrivate;
MaxDepth = maxDepth;
UseFullClassNames = useFullClassNames;
}
}
/// <summary>
/// Dump all properties and fields of the object to string
/// </summary>
public class Dump {
/// <summary>Dump object to string</summary>
public static string ToDump<T>(T objectToDump) {
return ToDump(objectToDump, null, null);
}
/// <summary>Dump object to string, adding 'name=' prefix before output</summary>
public static string ToDump<T>(T objectToDump, string name) {
return ToDump(objectToDump, name, null);
}
/// <summary>Dump object to string</summary>
public static string ToDump<T>(T objectToDump, DumpSettings settings) {
return ToDump(objectToDump, null, settings);
}
/// <summary> Dump object to string, adding 'name=' prefix before output </summary>
public static string ToDump<T>(T objectToDump, string name, DumpSettings settings) {
return ToDump(objectToDump, (objectToDump == null) ? typeof (T) : objectToDump.GetType(), name, settings);
}
/// <summary>
/// Dump object to string, interpreting object as the object of type , adding 'name=' prefix before output
/// </summary>
public static string ToDump(object objectToDump, Type type, string name, DumpSettings settings, int level = 0) {
return new Dump(objectToDump, type, name, level, settings).ToString();
}
/// <summary>
/// Какие типы выводить в виде строки не заглядывая внутрь
/// </summary>
public static void AddBloatType(string typename) {
lock (BloatTypes)
BloatTypes.Add(typename, true);
}
/// <summary>
/// Какие свойства выводить в виде строки не заглядывая внутрь
/// </summary>
public static void AddBloatProperty(Type type, string propertyName) {
AddProperty(type, propertyName, false);
}
/// <summary>
/// Какие свойства не выводить вовсе
/// </summary>
public static void AddHiddenProperty(Type type, string propertyName) {
AddProperty(type, propertyName, true);
}
/// <summary>
/// Этот тип писать так
/// </summary>
public static void AddTypeName(Type type, string typename) {
lock (BloatTypes)
FriendlyName.Add(type, typename);
}
public Dump(object objectToDump, Type type, string name, int level, DumpSettings settings) {
_object = objectToDump;
_type = type;
if (_type == null)
if (objectToDump != null)
_type = objectToDump.GetType();
else
_type = typeof(object);
_name = name;
_level = level;
_settings = settings ?? DefaultSettings;
}
/// Returns dump of the object
public override string ToString() {
try {
_out.Length = 0;
Process2(_name, _type, _object, _level);
return _out.ToString();
} catch (Exception e) {
return "??? thrown " + e.GetType().FullName;
}
}
//=====================================================================================================
private static void AddProperty(Type type, string propertyName, bool sideEffect) {
lock (PropertyHints) {
Dictionary<string, bool> prop;
if (!PropertyHints.TryGetValue(type, out prop)) {
prop = new Dictionary<string, bool>();
PropertyHints.Add(type, prop);
}
prop[propertyName] = sideEffect;
}
}
/// <summary>
/// Return a user-friendly type name of a given type. For example, int?[] instead of the default name
/// </summary>
/// <param name="type">Type</param>
/// <param name="fullName">Return full name of the type</param>
/// <returns>Type name</returns>
private static string GetFriendlyTypeName(Type type, bool fullName = false) {
if (type == null)
throw new ArgumentNullException("type");
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) {
var st = Nullable.GetUnderlyingType(type);
if (st != null)
return GetFriendlyTypeName(st) + "?";
return "T?";
}
if (type.IsArray)
return GetFriendlyTypeName(type.GetElementType()) + "[" + new string(',', type.GetArrayRank() - 1) + "]";
string s;
if (!fullName && FriendlyName.TryGetValue(type, out s))
return s;
var name = fullName ? type.FullName : type.Name;
if (type.IsGenericParameter || type.IsPrimitive || !type.IsGenericType || type == typeof(decimal))
return name;
var builder = new StringBuilder();
var index = name.IndexOf('`');
if (index == -1)
builder.Append(name);
else
builder.Append(name.Substring(0, index));
builder.Append('<');
var first = true;
foreach (var arg in type.GetGenericArguments()) {
if (!first) {
builder.Append(',');
}
builder.Append(GetFriendlyTypeName(arg));
first = false;
}
builder.Append('>');
return builder.ToString();
}
private void Process2(string name, Type t, object o, int depth) {
var typeName = GetFriendlyTypeName(t, _settings.UseFullClassNames);
_out.Append(new string(' ', depth*2));
if (!string.IsNullOrEmpty(name))
_out.Append(name + " = ");
_out.Append(t.IsEnum ? "(enum " : "(").Append(typeName).Append(") ");
if (o == null) {
_out.Append("<null>");
return;
}
if (o is Transform) {
var tr = ((Transform) o);
_out.AppendFormat("{{WorldPosition={0}, LocalPosition={1}, LocalRotation={2}, LocalScale={3}}}",
tr.position, tr.localPosition, tr.localRotation, tr.localScale);
return;
}
if (((t != typeof(string) && (o is string)) || _settings.MaxDepth <= depth) && !t.IsPrimitive && !t.IsEnum) {
_out.Append("\"" + ToEscapedString(o) + "\" /* ToString */");
return;
}
if (t == typeof (string) || (o is string)) {
_out.Append("\"" + ToEscapedString(o) + "\"");
return;
}
var flags = BindingFlags.Instance | BindingFlags.FlattenHierarchy;
if (!_settings.DisplayPrivate)
flags |= BindingFlags.Public;
var props = t.GetProperties(flags | BindingFlags.GetProperty);
var fi = t.GetFields(flags);
if (t.IsPrimitive || t.IsEnum || (t.IsValueType && props.Length == 0 && fi.Length == 0) ||
BloatTypes.ContainsKey(t.FullName) || t == typeof (decimal))
{
if ((t == typeof (int)) || (t == typeof (byte)) || (t == typeof (uint)) || t == typeof (long) ||
t == typeof (ulong))
_out.AppendFormat("{0} (0x{0:x})", o);
else if (t == typeof (float) || t == typeof (double) || t == typeof (decimal))
_out.AppendFormat("{0}", o);
else if (t == typeof (DateTime) || t == typeof (DateTime?))
_out.AppendFormat("{0:o} ({1})", o, ((DateTime) o).Kind);
else if (t == typeof (TimeSpan) || t == typeof (TimeSpan?))
_out.Append(((TimeSpan) o));
else if (t == typeof (Guid))
_out.Append(((Guid) o).ToString("B"));
else if (t == typeof (bool))
_out.Append(((bool) o) ? "true" : "false");
else if (t.IsEnum)
_out.AppendFormat("[{0}] /* 0x{0:x} */", o);
else
_out.AppendFormat("[{0}]", ToEscapedString(o));
return;
}
if (t.IsClass) {
int id;
if (_usedMap.TryGetValue(o, out id)) {
_out.AppendFormat("<see #{0}>", id);
return;
}
_usedMap[o] = ++_counter;
}
if (t.IsArray && ((Array) o).Rank == 1) {
Array arr;
if (t.IsArray)
arr = (o as Array);
else
arr = t.GetMethod("ToArray").Invoke(o, new object[] {}) as Array;
ProcessArray(t, arr, depth);
} else if ((o as IEnumerable) != null) {
if (o is IList)
_out.AppendFormat("[{0}] ", ((IList)o).Count);
WriteBrace(t);
ProcessEnumerables((IEnumerable) o, depth);
} else {
WriteBrace(t);
Dictionary<string, bool> propNames = null;
foreach (var info in PropertyHints)
if (t == info.Key || t.IsSubclassOf(info.Key)) {
propNames = info.Value;
break;
}
foreach (var p in props) {
bool sideEffect;
if (propNames != null && propNames.TryGetValue(p.Name, out sideEffect)) {
if (!sideEffect)
DumpProperty(p, o, depth + 1, true);
/*else {
Process2(p.Name, p.PropertyType, "<ignored>", depth + 1);
_out.AppendLine();
}*/
} else
DumpProperty(p, o, depth + 1, false);
}
foreach (var f in fi)
DumpField(f, o, depth + 1);
}
_out.Append(new string(' ', depth*2));
_out.Append("}");
}
private static string ToEscapedString(object o) {
return o.ToString().Replace("\r", "\\r").Replace("\n", "\\n").Replace("\b", "\\b").Replace("\t", "\\t");
}
private void WriteBrace(Type t) {
//_out.AppendFormat("{{ /* #{0}, {1:x8} */ ", _counter, o.GetHashCode());
if (t.IsClass) {
_out.AppendFormat("{{ /* #{0} */ ", _counter);
_out.AppendLine();
} else
_out.AppendLine("{");
}
private void DumpProperty(PropertyInfo p, object o, int level, bool asString) {
try {
if (p.GetIndexParameters().Length == 0) {
var obj = p.GetValue(o, null);
if (asString)
Process2(p.Name, (obj == null) ? p.PropertyType : obj.GetType(), (obj ?? "<null>").ToString(), level);
else
Process2(p.Name, (obj == null) ? p.PropertyType : obj.GetType(), obj, level);
_out.AppendLine();
} else {
_out.Append(new string(' ', level*2));
_out.AppendLine(p.Name + " = " + "(" + GetFriendlyTypeName(p.PropertyType) +
") ??? indexed property ignored");
}
} catch (Exception e) {
_out.Append(new string(' ', level*2));
_out.AppendLine(p.Name + " = " + "??? thrown " + e.GetType().FullName);
}
}
private void DumpField(FieldInfo p, object o, int level) {
try {
var obj = p.GetValue(o);
Process2(p.Name, (obj == null) ? p.FieldType : obj.GetType(), obj, level);
_out.AppendLine();
} catch (Exception e) {
_out.Append(new string(' ', level*2));
_out.AppendLine(p.Name + " = " + "??? thrown " + e.GetType().FullName);
}
}
private void ProcessEnumerables(IEnumerable enumerable, int nLevel) {
var index = 0;
foreach (var info in enumerable) {
var tinside = info == null ? typeof (object) : info.GetType();
Process2("[" + index + "]", tinside, info, nLevel + 1);
_out.AppendLine();
if (index++ > _settings.MaxItems) {
_out.AppendLine("...");
break;
}
}
}
private void ProcessArray(Type t, Array arr, int nLevel) {
var tInside = t.GetElementType();
if (tInside == typeof (byte)) {
var b = (byte[]) arr;
_out.AppendFormat(" {0} bytes [ ", arr.Length);
for (var i = 0; i < b.Length; ++i) {
if (i != 0)
_out.Append(' ');
_out.Append(b[i].ToString("x2"));
if (i > _settings.MaxItems) {
_out.Append("...");
break;
}
}
_out.Append(" ]");
return;
}
_out.Append("array[" + arr.Length + "] ");
if (arr.Length > 0) {
WriteBrace(t);
ProcessEnumerables(arr, nLevel);
} else
_out.AppendLine("{");
}
private static readonly Dictionary<string, bool> BloatTypes = new Dictionary<string, bool> {
{"System.DateTime", true},
{"System.TimeSpan", true},
{"System.Type", true},
{"System.Security.Principal.SecurityIdentifier", true},
{"System.Xml.Linq.XElement", true},
{"System.Xml.Linq.XDocument", true},
{"System.Reflection.RuntimeConstructorInfo", true},
{"System.Reflection.RuntimePropertyInfo", true},
{"System.Reflection.RuntimeMethodInfo", true},
{"System.RuntimeType", true},
{"System.Reflection.MethodBase", true},
{"System.Security.Policy.Evidence", true},
{"System.Globalization.CultureInfo", true},
{"System.Version", true},
{"UnityEngine.Vector2", true},
{"UnityEngine.Vector3", true},
{"UnityEngine.Vector4", true},
{"UnityEngine.Color", true},
{"UnityEngine.Color32", true},
{"UnityEngine.Matrix4x4", true},
};
/// <summary>
/// Тип: название свойства, false-выводить в виде строки/true-не выводить вообще
/// </summary>
private static readonly Dictionary<Type, Dictionary<string, bool>> PropertyHints =
new Dictionary<Type, Dictionary<string, bool>> {
{typeof (FileSystemInfo), new Dictionary<string, bool> {{"Parent", false}, {"Root", false}}},
{typeof (Material),
new Dictionary<string, bool> {{"color", true}}
},
{typeof (Component),
new Dictionary<string, bool> {
{"gameObject", true},
{"rigidbody", true},
{"rigidbody2D", true},
{"camera", true},
{"light", true},
{"animation", true},
{"constantForce", true},
{"renderer", true},
{"audio", true},
{"guiText", true},
{"networkView", true},
{"guiElement", true},
{"guiTexture", true},
{"collider", true},
{"collider2D", true},
{"hingeJoint", true},
{"particleEmitter", true},
{"particleSystem", true},
}
},
{typeof (GameObject),
new Dictionary<string, bool> {
{"gameObject", true},
{"rigidbody", true},
{"rigidbody2D", true},
{"camera", true},
{"light", true},
{"animation", true},
{"constantForce", true},
{"renderer", true},
{"audio", true},
{"guiText", true},
{"networkView", true},
{"guiElement", true},
{"guiTexture", true},
{"collider", true},
{"collider2D", true},
{"hingeJoint", true},
{"particleEmitter", true},
{"particleSystem", true},
{"scene", false}
}
},
};
private static readonly Dictionary<Type, string> FriendlyName = new Dictionary<Type, string> {
{typeof (string), "string"},
{typeof (int), "int"},
{typeof (uint), "uint"},
{typeof (long), "long"},
{typeof (ulong), "ulong"},
{typeof (short), "short"},
{typeof (ushort), "ushort"},
{typeof (char), "char"},
{typeof (byte), "byte"},
{typeof (decimal), "decimal"},
{typeof (float), "float"},
{typeof (double), "double"},
{typeof (object), "object"},
{typeof (void), "void"},
{typeof (bool), "bool"}
};
private static readonly DumpSettings DefaultSettings = new DumpSettings();
private readonly object _object;
private readonly Type _type;
private readonly string _name;
private readonly int _level;
private readonly DumpSettings _settings;
private readonly StringBuilder _out = new StringBuilder(10000);
private readonly Dictionary<object, int> _usedMap = new Dictionary<object, int>(new ReferenceComparer());
private int _counter;
private class ReferenceComparer : IEqualityComparer<object>
{
bool IEqualityComparer<object>.Equals(object x, object y) {
return ReferenceEquals(x, y);
}
int IEqualityComparer<object>.GetHashCode(object obj) {
if (ReferenceEquals(obj, null))
return 0;
return obj.GetHashCode();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment