• 第十五节: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
  • 相关阅读:
    SQL 查询中 not in的改进,--not exists
    REST接口--转摘
    C#中@的用法总结(转)
    有感于哈工大matlab被限制使用
    Oracle CURRVAL应用限制
    oracle to_char()函数--数字型到字符型
    如何提交代码到git仓库
    cannot find module 'xxx' 解决办法
    DOM-基本概念及使用
    AJAX-同源策略 跨域访问
  • 原文地址:https://www.cnblogs.com/sjqq/p/9461314.html
Copyright © 2020-2023  润新知