前言

前一篇讲完了反射, 这一篇来讲一下和反射息息相关的表达式树.

首先搞清楚 Delegate, Action, Func, Anonymous Method, Lambda, Expression tree

看大神的文章: C#中的Lambda表达式和表达式树

简单说, Delegate 委托是上古年代的东西,

后来有了泛型, 就演化成 Action 和 Func

然后开始玩匿名函数就有了 Lambda 表达式

然后又出来了一个表达式树.

Lambda 其实就是 Delegate, Action, Func 一样的东西.

而表达式树, 则是一些方法调用的表示手法.

表达式树由很多表达式组成, 经过 compile 可以变成 Lambda, 然后拿去执行.

也可以 parse 这个表达式树, 翻译成其它的东西.

比如 EF Core,

var person = await DbContext.People.Where(p => p.Name == "Derrick").ToListAsync();

上面这一句它可以翻译成 SQL

SELECT * FROM People WHERE Name = 'Derrick';

这篇不会讲到如何做翻译, 只会讲到如何动态创建表达式树.

Dynamic Expression How It Work

模拟一下动态创建 Expression for call EF Core's SingleAsync 方法.

public class Person {
public string Name { get; set; } = "";
}
public class Program
{
public static void Main()
{
var people = new List<Person> {
new Person { Name = "n1" },
new Person { Name = "n2" },
new Person { Name = "n3" },
new Person { Name = "n4" },
};
var person = people.AsQueryable().Single(p => p.Name == "n1");
// p => p.Name == "n1"
var pExp = Expression.Parameter(typeof(Person), "p"); // p
var pDotNameExp = Expression.Property(pExp, "Name"); // p.Name
var valueExp = Expression.Constant("n1"); // "n1"
var pDotNameEqualValueExp = Expression.Equal(pDotName, value); // p.Name == "n1"
var lambda = (Expression<Func<Person, bool>>)Expression.Lambda(pDotNameEqualValueExp, new ParameterExpression[] { pExp }); // p => p.Name == "n1"
var lambdaDelegate = lambda.Compile(); // 把表达式树 compile 成可执行的委托
var person2 = people.AsQueryable().Single(lambda); // queryable 情况下参数是表达式, 因为要翻译成 SQL
var person3 = people.Single(lambdaDelegate); // LINQ 的情况下参数是委托, 因为它是直接执行的
}
}

需要动态创建的表达式是这一句 p => p.Name == "n1"

从上面可以看到, 它是逐个逐个通过 Expression 创建和拼接出来的. 看注释一句一句理解.

你会发现它的语法有一种 left right 的感觉, 然后拼着拼着就有点像棵树了, 二叉树

通常, 最后会把 Expression 变成 lambda 作为方法的参数, 需不需要 compile 就看最终拿来干什么. 如果是 Queryable 就直接给表达式树它翻译, 如果是 LINQ 就 compile 成委托执行.

多一个例子

var people = new List<Person> {
new Person { Age = 1 },
new Person { Age = 2 },
new Person { Age = 3 },
new Person { Age = 4 },
};
var person = people.AsQueryable().Where(p => p.Age >= 1 && p.Age <= 3);
// p => p.Age >= 1 && p.Age <= 3
var pExp = Expression.Parameter(typeof(Person), "p"); // p
var pDotAgeExp = Expression.Property(pExp, "Age"); // p.Age
var value1Exp = Expression.Constant(1); // 1
var pDotAgeGreatThanOrEqualvalue1Exp = Expression.GreaterThanOrEqual(pDotAgeExp, value1Exp); // p.Age >= 1
var value3Exp = Expression.Constant(3); // 3
var pDotAgeLessThanOrEqualvalue3Exp = Expression.LessThanOrEqual(pDotAgeExp, value3Exp); // p.Age <= 1
var and = Expression.And(pDotAgeGreatThanOrEqualvalue1Exp, pDotAgeLessThanOrEqualvalue3Exp); // p.Age >= 1 && p.Age <= 3
var lambda = (Expression<Func<Person, bool>>)Expression.Lambda(and, pExp); // p => p.Age >= 1 && p.Age <= 3
var lambdaDelegate = lambda.Compile();
var result = people.Where(lambdaDelegate).ToList(); // [1,2,3]

不管表达式如何复杂, 它就是逐个逐个去拼接, 所以不要害怕. Expression 里面有非常非常都多方法, 几乎你能写出来的 code 都能找到对应的 Expression

