zoukankan      html  css  js  c++  java
  • 表达式树解析

    简言:

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

      

    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万次的赋值看下各自的消耗时间吧

    硬编码:

    反射:

    表达式树:

     表达式树除了第一次执行很慢,后面基本等同于硬编码的速度了。效率比反射快5-6倍。

  • 相关阅读:
    ASP.NET中几种加密方法
    Linux Mint17.1安装PHPStorm8.0.2
    HTTP 错误 500.23
    Kali Linux 下安装配置MongoDB数据库 ubuntu 下安装配置MongoDB源码安装数据库
    如何在Ubuntu 18.04 LTS上安装和配置MongoDB
    scrapy 如何链接有密码的redis scrapy-redis 设置redis 密码 scrapy-redis如何为redis配置密码
    Redis报错:DENIED Redis is running in protected mode
    ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2 "No such file or directory")
    用mingw32编译ffmpeg2.7
    VS2005编译QT4.8.2
  • 原文地址:https://www.cnblogs.com/norain/p/10654255.html
Copyright © 2011-2022 走看看