.NET Core表达式树的梳理

2021-01-22 23:13

阅读:357

标签:copyto   ons   自己   技术   pwa   部分   单行   reac   highlight   

最近要重写公司自己开发的ORM框架;其中有一部分就是查询的动态表达式;于是对这方面的东西做了一个简单的梳理

官网的解释:

表达式树以树形数据结构表示代码,其中每一个节点都是一种表达式,比如方法调用和 x  这样的二元运算等。

你可以对表达式树中的代码进行编辑和运算。 这样能够动态修改可执行代码、在不同数据库中执行 LINQ 查询以及创建动态查询。

表达式树还能用于动态语言运行时 (DLR) 以提供动态语言和 .NET 之间的互操作性,同时保证编译器编写员能够发射表达式树而非 Microsoft 中间语言 (MSIL)

 

总结:构造可执行的代码,以树形结构的方式构造;

表达式与委托:委托可以直接执行,而表达式不可以,表达式可以编译成委托

备注:实际上.NET还有以文本的方式构造可执行代码块的方式,但和表达式树还是有区别

 

我个人工作中主要在以下场景中使用表达式树:

1,代理类方法的构造(如AOP,gRpc,WebService中),通过表达式树,构造委托执行要代理的方法

2,EF,MongoDB,自定义ORM等数据访问层过滤使用的Expression,最典型的就是针对IQueryable类型分页,排序的动态扩展

3,常用的Linq中AND,OR等Predicate加强的一些扩展

4,数学表达式,伪SQL代码的执行解析器的构造(这个比较底层,复杂,更多的是纯数学,数据结构,堆栈,后缀表达式,解析器模式等一些东西)

 

Expression调试工具:2019下载地址ExpressionTreeVisualizer

Expression调试工具源码:https://github.com/zspitz/ExpressionTreeVisualizer/blob/master/README.md

Expression类有以下属性:

Body:表达式的主体(类似于方法体)

Parameters:Lambda表达示的参数(参数列表)

NodeType:得到树中某些节点的表达式类型(ExpressionType),这是一个有45种不同值的枚举类型,代表表达式节点的所有可能类型,如返回常数、
也可能返回参数、或者返回一个值是否小于另外一个(),或者返回两个值的和( )等等。

Type:得到表达式的静态类型

 

表达式树的几个比较常用的类型/方法,.NET FrameWork与.NET Core在表达式树的使用上做了一些调整

Expression:表达式树核心类,基类;提供了Call,Add等构建Expression的静态方法

Expression.Call():非常核心的一个方法,用于创建调用类的方法的方法类型表达式树;在.NET Core(易用)和.NET FrameWork(易懂)做了些变化

Expression.Parameter():用于创建参数类型表达式树

Expression.Constant():用于创建常量类型表达式树

Expression:强类型的表达式树类

Expression.Lambda:将Expression转换成Lambda表达式(设置Lambda的方法体和参数列表)

LambdaExpression.Parameters:做表达式树拼接时常用到

LambdaExpression.tailCall:属性,标记是否【尾调优化,参考后面

 

创建表达式树的方式(以下示例为.Net Core3.1)

1,以单行Lambda表达式创建表达式树( 包含方法体的Lambda表达式是不能创建表达式树 );

这种表达式树的构造方式,Lambda表达式实际上是给LambdaExpression的Body(Body为Expression类型)赋值

        //使用单行Lambda创建表达式
        static void Expression1()
        {
            //表达式树的Body
            Expressionint, int>> expression = a => a + 100;
            Expressionint, int, int>> expression2 = (a, b) => a + b;
            Console.WriteLine(expression.ToString());
            Console.WriteLine(expression.Compile()(120));
        }

如果你使用具有方法体的Lambda表达式创建表达式树,是不成功的;表达式树是Lambda表达式的内存中表现形式

        //错误示例:使用具有方法体的Lambda创建表达式
        static void Expression2()
        {
            //编译不通过,不支持有方法体的Lambda创建表达式
            //Expression> expression1 = a => { };
            //编译不通过,有方法体的Lambda创建表达式
            //Expression> expression2 = a => { return a + 100; };
            //Console.WriteLine(expression1.ToString());
        }

 