执行 lambdaDelegate

上面例子中, 我们都是把 Compile() 后的 lambdaDelegate pass 给 Single 和 Where 方法执行.

这里给一个自己执行的例子.

在第一篇 Anonymous method / Delegate, 我给过三种调用 delegate 的方式.

lambdaDelegate 也是委托, 按理说应该完全适用

第一种方式是类型清楚的, 这个没有问题

// width => width > 100;
var widthParameterExp = Expression.Parameter(typeof(int), "width");
var greaterThan100Exp = Expression.GreaterThan(widthParameterExp, Expression.Constant(100));
var lambdaDelegate = Expression.Lambda<Func<int, bool>>(greaterThan100Exp, widthParameterExp).Compile();
var isGreaterThan100 = lambdaDelegate(150); // True

第二种是类型不清楚的

var lambdaDelegate = Expression.Lambda(greaterThan100Exp, widthParameterExp).Compile();
var isGreaterThan100 = (bool)lambdaDelegate.Method.Invoke(lambdaDelegate.Target, new object[] { 150 })!;

结果报错了 : MethodInfo must be a runtime MethodInfo

第三种 DynamicInvoke 则没有问题

var isGreaterThan100 = (bool)lambdaDelegate.DynamicInvoke(new object[] { 150 })!;

注意: DynamicInvoke 是很慢的, 虽然是搞反射, 但如果能知道部分类型, 比如第一种方式, 那就尽量用第一种吧.

Expression.Call

表达式中也可以表达方法调用哦. 当然这个对翻译 SQL 的话可能会有点问题啦. 但是 for 执行的话就很好用哦.

public class Person {
public int Age { get; set; }
public bool IsDeleted(int value)
{
return true;
}
}
public class Program
{
public static void Main()
{
var people = new List<Person> {
new Person { Age = 1 },
new Person { Age = 2 },
new Person { Age = 3 },
new Person { Age = 4 },
};
// p => p.IsDeleted(5)
var pExp = Expression.Parameter(typeof(Person), "p"); // p
var method = typeof(Person).GetMethod("IsDeleted")!; // 通过反射获取 MethodInfo
var callMethodExp = Expression.Call(pExp, method, new Expression[] { Expression.Constant(5) }); // p.IsDeleted(5)
var lambda = (Expression<Func<Person, bool>>)Expression.Lambda(callMethodExp, pExp); // p => p.IsDeleted(5)
var lambdaDelegate = lambda.Compile();
var result = people.Where(lambdaDelegate).ToList(); // [1,2,3,4]
}
}

总结

动态创建表达式树, 可以帮助我们更好的管理 EF.Core 的代码. OData 也是通过这个方式去写入 EF Core 的 OData -> EF Core -> SQL.

以前我不喜欢写反射, 表达式树, 但后来我发现, 只要把小方法写好, 一点一点拼接上来, 它只是代码多, 但是并不会乱. 所以不要怕.

随便提一嘴,表达式树一直没有更新,许多 C# 新语法都不支持,相关 Github Issue – Extend expression trees to cover more/all language constructs

