zoukankan      html  css  js  c++  java
  • 第十五节:Expression表达式目录树(与委托的区别、自行拼接、总结几类实例间的拷贝)

    一. 基本介绍

    回忆: 最早接触到表达式目录树(Expression)可能要追溯到几年前使用EF早期的时候,发现where方法里的参数是Expression<Func<T,bool>>这么一个类型,当初不是很理解,只是知道传入lambda表达式使用即可,对于Expression和里面的Func<T,bool>到底是怎么一种关系,都不清楚。

      今天,带着回忆开发初期的心情,详细的介绍一下这一段时间对Expression的理解。

    1. Expression与Func委托的区别

      ①:委托是一种类型,是方法的抽象,通过委托可以将方法以参数的形式传递给另一个方法,同时调用委托的时候,它缩包含的方法都会被实现。委托的关键字是delegate,可以自定义委托,也可以使用内置委托,通过简化,可以将Lambda表达式或Lambda语句赋值给委托,委托的调用包括同步调用和异步调用。

      ②:表达式目录树(Expression),是一种数据结构,可以利用Lambda表达式进行声明,Lambda表达式的规则要符合Expression中Func委托的参数规则。

    可以利用Lambda表达式进行声明,但Lambda语句是不能声明的。

      Expression调用Compile方法可以转换成<TDelegate>中的委托。

    回顾委托的代码:

    复制代码
     1  {
     2                 //1. Func委托,必须要有返回值,最后一个参数为返回值,前面为输入参数
     3                 Func<int, int, int> func1 = new Func<int, int, int>((int m, int n) =>
     4                 {
     5                     return m * n + 2;
     6                 });
     7                 //对其进行最简化(Lambda语句)
     8                 Func<int, int, int> func2 = (m, n) =>
     9                 {
    10                     return m * n + 2;
    11                 };
    12                 //对其进行最简化(Lambda表达式)
    13                 Func<int, int, int> func3 = (m, n) => m * n + 2;
    14                 //调用委托
    15                 int result1 = func1.Invoke(2, 3);
    16                 int result2 = func2.Invoke(2, 3);
    17                 int result3 = func3.Invoke(2, 3);
    18                 Console.WriteLine("委托三种形式结果分别为:{0},{1},{2}", result1, result2, result3);
    19 }
    复制代码

    初识Expression表达式目录树的代码

    复制代码
     1  {
     2                 //报错 (Lambda语句无法转换成表达式目录树)
     3                 //Expression<Func<int, int, int>> exp1 = (m, n) =>
     4                 //{
     5                 //    return m * n + 2;
     6                 //};
     7 
     8                 //利用Lambda表达式  来声明 表达式目录树
     9                 Expression<Func<int, int, int>> exp2 = (m, n) => m * n + 2;
    10 
    11                 //利用Compile编译,可以将表达式目录树转换成委托
    12                 Func<int, int, int> func = exp2.Compile();
    13                 int result1 = func.Invoke(2, 3);
    14                 Console.WriteLine("表达式目录树转换成委托后结果为:{0}", result1);
    15 }
    复制代码

    执行结果

    2. 自己拼接表达式目录树

      ①. 核心:先把Lambda表达式写出来,然后用ILSpy工具进行编译,就能把表达式目录树的拼接过程给显示出来,当然有些代码不是我们想要的,需要转换成C#代码。

      ②. 了解拼接要用到的类型和方法:

      类型:常量值表达式(ConstantExpression)、命名参数表达式(ParameterExpression)、含二元运算符的表达式(BinaryExpression)

      方法:设置常量表达式的值(Constant方法)、设置参数表达式的变量(Parameter方法)、相加(Add方法)、相乘(Multiply方法)、Lambda方法:将拼接的二元表达式转换成表达式目录树

      ③. 步骤:声明变量和常量→进行二元计算→将最终二元运算符的表达式转换成表达式目录树

     下面分享拼接 Expression<Func<int, int, int>> express1 = (m, n) => m * n + 2; 的代码:

    复制代码
    {
                    Func<int, int, int> func1 = (m, n) => m * n + 2;
                    //利用反编译工具翻译下面这个Expression
                    Expression<Func<int, int, int>> express1 = (m, n) => m * n + 2;
                    //下面是反编译以后的代码(自己稍加改进了一下)
                    ParameterExpression parameterExpression = Expression.Parameter(typeof(int), "m");
                    ParameterExpression parameterExpression2 = Expression.Parameter(typeof(int), "n");
                    BinaryExpression binaryExpression = Expression.Multiply(parameterExpression, parameterExpression2);
                    ConstantExpression constantExpression = Expression.Constant(2, typeof(int));
                    BinaryExpression binaryFinalBody = Expression.Add(binaryExpression, constantExpression);
                    Expression<Func<int, int, int>> express = Expression.Lambda<Func<int, int, int>>(binaryFinalBody, new ParameterExpression[]
                    {
                        parameterExpression, 
                        parameterExpression2
                    });
                    int result = express.Compile().Invoke(2, 3);
                    Console.WriteLine("自己拼接的表达式目录树的结果为:{0}", result);
     } 
    复制代码

    运行结果:

     

    二. 实体间Copy赋值的几类处理方案

    背景: 在实际开发中,我们可能经常会遇到这种场景,两个实体的名称不同,属性完全相同,需要将一个实体的值赋值给另一个对应实体上的属性。

    解决这类问题通常有以下几种方案:

      1. 直接硬编码的形式:速度最快(0.126s)
      2. 通过反射遍历属性的形式 (6.328s)
      3. 利用序列化和反序列化的形式:将复制实体序列化字符串,在把该字符串反序列化被赋值实体(7.768s)
      4. 字典缓存+表达式目录树(Lambda的拼接代码了解即可) (0.663s)
      5. 泛型缓存+表达式目录树(Lambda的拼接代码了解即可) (2.134s)

     代码分享:

    复制代码
      1  public static class CopyUtils
      2     {
      3         //字典缓存
      4         private static Dictionary<string, object> _Dic = new Dictionary<string, object>();
      5 
      6         public static void Show()
      7         {
      8             //0. 准备实体 
      9             User user = new User()
     10             {
     11                 id = 1,
     12                 userName = "ypf",
     13                 userAge = 3
     14             };
     15             long time1 = 0;
     16             long time2 = 0;
     17             long time3 = 0;
     18             long time4 = 0;
     19             long time5 = 0;
     20 
     21             #region 1-直接硬编码的形式
     22             {
     23                 Task.Run(() =>
     24                 {
     25                     Stopwatch watch = new Stopwatch();
     26                     watch.Start();
     27                     for (int i = 0; i < 1000000; i++)
     28                     {
     29                         UserCopy userCopy = new UserCopy()
     30                         {
     31                             id = user.id,
     32                             userName = user.userName,
     33                             userAge = user.userAge,
     34                         };
     35                     }
     36                     watch.Stop();
     37                     time1 = watch.ElapsedMilliseconds;
     38                     Console.WriteLine("方案1所需要的时间为:{0}", time1);
     39                 });
     40 
     41             }
     42             #endregion
     43 
     44             #region 2-反射遍历属性
     45             {
     46                 Task.Run(() =>
     47                 {
     48                     Stopwatch watch = new Stopwatch();
     49                     watch.Start();
     50                     for (int i = 0; i < 1000000; i++)
     51                     {
     52                         CopyUtils.ReflectionMapper<User, UserCopy>(user);
     53                     }
     54                     watch.Stop();
     55                     time2 = watch.ElapsedMilliseconds;
     56                     Console.WriteLine("方案2所需要的时间为:{0}", time2);
     57                 });
     58             }
     59             #endregion
     60 
     61             #region 3-序列化和反序列化
     62             {
     63                 Task.Run(() =>
     64                 {
     65                     Stopwatch watch = new Stopwatch();
     66                     watch.Start();
     67                     for (int i = 0; i < 1000000; i++)
     68                     {
     69                         CopyUtils.SerialzerMapper<User, UserCopy>(user);
     70                     }
     71                     watch.Stop();
     72                     time3 = watch.ElapsedMilliseconds;
     73                     Console.WriteLine("方案3所需要的时间为:{0}", time3);
     74                 });
     75                 
     76             }
     77             #endregion
     78 
     79             #region 04-字典缓存+表达式目录树
     80             {
     81                 Task.Run(() =>
     82                 {
     83                     Stopwatch watch = new Stopwatch();
     84                     watch.Start();
     85                     for (int i = 0; i < 1000000; i++)
     86                     {
     87                         CopyUtils.DicExpressionMapper<User, UserCopy>(user);
     88                     }
     89                     watch.Stop();
     90                     time4 = watch.ElapsedMilliseconds;
     91                     Console.WriteLine("方案4所需要的时间为:{0}", time4);
     92                 });
     93             }
     94             #endregion
     95 
     96             #region 05-泛型缓存+表达式目录树
     97             {
     98                 Task.Run(() =>
     99                 {
    100                     Stopwatch watch = new Stopwatch();
    101                     watch.Start();
    102                     for (int i = 0; i < 1000000; i++)
    103                     {
    104                         GenericExpressionMapper<User, UserCopy>.Trans(user);
    105                     }
    106                     watch.Stop();
    107                     time5 = watch.ElapsedMilliseconds;
    108                     Console.WriteLine("方案5所需要的时间为:{0}", time5);
    109                 });
    110             }
    111             #endregion
    112      
    113         }
    复制代码

    上述代码涉及到的几个封装

    复制代码
     1  #region 封装-反射的方式进行实体间的赋值
     2         /// <summary>
     3         /// 反射的方式进行实体间的赋值
     4         /// </summary>
     5         /// <typeparam name="TIn">赋值的实体类型</typeparam>
     6         /// <typeparam name="TOut">被赋值的实体类型</typeparam>
     7         /// <param name="tIn"></param>
     8         public static TOut ReflectionMapper<TIn, TOut>(TIn tIn)
     9         {
    10             TOut tOut = Activator.CreateInstance<TOut>();
    11             //外层遍历获取【被赋值的实体类型】的属性
    12             foreach (var itemOut in tOut.GetType().GetProperties())
    13             {
    14                 //内层遍历获取【赋值的实体类型】的属性
    15                 foreach (var itemIn in tIn.GetType().GetProperties())
    16                 {
    17                     if (itemOut.Name.Equals(itemIn.Name))
    18                     {
    19                         //特别注意这里:SetValue和GetValue的用法
    20                         itemOut.SetValue(tOut, itemIn.GetValue(tIn));
    21                         break;
    22                     }
    23                 }
    24             }
    25             return tOut;
    26         }
    27         #endregion
    28 
    29         #region 封装-序列化反序列化进行实体见的赋值
    30         /// <summary>
    31         /// 序列化反序列化进行实体见的赋值
    32         /// </summary>
    33         /// <typeparam name="TIn">赋值的实体类型</typeparam>
    34         /// <typeparam name="TOut">被赋值的实体类型</typeparam>
    35         /// <param name="tIn"></param>
    36         public static TOut SerialzerMapper<TIn, TOut>(TIn tIn)
    37         {
    38             return JsonConvert.DeserializeObject<TOut>(JsonConvert.SerializeObject(tIn));
    39         }
    40         #endregion
    41 
    42         #region 封装-字典缓存+表达式目录树
    43         /// <summary>
    44         /// 序列化反序列化进行实体见的赋值
    45         /// </summary>
    46         /// <typeparam name="TIn">赋值的实体类型</typeparam>
    47         /// <typeparam name="TOut">被赋值的实体类型</typeparam>
    48         /// <param name="tIn"></param>
    49         public static TOut DicExpressionMapper<TIn, TOut>(TIn tIn)
    50         {
    51             string key = string.Format("funckey_{0}_{1}", typeof(TIn).FullName, typeof(TOut).FullName);
    52             if (!_Dic.ContainsKey(key))
    53             {
    54                 ParameterExpression parameterExpression = Expression.Parameter(typeof(TIn), "p");
    55                 List<MemberBinding> memberBindingList = new List<MemberBinding>();
    56                 foreach (var item in typeof(TOut).GetProperties())
    57                 {
    58                     MemberExpression property = Expression.Property(parameterExpression, typeof(TIn).GetProperty(item.Name));
    59                     MemberBinding memberBinding = Expression.Bind(item, property);
    60                     memberBindingList.Add(memberBinding);
    61                 }          
    62                 MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(typeof(TOut)), memberBindingList.ToArray());
    63                 Expression<Func<TIn, TOut>> lambda = Expression.Lambda<Func<TIn, TOut>>(memberInitExpression, new ParameterExpression[]
    64                 {
    65                     parameterExpression
    66                 });
    67                 Func<TIn, TOut> func = lambda.Compile();//拼装是一次性的
    68                 _Dic[key] = func;
    69             }
    70             return ((Func<TIn, TOut>)_Dic[key]).Invoke(tIn);
    71         }
    72         #endregion
    复制代码
     泛型缓存

    最终运行结果:

    三. 剥离表达式目录树

       这里补充几个常用的表达式目录树的拼接代码:And、Or、Not 。

     ExpressionExtend扩展类代码:

    复制代码
     1   public static class ExpressionExtend
     2     {
     3         /// <summary>
     4         /// 合并表达式 expr1 AND expr2
     5         /// </summary>
     6         /// <typeparam name="T"></typeparam>
     7         /// <param name="expr1"></param>
     8         /// <param name="expr2"></param>
     9         /// <returns></returns>
    10         public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
    11         {
    12             ParameterExpression newParameter = Expression.Parameter(typeof(T), "c");
    13             NewExpressionVisitor visitor = new NewExpressionVisitor(newParameter);
    14 
    15             var left = visitor.Replace(expr1.Body);
    16             var right = visitor.Replace(expr2.Body);
    17             var body = Expression.And(left, right);
    18             return Expression.Lambda<Func<T, bool>>(body, newParameter);
    19 
    20         }
    21         /// <summary>
    22         /// 合并表达式 expr1 or expr2
    23         /// </summary>
    24         /// <typeparam name="T"></typeparam>
    25         /// <param name="expr1"></param>
    26         /// <param name="expr2"></param>
    27         /// <returns></returns>
    28         public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
    29         {
    30 
    31             ParameterExpression newParameter = Expression.Parameter(typeof(T), "c");
    32             NewExpressionVisitor visitor = new NewExpressionVisitor(newParameter);
    33 
    34             var left = visitor.Replace(expr1.Body);
    35             var right = visitor.Replace(expr2.Body);
    36             var body = Expression.Or(left, right);
    37             return Expression.Lambda<Func<T, bool>>(body, newParameter);
    38         }
    39         public static Expression<Func<T, bool>> Not<T>(this Expression<Func<T, bool>> expr)
    40         {
    41             var candidateExpr = expr.Parameters[0];
    42             var body = Expression.Not(expr.Body);
    43 
    44             return Expression.Lambda<Func<T, bool>>(body, candidateExpr);
    45         }
    46     }
    复制代码

    NewExpressionVisitor建立新表达式辅助类代码:

    复制代码
     1   /// <summary>
     2     /// 建立新表达式
     3     /// </summary>
     4     internal class NewExpressionVisitor : ExpressionVisitor
     5     {
     6         public ParameterExpression _NewParameter { get; private set; }
     7         public NewExpressionVisitor(ParameterExpression param)
     8         {
     9             this._NewParameter = param;
    10         }
    11         public Expression Replace(Expression exp)
    12         {
    13             return this.Visit(exp);
    14         }
    15         protected override Expression VisitParameter(ParameterExpression node)
    16         {
    17             return this._NewParameter;
    18         }
    19     }
    复制代码

    如何使用的代码:

    复制代码
     1  public class VisitorUtils
     2     {
     3         public static void Show()
     4         {
     5             Expression<Func<User, bool>> lambda1 = x => x.userAge > 5;
     6             Expression<Func<User, bool>> lambda2 = x => x.id > 5;
     7             Expression<Func<User, bool>> lambda3 = lambda1.And(lambda2);
     8             Expression<Func<User, bool>> lambda4 = lambda1.Or(lambda2);
     9             Expression<Func<User, bool>> lambda5 = lambda1.Not();
    10 
    11             Do1(lambda1);
    12             Do1(lambda2);
    13             Do1(lambda3);
    14             Do1(lambda4);
    15             Do1(lambda5);
    16         }
    17 
    18         private static void Do1(Expression<Func<User, bool>> func)
    19         {
    20             List<User> user = new List<User>()
    21             {
    22                 new User(){id=4,userName="123",userAge=4},
    23                 new User(){id=5,userName="234",userAge=5},
    24                 new User(){id=6,userName="345",userAge=6},
    25             };
    26 
    27             List<User> peopleList = user.Where(func.Compile()).ToList();
    28         }
    29     }

    https://www.cnblogs.com/yaopengfei/p/7486870.html
  • 相关阅读:
    SP笔记:交叉实现七行并成一行
    HTML tag 学习
    操作哈希表
    Efficient bipedal robots based on passivedynamic walkers
    Pushing People Around
    ZEROMOMENT PONTTHIRTY FIVE YEARS OF ITS LIFE

    Active Learning for RealTime Motion Controllers
    Accelerometerbased User Interfaces for the Control of a Physically Simulated Character
    Dynamic Response for Motion Capture Animation
  • 原文地址:https://www.cnblogs.com/sjqq/p/9461314.html
Copyright © 2011-2022 走看看