2,通过API创建表达式树

    创建没有入参和返回参数的表达式树

        //无入参,返参的表达式
        static void Expression3()
        {
            var WriteLine = typeof(Console).GetMethod("WriteLine", new[] { typeof(string) });
            //表达式树的Body
            var method = Expression.Block(Expression.Call(null, WriteLine, Expression.Constant("来自Expression的输出")));
            var action = Expression.Lambda(method).Compile();
            action();
        }

  创建有入参,没有返回参数的表达式树

        //有入参,无返参的表达式
        static void Expression4()
        {
            var stringParam = Expression.Parameter(typeof(string), "stringParam");
            var WriteLine = typeof(Console).GetMethod("WriteLine", new[] { typeof(string) });
            var method = Expression.Block(Expression.Call(null, WriteLine, new[] { stringParam }));
            var action = Expression.Lambda>(method, new[] { stringParam }).Compile();
            action("来自Expression4的输出yyyyyy");
        }

  创建有入参,有返回参数的表达式树  

        //有入参,有返参的表达式1
        static void Expression5()
        {
            var stringParam = Expression.Parameter(typeof(string), "stringParam");
            var WriteLine = typeof(Console).GetMethod("WriteLine", new[] { typeof(string) });
            var method = Expression.Block(
                Expression.Call(null, WriteLine, new[] { stringParam }),
                Expression.Assign(stringParam, Expression.Constant("你好啊"))
                );
            var action = Expression.Lambda>(method, new[] { stringParam }).Compile();
            var ms = action("Expression5ss");
        }

        //有入参,有返参的表达式2
        static void Expression6()
        {
            var stringParam = Expression.Parameter(typeof(string), "stringParam");
            var method = Expression.Block(Expression.Call(null,typeof(string).GetMethod("Concat", new[] { typeof(string), typeof(string) }), new Expression[] { stringParam, Expression.Constant("哈哈哈哈") }));
            var action = Expression.Lambda>(method, new[] { stringParam }).Compile();
            var ms = action("test单个参数");
            Console.WriteLine(ms);
        }

        //有入参,有返参的表达式3
        static void Expression7()
        {
            var stringParam = Expression.Parameter(typeof(string), "stringParam");
            var stringParam2 = Expression.Parameter(typeof(string), "stringParam2");
            var method = Expression.Block(
                new[] { stringParam2 },
                Expression.Assign(stringParam2, Expression.Constant("参数2")),
                Expression.Call(null, typeof(string).GetMethod("Concat", new[] { typeof(string), typeof(string) }), new Expression[] { stringParam, stringParam2 })
                );
            var action = Expression.Lambda>(method, new[] { stringParam }).Compile();
            var ms = action("test单个参数,且Call声明参数");
            Console.WriteLine(ms);
        }

        //有入参,有返参的表达式4
        static void Expression8()
        {
            var stringParam = Expression.Parameter(typeof(string), "stringParam");
            var stringParam2 = Expression.Parameter(typeof(string), "stringParam2");
            var method = Expression.Block(
                Expression.Call(null, typeof(string).GetMethod("Concat", new[] { typeof(string), typeof(string) }), new Expression[] { stringParam, stringParam2 })
                );
            var action = Expression.Lambda>(method, new[] { stringParam, stringParam2 }).Compile();
            var ms = action("多个参数:参数1","啦啦啦啦");
            Console.WriteLine(ms);
        }

 

函数尾部调用参数优化:简称尾调优化

1,当函数的最后一步时调用其他函数时,即为尾调;尾调不只是说函数的最尾部;示例,Update,Add方法都是尾部调用

public int AddUser(string id,string name) {
    if (id==null) {
       return Update(name);
    }
    return Add(name);
}

  

2,LambdaExpression.tailCall属性和Expresssion.Lambda方法中的tailCall参数都是用来标记是否需要尾调优化的,

  如果标记尾调优化,则表示当执行进入尾调的函数时,当前函数已经不再依赖父函数上的数据,父函数上的所占用的内存空间将被清空,用于优化节省内存

 

