前言

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

首先搞清楚 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. 在宝塔上配置打包好的vue3项目

    配置文件如下 server{ listen 80; server_name gongchang.365cb.cn; index index.html index.htm default.php def ...

  2. 第七节 JMeter基础-高级登录【数据驱动-参数化】

    声明:本文所记录的仅本次操作学习到的知识点,其中商城IP错误,请自行更改. 背景:一个接口的不同情况,其实就是请求参数不一样.期望结果不一样.把这些不一样的东西都提取出来进行管理,下次可以直接使用.因 ...

  3. 关于我自己的Gui界面库完善

    仓库地址:https://gitee.com/GPRO/Gui 功能说明:  解析XML, 接入AngleScript. 接下来需要做的: 因为有了WPF,MFC,HTML甚至UE5的使用经验,这里我 ...

  4. .NET TCP、UDP、Socket、WebSocket

    做.NET应用开发肯定会用到网络通信,而进程间通信是客户端开发使用频率较高的场景. 进程间通信方式主要有命名管道.消息队列.共享内存.Socket通信,个人使用最多的是Sokcet相关. 而Socke ...

  5. 1分钟了解HashSet的使用

    前言:刷leetcode的时候体验到hashset有多厉害了,用了他剪枝之后直接不爆超时了.速度大大滴快 使用方法 1.创建set对象Set<Integer>set=new HashSet ...

  6. Java还是C#?我该如何选择?给年轻人的建议...

    一.年轻人应该通吃 其实这不应该是我们真正的主题,而且入了行的也很少会java还是c#这么比,但初学的,java和c#往往就代表了两大流派,java代替了j2ee,c#代替了.net,ok,没有关系, ...

  7. web3 产品介绍:Mirror.xyz是一个创新的去中心化出版平台

    Mirror.xyz是一个创新的去中心化出版平台,它使作者能够创建.发布和管理自己的内容,并与读者建立直接的经济联系.在本文中,我们将介绍Mirror.xyz的主要特点.功能以及如何使用它来发布和消费 ...

  8. 14、SpringMVC之注解配置

    14.1.概述 在实际工作中,一般使用配置类和注解代替web.xml和SpringMVC配置文件的功能: 在 Servlet3.0 环境中,容器会在类路径中查找实现了 javax.servlet.Se ...

  9. 英语表达中address和solve的区别

    "Address" 和 "solve" 都表示处理问题,但在具体用法和含义上有所不同: Address: 含义: 处理.应对.讨论或提及问题. 强调: 关注并开 ...

  10. Visual Studio 个人配置和插件

    主题和字体 一般为黑色深色主题,看起来比较舒服. 字体使用Fira Code,好处就是它把 =>和!=换成更加熟悉的表示.就比如以下.缺点就是习惯之后,看别人的代码就不习惯. 插件 当然是首推R ...