表达式树解析

相信各位在写程序时都会碰到,对象拷贝的问题。例如:

class PersonEntity
{
public long Id { get; set; }
public string Name { get; set; }
public string XXX { get; set; }
public int Age { get; set; }
public DateTime CreateTime { get; set; } = DateTime.Now;
}
class PersonDTO
{
public long Id { get; set; }
public string Name { get; set; }
public string XXX { get; set; }
public int Age { get; set; }
}

  将一个PersonEntity对象的属性值,赋值给一个PersonDTO对象。一般我们的处理方式就是写一个TOXXX方法。但是如果下次换一个类,那就又得写一个TOXXX方法。而且如果类添加或者减少一个字段,那么就得修改这个方法,如果字段少,倒还好,字段一多,就凉凉。到了这里,我想大家一定会想到用反射解决这个问题,但反射有个效率问题。今天我介绍一种新的方法:那就是表达式树。说到表达式树,如果你用过EF,一定知道xxxx.Where(e=>e.Id==1)这个语法,where 方法里的语句很是一个lambda表达式,其实它是一个Expression<Func<T,bool>>的表达式树。下面我们来看看:

例如:

        static void Main(string[] args)
        {
            Expression<Func<PersonEntity, PersonDTO>> ex = e => new PersonDTO
            {
                Id = e.Id,
                Name = e.Name
            };
            PersonDTO dto = ex.Compile()(new PersonEntity { Id = 1, Name = "xxx" });

            Console.ReadKey();
        }

  我们来看下编译器将它编译成什么样吧:

 1 private static void Main(string[] args)
 2         {
 3             ParameterExpression parameterExpression = Expression.Parameter(typeof(PersonEntity), "e");
 4             Expression<Func<PersonEntity, PersonDTO>> expression = Expression.Lambda<Func<PersonEntity, PersonDTO>>(Expression.MemberInit(Expression.New(typeof(PersonDTO)), new MemberBinding[]
 5             {
 6                 Expression.Bind((MethodInfo)MethodBase.GetMethodFromHandle(ldtoken(set_Id())), Expression.Property(parameterExpression, (MethodInfo)MethodBase.GetMethodFromHandle(ldtoken(get_Id())))), 
 7                 Expression.Bind((MethodInfo)MethodBase.GetMethodFromHandle(ldtoken(set_Name())), Expression.Property(parameterExpression, (MethodInfo)MethodBase.GetMethodFromHandle(ldtoken(get_Name()))))
 8             }), new ParameterExpression[]
 9             {
10                 parameterExpression
11             });
12             PersonDTO personDTO = expression.Compile()(new PersonEntity
13             {
14                 Id = 1L, 
15                 Name = "xxx"
16             });
17             Console.ReadKey();
18         }

同志们,不要慌,我们来给他梳理下,然后就变成我们看的懂的了:

 1                 Type dtoType = typeof(PersonDTO);
 2                 Type entityType = typeof(PersonEntity);
 3 
 4                 //创建一个参数表达式
 5                 ParameterExpression parameterExpression = Expression.Parameter(entityType, "e");
 6 
 7                 //创建一个访问属性表达式
 8                 MemberExpression idMemberEx = Expression.Property(parameterExpression, entityType.GetProperty("Id"));
 9                 //创建一个属性或者字段初始化表达式
10                 MemberBinding idMemberBinding= Expression.Bind(dtoType.GetProperty("Id"),idMemberEx);
11 
12                 //创建一个访问属性表达式
13                 MemberExpression nameMemberEx = Expression.Property(parameterExpression, entityType.GetProperty("Name"));
14                 //创建一个属性或者字段初始化表达式
15                 MemberBinding nameMemberBinding = Expression.Bind(dtoType.GetProperty("Name"), nameMemberEx);
16 
17                 var properMemberBindings = new MemberBinding[] {
18                     idMemberBinding,nameMemberBinding
19                 };
20                 //创建一个创建对象表达式,它会自动调用该类型的无参构造函数
21                 NewExpression dtonewex = Expression.New(dtoType);
22                 ///创建表达式主体。用于填充 System.Linq.Expressions.MemberBinding 集合的 System.Linq.Expressions.MemberInitExpression.Bindings   对象的数组。
23                 MemberInitExpression memberInit = Expression.MemberInit(dtonewex, properMemberBindings);
24 
25                 //创建一个lambda表达式,表达式主体是memberInit,参数是parameterExpression
26                 Expression<Func<PersonEntity, PersonDTO>> expression = Expression.Lambda<Func<PersonEntity, PersonDTO>>(memberInit, new ParameterExpression[]
27                 {
28                     parameterExpression
29                 });
30                 //将表达式树生成一个委托
31                 PersonDTO personDTO = expression.Compile()(new PersonEntity
32                 {
33                     Id = 1L,
34                     Name = "xxx"
35                 });
36                 Console.WriteLine(personDTO.Name);

  运行结果为:

