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

官网的解释:

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

你可以对表达式树中的代码进行编辑和运算。 这样能够动态修改可执行代码、在不同数据库中执行 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<TDelegate>:强类型的表达式树类

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

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

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

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

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

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

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

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

        //错误示例:使用具有方法体的Lambda创建表达式
static void Expression2()
{
//编译不通过,不支持有方法体的Lambda创建表达式
//Expression<Action<int>> expression1 = a => { };
//编译不通过,有方法体的Lambda创建表达式
//Expression<Func<int, int>> 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<Action>(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<Action<string>>(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<Func<string, string>>(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<Func<string, string>>(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<Func<string, string>>(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<Func<string, string,string>>(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参数都是用来标记是否需要尾调优化的,

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

表达式树动态复制对象:

    /// <summary>
/// 当前类
/// </summary>
public class Student
{
public int Id { get; set; }
public string UserName { get; set; }
public string Name { get; set; }
} /// <summary>
/// 目标类
/// </summary>
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, StudentCopy>(student);
stopwatch.Stop();
Console.WriteLine($"reflect:{stopwatch.ElapsedTicks}"); stopwatch = new Stopwatch();
stopwatch.Start();
var entity3 = CopyToByReflect2<Student, StudentCopy>(student);
stopwatch.Stop();
Console.WriteLine($"reflect:{stopwatch.ElapsedTicks}"); //使用json
stopwatch = new Stopwatch();
stopwatch.Start();
var entity4 = CopyToByJson<Student, StudentCopy>(student);
stopwatch.Stop();
Console.WriteLine($"json:{stopwatch.ElapsedTicks}"); //使用表达式构造委托
stopwatch = new Stopwatch();
stopwatch.Start();
var entity5 = CopyToByExpression<Student, StudentCopy>(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, Tout>(Tin dto)
{
Tout entity = Activator.CreateInstance<Tout>();
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, Tout>(Tin dto)
{
Tout entity = Activator.CreateInstance<Tout>();
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, Tout>(TIn dto)
{
return JsonConvert.DeserializeObject<Tout>(JsonConvert.SerializeObject(dto));
} static Tout CopyToByExpression<Tin, Tout>(Tin dto)
{
ParameterExpression parameterExpression = Expression.Parameter(typeof(Tin), "a");
List<MemberBinding> memberBindings = new List<MemberBinding>();
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<Func<Tin, Tout>> lambda = Expression.Lambda<Func<Tin, Tout>>(memberInitExpression,
new ParameterExpression[] { parameterExpression });
Func<Tin, Tout> 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表达式树的梳理的更多相关文章

  1. ASP.NET Core中使用表达式树创建URL

    当我们在ASP.NET Core中生成一个action的url会这样写: var url=_urlHelper.Action("Index", "Home"); ...

  2. Asp.net Core C#进行筛选、过滤、使用PredicateBuilder进行动态拼接lamdba表达式树并用作条件精准查询,模糊查询

    在asp.net core.asp.net 中做where条件过滤筛选的时候写的长而繁琐不利于维护,用PredicateBuilder进行筛选.过滤.LInq配合Ef.core进行动态拼接lamdba ...

  3. asp.net core 排序过滤分页组件:sieve(2)表达式树的复习

    在Sieve组件中使用了很多关于表达式树的知识,但在我们日常的工作中写表达式树的机会是非常少的,至少在我的编程生涯中没怎么写过表达式树(可能也就是3,4次).所以,为了能够看懂Sieve里面的源代码, ...

  4. 不可不知的表达式树(1)Expression初探

    说起Lambda表达式,大家基本都很熟悉了,而表达式树(Expression Trees),则属于80%的工作中往往都用不到的那种技术,所以即便不是什么新技术,很多人对其理解都并不透彻.此文意图从表达 ...

  5. 程序猿修仙之路--数据结构之你是否真的懂数组? c#socket TCP同步网络通信 用lambda表达式树替代反射 ASP.NET MVC如何做一个简单的非法登录拦截

    程序猿修仙之路--数据结构之你是否真的懂数组?   数据结构 但凡IT江湖侠士,算法与数据结构为必修之课.早有前辈已经明确指出:程序=算法+数据结构  .要想在之后的江湖历练中通关,数据结构必不可少. ...

  6. 用lambda表达式树替代反射

    本节重点不讲反射机制,而是讲lambda表达式树来替代反射中常用的获取属性和方法,来达到相同的效果但却比反射高效. 每个人都知道,用反射调用一个方法或者对属性执行SetValue和GetValue操作 ...

  7. 利用表达式树Expression优化反射性能

    最近做了一个.Net Core环境下,基于NPOI的Excel导入导出以及Word操作的服务封装,涉及到大量反射操作,在性能优化过程中使用到了表达式树,记录一下. Excel导入是相对比较麻烦的一块, ...

  8. C#3.0新增功能10 表达式树 03 支持表达式树的框架类型

    连载目录    [已更新最新开发文章,点击查看详细] 存在可与表达式树配合使用的 .NET Core framework 中的类的大型列表. 可以在 System.Linq.Expressions 查 ...

  9. C#3.0新增功能10 表达式树 04 执行表达式

    连载目录    [已更新最新开发文章,点击查看详细] 表达式树 是表示一些代码的数据结构. 它不是已编译且可执行的代码. 如果想要执行由表达式树表示的 .NET 代码,则必须将其转换为可执行的 IL ...

随机推荐

  1. C#LeetCode刷题之#349-两个数组的交集(Intersection of Two Arrays)

    问题 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/4042 访问. 给定两个数组,编写一个函数来计算它们的交集. 输入 ...

  2. Spring——IOC(控制反转)与DI(依赖注入)

    IOC与DI的理解及使用 控制反转IOC(Inversion of Control)是一种设计思想,DI(依赖注入)是实现IOC的一种方法.在没有IOC的程序中,我们使用面向对象编程,对象的创建于对象 ...

  3. 三分钟秒懂BIO/NIO/AIO区别?

    首先来举个例子说明吧,假设你想吃一份盖饭: 同步阻塞:你到饭馆点餐,然后在那等着,还要一边喊:好了没啊! 同步非阻塞:在饭馆点完餐,就去遛狗了.不过溜一会儿,就回饭馆喊一声:好了没啊! 异步阻塞:遛狗 ...

  4. css中关于:nth-child()和:nth-of-type()的深入理解

    css中关于:nth-child()和:nth-of-type()的深入理解 在css中有这样一类属性,是以:nth-开头的,其中最常见的就是:nth-child() 和 :nth-of-type() ...

  5. 简述python中`functools.wrapper()

    简述python中functools.wrapper() 首先对于最简单的函数: def a(): pass if __name__ == '__main__': print(a.__name__) ...

  6. MySQL元数据信息

    一.查看数据库清单 show databases; 二.查看数据库下表清单 方法1) show full tables from ${schema名}; 方法2) use ${数据库名}; show ...

  7. unity探索者之微信分享所有流程,非第三方插件

    版权声明:本文为原创文章,转载请声明http://www.cnblogs.com/unityExplorer/p/7560575.html 很久没有写新博客了,前段时间有些忙,这几天趟了几个微信分享的 ...

  8. Java BigDecimal使用指南

    提起BigDecimal,相信大家都使用过,之所以总结这篇呢,是因为最近发现项目中使用的不是太规范,在某些场景下甚至出现代码抛出异常的情况, 所以就总结了这篇,希望大家在使用时,可以少踩一些坑. 1. ...

  9. seo如何发外链

    http://www.wocaoseo.com/thread-228-1-1.html 在做外链方面博主并没有什么太多的经验,做为一位seo,下面武汉seo把自己做外链的大条列出来,都是经过本身实践并 ...

  10. .NET Core3.1 Dotnetty实战第一章

    一.概要 本系列文章主要讲述由微软Azure团队研发的.net的版本的netty,Dotnetty.所有的开发都将基于.net core 3.1版本进行开发. Dotnetty是什么,原本Netty是 ...