ASP.NET Core C# 反射 & 表达式树 (第三篇)的更多相关文章

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

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

  2. 使用Asp.Net Core MVC 开发项目实践[第三篇:基于EF Core的扩展]

    上篇我们说到了EFCore的基础使用,这篇我们将讲解下基于EFCore的扩展. 我们在Mango.Framework.EFCore类库项目中创建一个类名EFExtended的扩展类,并且引入相关的命名 ...

  3. ASP.NET Core Web开发学习笔记-1介绍篇

    ASP.NET Core Web开发学习笔记-1介绍篇 给大家说声报歉,从2012年个人情感破裂的那一天,本人的51CTO,CnBlogs,Csdn,QQ,Weboo就再也没有更新过.踏实的生活(曾辞 ...

  4. 从零开始实现ASP.NET Core MVC的插件式开发(三) - 如何在运行时启用组件

    标题:从零开始实现ASP.NET Core MVC的插件式开发(三) - 如何在运行时启用组件 作者:Lamond Lu 地址:https://www.cnblogs.com/lwqlun/p/112 ...

  5. C#动态构建表达式树(三)——表达式的组合

    C#动态构建表达式树(三)--表达式的组合 前言 在筛选数据的过程中,可能会有这样的情况:有一些查询条件是公共的,但是根据具体的传入参数可能需要再额外增加一个条件.对于这种问题一般有两种方法: a. ...

  6. ASP.NET自定义控件组件开发 第一章 第三篇

    原文:ASP.NET自定义控件组件开发 第一章 第三篇 第三篇:第一章的完结篇 系列文章链接: ASP.NET自定义控件组件开发 第一章 待续 ASP.NET自定义控件组件开发 第一章 第二篇 接着待 ...

  7. ASP.NET自定义控件组件开发 第一章 第三篇 第一章的完结篇

    ASP.NET自定义控件组件开发 第一章 第三篇   第三篇:第一章的完结篇 系列文章链接: ASP.NET自定义控件组件开发 第一章 待续 ASP.NET自定义控件组件开发 第一章 第二篇 接着待续 ...

  8. C# 反射 表达式树 模糊搜索

    反射实体T,非datetime字段反射获取表达式树   public static Expression<Func<T, bool>> GetSearchExpression& ...

  9. ASP.NET Core 中的依赖注入 [共7篇]

    一.控制反转(IoC) ASP.NET Core在启动以及后续针对每个请求的处理过程中的各个环节都需要相应的组件提供相应的服务,为了方便对这些组件进行定制,ASP.NET通过定义接口的方式对它们进行了 ...

  10. ASP.NET Core 快速入门【第二弹-实战篇】

    上篇讲了asp.net core在linux上的环境部署.今天我们将做几个小玩意实战一下.用到的技术和工具有mysql.websocket.AngleSharp(爬虫html解析).nginx多站点部 ...

随机推荐

  1. linux系统&自动清理日志实现脚本

    文章来源:https://blog.csdn.net/lakelise/article/details/93711932 编写清理脚本,添加到定时任务中:创建可执行文件cd /hometouch cl ...

  2. ScaleDet:AWS 基于标签相似性提出可扩展的多数据集目标检测器 | CVPR 2023

    论文提出了一种可扩展的多数据集目标检测器(ScaleDet),可通过增加训练数据集来扩大其跨数据集的泛化能力.与现有的主要依靠手动重新标记或复杂的优化来统一跨数据集标签的多数据集学习器不同,论文引入简 ...

  3. ffmpeg一些笔记: 代码调试数据

    1.AAC,MP3他的解码数据格式不支持,程序中给的是这个AV_SAMPLE_FMT_FLTP,  Screen-Cpature-Recoder的codec-id为AV_CODEC_RAW_VIDEO ...

  4. gitbook 入门教程之比较代码块差异 diff 插件

    在 markdown 文档中显示代码之间的差异的 Gitbook 插件 English | 中文 主页 Github : https://snowdreams1006.github.io/gitboo ...

  5. 我用Awesome-Graphs看论文:解读Naiad

    Naiad论文:<Naiad: A Timely Dataflow System> 前面通过文章<论文图谱当如是:Awesome-Graphs用200篇图系统论文打个样>向大家 ...

  6. 【Java】 Void 类型

    void 也算一个类型,而且是基本数据类型 和其它数据类型一样提供了对应的包装类Void 每个包装类都提供一个TYPE字节实例,返回对应的原型类实例 public static void main(S ...

  7. 【RabbitMQ】11 深入部分P4 延迟队列

    一.延迟队列: 消息经过交换机分配到队列上之后,在到达指定的时间,才会被消费? 需求: 1.下单之后的30分钟,用户未支付,订单取消,回滚库存 2.新用户注册7天后,发送短信慰问,或者是用户生日发送短 ...

  8. nvidia 机器人仿真环境Isaac Sim

  9. 树莓派 3b+型号 pip3方式 安装 TensorFlow

    树莓派系统为: 首先选择 pip3  方式进行安装: 树莓派上执行: 发现速度过慢,于是选择先在Windows主机上下载,然后再把文件传到树莓派上进行安装. 不过后来发现即使使用迅雷这样强大的下载工具 ...

  10. 强化学习中经典算法 —— reinforce算法 —— (进一步理解, 理论推导出的计算模型和实际应用中的计算模型的区别)

    在奖励折扣率为1的情况下,既没有折扣的情况下,reinforce算法理论上可以写为: 但是在有折扣的情况下,reinforce算法理论上可以写为: 以上均为理论模型. ================ ...