表达式树动态复制对象:

    /// 
    /// 当前类
    /// 
    public class Student
    {
        public int Id { get; set; }
        public string UserName { get; set; }
        public string Name { get; set; }
    }

    /// 
    /// 目标类
    /// 
    public class StudentCopy
    {
        public int Id { get; set; }
        public string UserName { get; set; }
        public string Name { get; set; }
    }

 

    class Program
    {
        static void Main(string[] args)
        {
            Student student = new Student()
            {
                Id = 1002,
                Name = "哈哈哈",
                UserName = "cc"
            };

            //使用new
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();
            var entity1 = CopyToByNew(student);
            stopwatch.Stop();
            Console.WriteLine($"new:{stopwatch.ElapsedTicks}");

            //使用反射
            stopwatch = new Stopwatch();
            stopwatch.Start();
            var entity2 = CopyToByReflect1(student);
            stopwatch.Stop();
            Console.WriteLine($"reflect:{stopwatch.ElapsedTicks}");

            stopwatch = new Stopwatch();
            stopwatch.Start();
            var entity3 = CopyToByReflect2(student);
            stopwatch.Stop();
            Console.WriteLine($"reflect:{stopwatch.ElapsedTicks}");

            //使用json
            stopwatch = new Stopwatch();
            stopwatch.Start();
            var entity4 = CopyToByJson(student);
            stopwatch.Stop();
            Console.WriteLine($"json:{stopwatch.ElapsedTicks}");

            //使用表达式构造委托
            stopwatch = new Stopwatch();
            stopwatch.Start();
            var entity5 = CopyToByExpression(student);
            stopwatch.Stop();
            Console.WriteLine($"expression:{stopwatch.ElapsedTicks}");

            Console.WriteLine("Hello World!");
            Console.ReadKey();
        }

        static StudentCopy CopyToByNew(Student student)
        {
            return new StudentCopy
            {
                Id = student.Id,
                Name = student.Name,
                UserName = student.UserName
            };
        }

        static Tout CopyToByReflect1(Tin dto)
        {
            Tout entity = Activator.CreateInstance();
            foreach (var itemOut in entity.GetType().GetProperties())
            {
                var propIn = dto.GetType().GetProperty(itemOut.Name);
                if (propIn != null)
                {
                    itemOut.SetValue(entity, propIn.GetValue(dto));
                }
            }
            return entity;
        }

        static Tout CopyToByReflect2(Tin dto)
        {
            Tout entity = Activator.CreateInstance();
            foreach (var itemOut in entity.GetType().GetFields())
            {
                var propIn = dto.GetType().GetField(itemOut.Name);
                itemOut.SetValue(entity, propIn.GetValue(dto));
            }
            return entity;
        }

        static Tout CopyToByJson(TIn dto)
        {
            return JsonConvert.DeserializeObject(JsonConvert.SerializeObject(dto));
        }

        static Tout CopyToByExpression(Tin dto)
        {
            ParameterExpression parameterExpression = Expression.Parameter(typeof(Tin), "a");
            List memberBindings = new List();
            foreach (var item in typeof(Tout).GetProperties())
            {
                MemberExpression property = Expression.Property(parameterExpression, typeof(Tin).GetProperty(item.Name));
                MemberBinding memberBinding = Expression.Bind(item, property);
                memberBindings.Add(memberBinding);
            }
            MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(typeof(Tout)), memberBindings.ToArray());
            Expression> lambda = Expression.Lambda>(memberInitExpression,
                new ParameterExpression[] { parameterExpression });
            Func func = lambda.Compile();
            return func(dto);
        }
    }

技术图片  

 

技术图片

 

使用ExpressionTreeVisualizer工具

下载地址:参考前面的地址

安装方式:https://docs.microsoft.com/en-us/visualstudio/debugger/how-to-install-a-visualizer?view=vs-2019#to-install-a-visualizer-for-visual-studio-2019

ExpressionTreeVisualizer工具主要有三个功能

  1. 表达式树结构的可视化
  2. 表达式树源码显示
  3. 表达式树节点结构: parameters, closure variables, constants and default values

技术图片

.NET Core表达式树的梳理

标签:copyto   ons   自己   技术   pwa   部分   单行   reac   highlight   

原文地址:https://www.cnblogs.com/cmliu/p/13246185.html


评论


亲,登录后才可以留言!