前言
在开发中,对于经常出现一些数据类,我们常常需要知道它几乎所有字段的值,这时候为了方便,我们都是自己将对象打印到控制台中进行查看,但是如果对象没有重新ToString方法,那我们就只能看到类名,而无法知道它字段的信息,所以需要自己重写ToString方法,但是如果对于某个数据类他的所有子类都有这种需求时,那就会十分麻烦,所以通过代码去动态生成ToString的结果将非常方面后续调试。
实现
实现主要就两种方式,一种就是反射实现,还有一种就是表达式树实现。这里我两种实现都提供一些。一些的实现只简单的输出了对象值,对于对象内套对象的情况,以及数组或迭代器的情况我没有做处理,大家可自行在此基础上做实现。
反射实现
internal static class ReflectToString<T>
{
public static string ToStringEX(T obj)
{
StringBuilder sb = new StringBuilder();
Type type = typeof(T);
sb.Append('{');
foreach (FieldInfo fieldInfo in type.GetFields())
{
sb.Append($"{fieldInfo.Name}={fieldInfo.GetValue(obj)},");
}
sb.Remove(sb.Length - 1, 1);
sb.Append('}');
return sb.ToString();
}
}
通过反射去获取所有成员变量,然后再进行字符串拼接即可。
表达式树实现
internal static class ExpressionToString<T>
{
private static readonly Func<T, string> _toStringFunc;
static ExpressionToString()
{
Type type = typeof(T);
MethodInfo concat2Method = typeof(string).GetMethod("Concat", new Type[] { typeof(string), typeof(string) });
MethodInfo concat3Method = typeof(string).GetMethod("Concat", new Type[] { typeof(string), typeof(string), typeof(string) });
MethodInfo addMethod = typeof(List<string>).GetMethod("Add");
MethodInfo joinMethod = typeof(string).GetMethod("Join", new Type[] {typeof(string), typeof(IEnumerable<string>) });
MethodInfo toStringMethod = typeof(object).GetMethod("ToString");
var sepExpr = Expression.Constant(",");
var parameterExpr = Expression.Parameter(type, "obj");
var fieldsExpr = Expression.Variable(typeof(List<string>), "fields");
var expressions = new List<Expression>();
var newFieldsExpr = Expression.New(typeof(List<string>));
expressions.Add(Expression.Assign(fieldsExpr, newFieldsExpr));
foreach (FieldInfo fieldInfo in type.GetFields())
{
var fieldExpr = Expression.Field(parameterExpr, fieldInfo);
var nameExpr = Expression.Constant($"{fieldInfo.Name}=");
MethodCallExpression valueExpr;
valueExpr = Expression.Call(fieldExpr, toStringMethod);
var strExpr = Expression.Call(concat2Method, nameExpr, valueExpr);
expressions.Add(Expression.Call(fieldsExpr, addMethod, strExpr));
}
var headStrExpr = Expression.Constant("{");
var tailStrExpr = Expression.Constant("}");
var contentExpr = Expression.Call(joinMethod, sepExpr, fieldsExpr);
expressions.Add(Expression.Call(concat3Method, headStrExpr, contentExpr, tailStrExpr));
var body = Expression.Block(new ParameterExpression[] { fieldsExpr }, expressions);
_toStringFunc = Expression.Lambda<Func<T, string>>(body, parameterExpr).Compile();
}
public static string ToStringEX(T obj)
{
return _toStringFunc(obj);
}
}
可以看到表达式树实现的代码比反射要复杂很多,但是其效率也会更高。
效果展示
const int SIZE_1 = 10000;
const int SIZE_2 = 1000000;
const int SIZE_3 = 100000000;
var liSi = new Person()
{
name = "李四",
age = 15,
};
var zhangSan = new Person()
{
name = "张三",
age = 30,
friends = new List<Person>()
{
liSi
}
};
Console.WriteLine(ReflectToString<Person>.ToStringEX(zhangSan));
Console.WriteLine(ExpressionToString<Person>.ToStringEX(zhangSan));
//Console.WriteLine(Test(zhangSan, SIZE_1));
//Console.WriteLine(Test(zhangSan, SIZE_2));
//Console.WriteLine(Test(zhangSan, SIZE_3));
double Test(Person person, int count)
{
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = 0; i < count; i++)
{
string s = person.ToString();
}
watch.Stop();
//Console.WriteLine(GC.GetTotalMemory(false));
return watch.Elapsed.TotalSeconds;
}
public class Person
{
public string name;
public int age;
public List<Person> friends;
public override string ToString()
{
return ExpressionToString<Person>.ToStringEX(this);
//return ReflectToString<Person>.ToStringEX(this);
}
}
上面是反射实现的结果,下面是表达式树实现的结果
效率测试
测试代码为以上代码将注释取消,并且再ToString中切换实现方式。
以下是反射的效率:
以下是表达式树的效率:
可以看到,表达式树只有开始时慢,后面都比较快,这是因为表达式树第一次访问时有做懒加载的缓存,已然后续变快,这也是表达式树相比于反射的优势,表达式树可以做缓存,而反射不行。不过两个执行速度都不算非常快,但是正常情况要输出对象也只是再调试时使用,正常不会专门如此,所以一般不会如此极端,效率是完全够用的。