【C#】表达式树实现对象ToString方法

前言

在开发中,对于经常出现一些数据类,我们常常需要知道它几乎所有字段的值,这时候为了方便,我们都是自己将对象打印到控制台中进行查看,但是如果对象没有重新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中切换实现方式。

以下是反射的效率:

以下是表达式树的效率:

 

 可以看到,表达式树只有开始时慢,后面都比较快,这是因为表达式树第一次访问时有做懒加载的缓存,已然后续变快,这也是表达式树相比于反射的优势,表达式树可以做缓存,而反射不行。不过两个执行速度都不算非常快,但是正常情况要输出对象也只是再调试时使用,正常不会专门如此,所以一般不会如此极端,效率是完全够用的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值