Created
March 26, 2018 13:14
-
-
Save MerlinTwi/a3177e44f48f51d65336f58a79824351 to your computer and use it in GitHub Desktop.
C# Dump all properties and fields of the object to string
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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