调试用对象输出
编写程序跟踪输出时,有时候需要输出某个对象所有字段和属性的值,类似于Watch。如果只有一个两个类的对象,事情并不麻烦,但是如果有很多类的对象,那么单独写就很费时。鉴于此,我写了一个通用的对象转储输出类,用于针对绝大多数对象进行转储为字符串形式。
编写程序跟踪输出时,有时候需要输出某个对象所有字段和属性的值,类似于Watch。如果只有一个两个类的对象,事情并不麻烦,但是如果有很多类的对象,那么单独写就很费时。鉴于此,我写了一个通用的对象转储输出类,用于针对绝大多数对象进行转储为字符串形式。
Code
using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.Reflection;
namespace Abacl.Library.Diagnostics
{
[Flags]
public enum ObjectDumpOptions
{
Fields = 0x00000001,
Properties = 0x00000002,
NonPublic = 0x00000010,
Static = 0x00000020,
Recurisive = 0x00001000,
ExpandArray = 0x00002000,
WithType = 0x00010000,
UsingFullTypeName = 0x00040000,
UsingTypeKeywords = 0x00080000,
WithName = 0x00100000,
SingleLine = 0x01000000,
NullIsNull = 0x02000000,
Default = Fields | Recurisive | UsingTypeKeywords | WithName | WithType | ExpandArray,
DefaultBrief = Fields | Recurisive | UsingTypeKeywords | WithType | WithName | SingleLine,
DefaultBriefMultiLines = Fields | Recurisive | UsingTypeKeywords | WithType| WithName
}
public static class TextDump
{
public static string DumpObject(object obj, ObjectDumpOptions options)
{
return DumpObject(obj, options, 0);
}
public static string DumpObject(object obj, ObjectDumpOptions options, int indent)
{
StringBuilder sb = new StringBuilder();
if (obj == null)
{
if ((options & ObjectDumpOptions.NullIsNull) != 0)
{
return null;
}
else
{
return "<null>";
}
}
else
{
Type type = obj.GetType();
if ((options & ObjectDumpOptions.WithType) != 0)
{
sb.Append('(');
if ((options & ObjectDumpOptions.UsingTypeKeywords) != 0)
{
if (type.IsArray)
{
sb.Append("array ");
}
if (type.IsClass && type != typeof(string))
{
if (type.IsSubclassOf(typeof(Delegate)))
{
sb.Append("delegate ");
}
else if (type.IsValueType)
{
sb.Append("struct ");
}
else
{
sb.Append("class ");
}
}
if (type.IsAnsiClass || type.IsUnicodeClass || type.IsAutoClass)
{
if (type.IsValueType && !type.IsPrimitive && !type.IsEnum)
{
sb.Append("struct ");
}
}
if (type.IsEnum)
{
sb.Append("enum ");
}
if (type.IsInterface)
{
sb.Append("interface ");
}
}
if ((options & ObjectDumpOptions.UsingFullTypeName) != 0)
{
sb.Append(ConvertToTypeKeywords(type, true));
}
else
{
sb.Append(ConvertToTypeKeywords(type, false));
}
sb.Append(')');
}
if (type.IsPrimitive)
{
if (type == typeof(char))
{
sb.Append('\'' + obj.ToString() + '\'');
}
else
{
sb.Append(obj.ToString());
}
}
else if (type.IsEnum)
{
sb.Append(obj.ToString());
}
else if (type == typeof(string))
{
sb.Append('"' + obj.ToString() + '"');
}
else if (type.IsSubclassOf(typeof(Delegate)))
{
Delegate d = (Delegate)obj;
if ((options & ObjectDumpOptions.UsingFullTypeName) != 0)
{
sb.Append(d.Method.DeclaringType.FullName + '.' + d.Method.Name);
}
else
{
sb.Append(d.Method.DeclaringType.Name + '.' + d.Method.Name);
}
}
else if (type.IsArray)
{
Array array = (Array)obj;
if ((options & ObjectDumpOptions.ExpandArray) != 0)
{
sb.Append('[');
if ((options & ObjectDumpOptions.SingleLine) == 0)
{
Indent(ref indent, sb);
}
bool first = true;
for (int i = array.GetLowerBound(0); i < array.GetUpperBound(0); ++i)
{
if (!first)
{
sb.Append(", ");
if ((options & ObjectDumpOptions.SingleLine) == 0)
{
NewLine(indent, sb);
}
}
sb.AppendFormat("array[{0}] = ", i);
sb.Append(DumpObject(array.GetValue(i), GetRecurisiveOptions(options), indent));
first = false;
}
if ((options & ObjectDumpOptions.SingleLine) == 0)
{
Unindent(ref indent, sb);
}
sb.Append(']');
}
else
{
sb.AppendFormat("<{0} elements>", array.Length);
}
}
else
{
sb.Append('[');
if ((options & ObjectDumpOptions.SingleLine) == 0)
{
Indent(ref indent, sb);
}
bool isFirst = true;
if ((options & ObjectDumpOptions.Fields) != 0)
{
foreach (FieldInfo field in type.GetFields(GetBindingFlags(options)))
{
if (!isFirst)
{
sb.Append(", ");
if ((options & ObjectDumpOptions.SingleLine) == 0)
{
NewLine(indent, sb);
}
}
if ((options & ObjectDumpOptions.WithName) != 0)
{
sb.Append(field.Name);
sb.Append(" = ");
}
sb.Append(DumpObject(field.GetValue(obj), GetRecurisiveOptions(options), indent));
isFirst = false;
}
}
if ((options & ObjectDumpOptions.Properties) != 0)
{
foreach (PropertyInfo property in type.GetProperties(GetBindingFlags(options)))
{
if (!isFirst)
{
sb.Append(", ");
if ((options & ObjectDumpOptions.SingleLine) == 0)
{
NewLine(indent, sb);
}
}
if ((options & ObjectDumpOptions.WithName) != 0)
{
sb.Append(property.Name);
sb.Append(" = ");
}
ParameterInfo[] parameters = property.GetIndexParameters();
if (parameters == null || parameters.Length == 0)
{
sb.Append(DumpObject(property.GetValue(obj, null), GetRecurisiveOptions(options), indent));
}
else
{
sb.Append("<>");
}
isFirst = false;
}
}
if ((options & ObjectDumpOptions.SingleLine) == 0)
{
Unindent(ref indent, sb);
}
sb.Append(']');
}
return sb.ToString();
}
}
public static string DumpMethod(ObjectDumpOptions options, string methodName, object result, params object[] args)
{
StringBuilder sb = new StringBuilder();
int indent = 0;
sb.Append(methodName);
sb.Append('(');
if ((options & ObjectDumpOptions.SingleLine) == 0 && args.Length != 0)
{
Indent(ref indent, sb);
}
bool isFirst = true;
foreach (object arg in args)
{
if (!isFirst)
{
sb.Append(", ");
if ((options & ObjectDumpOptions.SingleLine) == 0)
{
NewLine(indent, sb);
}
}
sb.Append(DumpObject(arg, options, indent));
isFirst = false;
}
if ((options & ObjectDumpOptions.SingleLine) == 0 && args.Length != 0)
{
Unindent(ref indent, sb);
}
sb.Append(") = ");
sb.Append(DumpObject(result, options, indent));
return sb.ToString();
}
private static string ConvertToTypeKeywords(Type type, bool fullName)
{
Debug.Assert(type != null);
if (type == typeof(bool))
{
return "bool";
}
if (type == typeof(char))
{
return "char";
}
if (type == typeof(byte))
{
return "byte";
}
if (type == typeof(sbyte))
{
return "sbyte";
}
if (type == typeof(short))
{
return "short";
}
if (type == typeof(ushort))
{
return "ushort";
}
if (type == typeof(int))
{
return "int";
}
if (type == typeof(uint))
{
return "uint";
}
if (type == typeof(long))
{
return "long";
}
if (type == typeof(ulong))
{
return "ulong";
}
if (type == typeof(float))
{
return "float";
}
if (type == typeof(double))
{
return "double";
}
if (type == typeof(decimal))
{
return "decimal";
}
if (type == typeof(string))
{
return "string";
}
if (type == typeof(Nullable))
{
return ConvertToTypeKeywords(Nullable.GetUnderlyingType(type), fullName) + "?";
}
if (fullName)
{
return type.FullName;
}
else
{
return type.Name;
}
}
private static BindingFlags GetBindingFlags(ObjectDumpOptions options)
{
BindingFlags flags = BindingFlags.Public;
if ((options & ObjectDumpOptions.NonPublic) != 0)
{
flags |= BindingFlags.NonPublic;
}
if ((options & ObjectDumpOptions.Static) != 0)
{
flags |= BindingFlags.Static;
}
else
{
flags |= BindingFlags.Instance;
}
return flags;
}
private static ObjectDumpOptions GetRecurisiveOptions(ObjectDumpOptions options)
{
if ((options & ObjectDumpOptions.Recurisive) == 0)
{
options &= ~ObjectDumpOptions.Fields;
options &= ~ObjectDumpOptions.Properties;
options &= ~ObjectDumpOptions.ExpandArray;
}
return options;
}
private static void Indent(ref int indent, StringBuilder sb)
{
++indent;
NewLine(indent, sb);
}
private static void Unindent(ref int indent, StringBuilder sb)
{
--indent;
NewLine(indent, sb);
}
private static void NewLine(int indent, StringBuilder sb)
{
sb.Append(Environment.NewLine);
sb.Append(new string(' ', indent * 2));
}
}
}
using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.Reflection;
namespace Abacl.Library.Diagnostics
{
[Flags]
public enum ObjectDumpOptions
{
Fields = 0x00000001,
Properties = 0x00000002,
NonPublic = 0x00000010,
Static = 0x00000020,
Recurisive = 0x00001000,
ExpandArray = 0x00002000,
WithType = 0x00010000,
UsingFullTypeName = 0x00040000,
UsingTypeKeywords = 0x00080000,
WithName = 0x00100000,
SingleLine = 0x01000000,
NullIsNull = 0x02000000,
Default = Fields | Recurisive | UsingTypeKeywords | WithName | WithType | ExpandArray,
DefaultBrief = Fields | Recurisive | UsingTypeKeywords | WithType | WithName | SingleLine,
DefaultBriefMultiLines = Fields | Recurisive | UsingTypeKeywords | WithType| WithName
}
public static class TextDump
{
public static string DumpObject(object obj, ObjectDumpOptions options)
{
return DumpObject(obj, options, 0);
}
public static string DumpObject(object obj, ObjectDumpOptions options, int indent)
{
StringBuilder sb = new StringBuilder();
if (obj == null)
{
if ((options & ObjectDumpOptions.NullIsNull) != 0)
{
return null;
}
else
{
return "<null>";
}
}
else
{
Type type = obj.GetType();
if ((options & ObjectDumpOptions.WithType) != 0)
{
sb.Append('(');
if ((options & ObjectDumpOptions.UsingTypeKeywords) != 0)
{
if (type.IsArray)
{
sb.Append("array ");
}
if (type.IsClass && type != typeof(string))
{
if (type.IsSubclassOf(typeof(Delegate)))
{
sb.Append("delegate ");
}
else if (type.IsValueType)
{
sb.Append("struct ");
}
else
{
sb.Append("class ");
}
}
if (type.IsAnsiClass || type.IsUnicodeClass || type.IsAutoClass)
{
if (type.IsValueType && !type.IsPrimitive && !type.IsEnum)
{
sb.Append("struct ");
}
}
if (type.IsEnum)
{
sb.Append("enum ");
}
if (type.IsInterface)
{
sb.Append("interface ");
}
}
if ((options & ObjectDumpOptions.UsingFullTypeName) != 0)
{
sb.Append(ConvertToTypeKeywords(type, true));
}
else
{
sb.Append(ConvertToTypeKeywords(type, false));
}
sb.Append(')');
}
if (type.IsPrimitive)
{
if (type == typeof(char))
{
sb.Append('\'' + obj.ToString() + '\'');
}
else
{
sb.Append(obj.ToString());
}
}
else if (type.IsEnum)
{
sb.Append(obj.ToString());
}
else if (type == typeof(string))
{
sb.Append('"' + obj.ToString() + '"');
}
else if (type.IsSubclassOf(typeof(Delegate)))
{
Delegate d = (Delegate)obj;
if ((options & ObjectDumpOptions.UsingFullTypeName) != 0)
{
sb.Append(d.Method.DeclaringType.FullName + '.' + d.Method.Name);
}
else
{
sb.Append(d.Method.DeclaringType.Name + '.' + d.Method.Name);
}
}
else if (type.IsArray)
{
Array array = (Array)obj;
if ((options & ObjectDumpOptions.ExpandArray) != 0)
{
sb.Append('[');
if ((options & ObjectDumpOptions.SingleLine) == 0)
{
Indent(ref indent, sb);
}
bool first = true;
for (int i = array.GetLowerBound(0); i < array.GetUpperBound(0); ++i)
{
if (!first)
{
sb.Append(", ");
if ((options & ObjectDumpOptions.SingleLine) == 0)
{
NewLine(indent, sb);
}
}
sb.AppendFormat("array[{0}] = ", i);
sb.Append(DumpObject(array.GetValue(i), GetRecurisiveOptions(options), indent));
first = false;
}
if ((options & ObjectDumpOptions.SingleLine) == 0)
{
Unindent(ref indent, sb);
}
sb.Append(']');
}
else
{
sb.AppendFormat("<{0} elements>", array.Length);
}
}
else
{
sb.Append('[');
if ((options & ObjectDumpOptions.SingleLine) == 0)
{
Indent(ref indent, sb);
}
bool isFirst = true;
if ((options & ObjectDumpOptions.Fields) != 0)
{
foreach (FieldInfo field in type.GetFields(GetBindingFlags(options)))
{
if (!isFirst)
{
sb.Append(", ");
if ((options & ObjectDumpOptions.SingleLine) == 0)
{
NewLine(indent, sb);
}
}
if ((options & ObjectDumpOptions.WithName) != 0)
{
sb.Append(field.Name);
sb.Append(" = ");
}
sb.Append(DumpObject(field.GetValue(obj), GetRecurisiveOptions(options), indent));
isFirst = false;
}
}
if ((options & ObjectDumpOptions.Properties) != 0)
{
foreach (PropertyInfo property in type.GetProperties(GetBindingFlags(options)))
{
if (!isFirst)
{
sb.Append(", ");
if ((options & ObjectDumpOptions.SingleLine) == 0)
{
NewLine(indent, sb);
}
}
if ((options & ObjectDumpOptions.WithName) != 0)
{
sb.Append(property.Name);
sb.Append(" = ");
}
ParameterInfo[] parameters = property.GetIndexParameters();
if (parameters == null || parameters.Length == 0)
{
sb.Append(DumpObject(property.GetValue(obj, null), GetRecurisiveOptions(options), indent));
}
else
{
sb.Append("<>");
}
isFirst = false;
}
}
if ((options & ObjectDumpOptions.SingleLine) == 0)
{
Unindent(ref indent, sb);
}
sb.Append(']');
}
return sb.ToString();
}
}
public static string DumpMethod(ObjectDumpOptions options, string methodName, object result, params object[] args)
{
StringBuilder sb = new StringBuilder();
int indent = 0;
sb.Append(methodName);
sb.Append('(');
if ((options & ObjectDumpOptions.SingleLine) == 0 && args.Length != 0)
{
Indent(ref indent, sb);
}
bool isFirst = true;
foreach (object arg in args)
{
if (!isFirst)
{
sb.Append(", ");
if ((options & ObjectDumpOptions.SingleLine) == 0)
{
NewLine(indent, sb);
}
}
sb.Append(DumpObject(arg, options, indent));
isFirst = false;
}
if ((options & ObjectDumpOptions.SingleLine) == 0 && args.Length != 0)
{
Unindent(ref indent, sb);
}
sb.Append(") = ");
sb.Append(DumpObject(result, options, indent));
return sb.ToString();
}
private static string ConvertToTypeKeywords(Type type, bool fullName)
{
Debug.Assert(type != null);
if (type == typeof(bool))
{
return "bool";
}
if (type == typeof(char))
{
return "char";
}
if (type == typeof(byte))
{
return "byte";
}
if (type == typeof(sbyte))
{
return "sbyte";
}
if (type == typeof(short))
{
return "short";
}
if (type == typeof(ushort))
{
return "ushort";
}
if (type == typeof(int))
{
return "int";
}
if (type == typeof(uint))
{
return "uint";
}
if (type == typeof(long))
{
return "long";
}
if (type == typeof(ulong))
{
return "ulong";
}
if (type == typeof(float))
{
return "float";
}
if (type == typeof(double))
{
return "double";
}
if (type == typeof(decimal))
{
return "decimal";
}
if (type == typeof(string))
{
return "string";
}
if (type == typeof(Nullable))
{
return ConvertToTypeKeywords(Nullable.GetUnderlyingType(type), fullName) + "?";
}
if (fullName)
{
return type.FullName;
}
else
{
return type.Name;
}
}
private static BindingFlags GetBindingFlags(ObjectDumpOptions options)
{
BindingFlags flags = BindingFlags.Public;
if ((options & ObjectDumpOptions.NonPublic) != 0)
{
flags |= BindingFlags.NonPublic;
}
if ((options & ObjectDumpOptions.Static) != 0)
{
flags |= BindingFlags.Static;
}
else
{
flags |= BindingFlags.Instance;
}
return flags;
}
private static ObjectDumpOptions GetRecurisiveOptions(ObjectDumpOptions options)
{
if ((options & ObjectDumpOptions.Recurisive) == 0)
{
options &= ~ObjectDumpOptions.Fields;
options &= ~ObjectDumpOptions.Properties;
options &= ~ObjectDumpOptions.ExpandArray;
}
return options;
}
private static void Indent(ref int indent, StringBuilder sb)
{
++indent;
NewLine(indent, sb);
}
private static void Unindent(ref int indent, StringBuilder sb)
{
--indent;
NewLine(indent, sb);
}
private static void NewLine(int indent, StringBuilder sb)
{
sb.Append(Environment.NewLine);
sb.Append(new string(' ', indent * 2));
}
}
}
不同的ObjectDumpOptions指示不同的输出格式:
Fields:转储字段
Properties:转储属性
NonPublic:转储非公共成员(字段、属性等)
Static:转储静态成员(如选择此选项,则实例成员不会包括的输出中)
Recursive:递归转储所有内含的类型(如不包含此项,则仅转储直接成员)
ExpandArray:扩展所有数组元素(如不包含此项,则仅输出数组的元素类型以及元素数量
WithType:输出结果中包含字段或属性的类型名
UsingFullTypeName:使用完整的类型名(包含名字空间)
UsingTypeKeywords:使用C#的关键字来代替部分内置类型,如int表示Int32
WithName:输出结果中包含字段或属性的名称
SingleLine:单行输出(如果不包含此项,则会适当分行)
NullIsNull:如果输入对象是null,则转储结果为null,否则转储结果为字符串“<null>”
Default,DefaultBrief,DefaultBriefMultiLines:包括一些常用的选项。
DumpObject函数用于转储一个对象,DumpMethod函数用于转储一个方法的调用。对于下面这个程序:
Code
public class Test
{
private int m_field;
public int Property
{
get
{
return m_field;
}
}
public Test(int value)
{
m_field = value;
}
}
class Program
{
static void Main(string[] args)
{
Test test = new Test(100);
string dump = TextDump.DumpObject(test, ObjectDumpOptions.Fields | ObjectDumpOptions.Properties | ObjectDumpOptions.NonPublic | ObjectDumpOptions.Recurisive | ObjectDumpOptions.ExpandArray | ObjectDumpOptions.WithType | ObjectDumpOptions.WithName | ObjectDumpOptions.NullIsNull);
Console.WriteLine(dump);
}
}
public class Test
{
private int m_field;
public int Property
{
get
{
return m_field;
}
}
public Test(int value)
{
m_field = value;
}
}
class Program
{
static void Main(string[] args)
{
Test test = new Test(100);
string dump = TextDump.DumpObject(test, ObjectDumpOptions.Fields | ObjectDumpOptions.Properties | ObjectDumpOptions.NonPublic | ObjectDumpOptions.Recurisive | ObjectDumpOptions.ExpandArray | ObjectDumpOptions.WithType | ObjectDumpOptions.WithName | ObjectDumpOptions.NullIsNull);
Console.WriteLine(dump);
}
}
输出的结果是:
(Test)[
m_field = (int)100,
Property = (int)100
]