前言

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

首先搞清楚 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. git 怎么将某个开发分支最近几次的提交合并成一次提交

    1. 切换到开发分支: git checkout dev 2. 运行交互式 rebase 命令,并指定要合并的提交数量(在这个例子中是最近的3次提交): git rebase -i HEAD~3 3. ...

  2. 基于微信小程序+Springboot校园二手商城系统设计和实现

    \n文末获取源码联系 感兴趣的可以先收藏起来,大家在毕设选题,项目以及论文编写等相关问题都可以给我加好友咨询 一. 前言介绍: 在当今社会的高速发展过程中,产生的劳动力越来越大,提高人们的生活水平和质 ...

  3. 新做了一个MySQL 数据库 DDL 差异对比的网站

    MySQL 数据库 DDL 差异对比的网站 摘要 新做了个网站,用来对比不同环境下的 DDL 差异,生成变更点和 迁移 DDL 网站地址:https://ddlcompare.com/ 对比过程中如果 ...

  4. Java 监听POST请求

    要监听POST请求,我们可以使用Java中的HttpServlet类.以下是一个使用Servlet API监听POST请求的完整示例.这个示例使用了Servlet 3.1规范,不需要在web.xml中 ...

  5. python 返回实例对象个数

    python 返回实例对象个数 Python 没有提供任何内部机制来跟踪一个类有多少个实例被创建了,或者记录这些实例是些什么东西.如果需要这些功能,你可以显式加入一些代码到类定义或者__init__( ...

  6. chromedriver.exe存放位置

    chromedriver.exe存放位置 如果chromedriver.exe存放位置不对的话,driver=webdriver.Chrome() 会报错! 一般需要存放在python下面的Scrip ...

  7. [rCore学习笔记 019]在main中测试本章实现

    写在前面 本随笔是非常菜的菜鸡写的.如有问题请及时提出. 可以联系:1160712160@qq.com GitHhub:https://github.com/WindDevil (目前啥也没有 批处理 ...

  8. 【Eclipse】入门使用

    Eclipse界面简单概述 第一次启动时,工作空间的选择 工作界面的介绍: 选项条 工具栏 工程浏览窗口 工程大纲窗口 控制台输出窗口 在窗口选项中悬浮放在Show View选项中可以查看所有的窗口 ...

  9. Jax框架:通过显存分析判断操作是否进行jit编译

    相关: https://jax.readthedocs.io/en/latest/device_memory_profiling.html 代码: import jax import jax.nump ...

  10. Trump 黑马 or 搅局者? 讲座视频分享

    沈逸-特朗普能走多远   https://www.bilibili.com/video/BV1r7411t7VS/?spm_id_from=333.788.videocard.2     国际关系 对 ...