我们可以将它封装下,变成我们想要的万能的TOXXX方法

    public class ValTransformationCommon<TEntity, TRes> where TEntity : class, new() where TRes : class, new()
    {
        private static IDictionary<string, Func<TEntity, TRes>> mapperDicts = new ConcurrentDictionary<string, Func<TEntity, TRes>>();
        /// <summary>
        /// 实体映射,暂且不能映射导航属性。规则是:类中的属性名字必须一样
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="obj"></param>
        /// <returns></returns>
        public static TRes Mapper(TEntity obj)
        {
            Type enType = typeof(TEntity);
            Type resType = typeof(TRes);
            string key = $"mapperKey_{enType.Name}_{resType.Name}";
            //判断缓存里是否存在该委托
            if (mapperDicts.ContainsKey(key))
            {
                return mapperDicts[key](obj);
            }
            //创建表达式树参数
            ParameterExpression parameter = Expression.Parameter(enType, "e");
            List<MemberBinding> list = new List<MemberBinding>();
            //遍历属性
            foreach (var item in resType.GetProperties())
            {
                var pro = enType.GetProperty(item.Name);
                if (pro == null)
                {
                    continue;
                }
                MemberExpression propertyExpression = Expression.Property(parameter, pro);
                MemberBinding bind = Expression.Bind(item, propertyExpression);
                list.Add(bind);
            }
            //遍历字段
            foreach (var item in resType.GetFields())
            {
                var filed = enType.GetField(item.Name);
                if (filed == null)
                {
                    continue;
                }
                MemberExpression property = Expression.Field(parameter, filed);
                MemberBinding bind = Expression.Bind(item, property);
                list.Add(bind);
            }
            //将属性值和字段值复制给新的对象,
            MemberInitExpression init = Expression.MemberInit(Expression.New(resType), list);
            //获取表达式树
            Expression<Func<TEntity, TRes>> expression = Expression.Lambda<Func<TEntity, TRes>>(init, parameter);
            Func<TEntity, TRes> func = expression.Compile();
            //将委托缓存起来
            mapperDicts[key] = func;
            return func(obj);
        }     
    }

那么它和反射相比,那个效率高呢?下面我们来对比下吧:

反射的方法:

   static TResult TOXXX<TResult, TEntity>(TEntity entity) where TEntity : class, new() where TResult : class, new()
        {
            Type entityType = typeof(TEntity);
            Type resType = typeof(TResult);
            TResult result = (TResult)Activator.CreateInstance(resType);
            foreach (var item in entityType.GetProperties())
            {
                var pro = resType.GetProperty(item.Name);
                if (resType.GetProperty(item.Name) == null)
                {
                    continue;
                }
                pro.SetValue(result,item.GetValue(entity));
            }
            foreach (var item in entityType.GetFields())
            {
                var pro = resType.GetField(item.Name);
                if (resType.GetProperty(item.Name) == null)
                {
                    continue;
                }
                pro.SetValue(result, item.GetValue(entity));
            }
            return result;
        }

测试的代码:

            {
                //for (int i = 0; i < 1000; i++)
                //{
                //    Stopwatch stop = new Stopwatch();
                //    stop.Start();
                //    var en = new PersonEntity { Id = 1, Name = "1111", XXX="xxx", Age=1 };
                //    var dto = new PersonDTO { Age = en.Age, Id = en.Id, Name = en.Name, XXX = en.XXX };
                //    stop.Stop();
                //    Console.WriteLine(stop.Elapsed.TotalMilliseconds);
                //}
            }

            {
                //for (int i = 0; i < 1000; i++)
                //{
                //    Stopwatch stop = new Stopwatch();
                //    stop.Start();
                //    var dto = TOXXX<PersonDTO, PersonEntity>(new PersonEntity { Id = 1, Name = "1111", XXX = "xxx", Age = 1 });
                //    stop.Stop();
                //    Console.WriteLine(stop.Elapsed.TotalMilliseconds);
                //}
            }
            {
                for (int i = 0; i < 1000; i++)
                {
                    Stopwatch stop = new Stopwatch();
                    stop.Start();
                    var dto = ValTransformationCommon<PersonEntity, PersonDTO>.Mapper(new PersonEntity { Id = 1, Name = "1111", XXX = "xxx", Age = 1 });
                    stop.Stop();
                    Console.WriteLine(stop.Elapsed.TotalMilliseconds);
                }
            }

我们来试下10万次的赋值看下各自的消耗时间吧

硬编码:

反射:

表达式树:

猜你喜欢

转载自www.cnblogs.com/norain/p/10654255.html