C#3.0新增功能10 表达式树 06 生成表达式
到目前为止,你所看到的所有表达式树都是由 C# 编译器创建的。 你所要做的是创建一个 lambda 表达式,将其分配给一个类型为 Expression<Func<T>>
或某种相似类型的变量。 这不是创建表达式树的唯一方法。 很多情况下,可能需要在运行时在内存中生成一个表达式。
由于这些表达式树是不可变的,所以生成表达式树很复杂。 不可变意味着必须以从叶到根的方式生成表达式树。 用于生成表达式树的 API 体现了这一点:用于生成节点的方法将其所有子级用作参数。 让我们通过几个示例来了解相关技巧。
Expression<Func<int>> sum = () => 1 + 2;
若要构造该表达式树,必须构造叶节点。 叶节点是常量,因此可以使用 Expression.Constant
方法创建节点:
var one = Expression.Constant(1, typeof(int)); var two = Expression.Constant(2, typeof(int));
接下来,将生成加法表达式:
var addition = Expression.Add(one, two);
一旦获得了加法表达式,就可以创建 lambda 表达式:
var lambda = Expression.Lambda(addition);
这是一个非常简单的 Lambda 表达式,因为它不包含任何参数。 在本节的后续部分,你将了解如何将实参映射到形参并生成更复杂的表达式。
对于此类简单的表达式,可以将所有调用合并到单个语句中:
var lambda = Expression.Lambda( Expression.Add(Expression.Constant(1, typeof(int)), Expression.Constant(2, typeof(int)) ) );
这是在内存中生成表达式树的基础知识。 更复杂的树通常意味着更多的节点类型,并且树中有更多的节点。 让我们再浏览一个示例,了解通常在创建表达式树时创建的其他两个节点类型:参数节点和方法调用节点。
生成一个表达式树以创建此表达式:
Expression<Func<double, double, double>> distanceCalc = (x, y) => Math.Sqrt(x * x + y * y);
首先,创建 x
和 y
的参数表达式:
var xParameter = Expression.Parameter(typeof(double), "x"); var yParameter = Expression.Parameter(typeof(double), "y");
按照你所看到的模式创建乘法和加法表达式:
var xSquared = Expression.Multiply(xParameter, xParameter); var ySquared = Expression.Multiply(yParameter, yParameter); var sum = Expression.Add(xSquared, ySquared);
接下来,需要为调用 Math.Sqrt
创建方法调用表达式。
var sqrtMethod = typeof(Math).GetMethod("Sqrt", new[] { typeof(double) }); var distance = Expression.Call(sqrtMethod, sum);
最后,将方法调用放入 lambda 表达式,并确保定义 lambda 表达式的参数:
var distanceLambda = Expression.Lambda(distance,xParameter, yParameter);
在这个更复杂的示例中,你看到了创建表达式树通常使用的其他几种技巧。
首先,在使用它们之前,需要创建表示参数或局部变量的对象。 创建这些对象后,可以在表达式树中任何需要的位置使用它们。
其次,需要使用反射 API 的一个子集来创建 MethodInfo
对象,以便创建表达式树以访问该方法。 必须仅限于 .NET Core 平台上提供的反射 API 的子集。 同样,这些技术将扩展到其他表达式树。
不仅限于使用这些 API 可以生成的代码。 但是,要生成的表达式树越复杂,代码就越难以管理和阅读。
让我们生成一个与此代码等效的表达式树:
Func<int, int> factorialFunc = (n) => { var res = 1; while (n > 1) { res = res * n; n--; } return res; };
请注意上面我未生成表达式树,只是生成了委托。 使用 Expression
类不能生成语句 lambda。 下面是生成相同的功能所需的代码。 它很复杂,这是因为没有用于生成 while
循环的 API,而是需要生成一个包含条件测试的循环和一个用于中断循环的标签目标。
1 var nArgument = Expression.Parameter(typeof(int), "n"); 2 var result = Expression.Variable(typeof(int), "result"); 3 4 // 创建一个表示返回值的标签 5 LabelTarget label = Expression.Label(typeof(int)); 6 7 var initializeResult = Expression.Assign(result, Expression.Constant(1)); 8 9 // 这是执行乘法运算的内部块, 10 // 并减小“n”的值 11 var block = Expression.Block( 12 Expression.Assign(result, 13 Expression.Multiply(result, nArgument)), 14 Expression.PostDecrementAssign(nArgument) 15 ); 16 17 // 创建一个方法体 18 BlockExpression body = Expression.Block( 19 new[] { result }, 20 initializeResult, 21 Expression.Loop( 22 Expression.IfThenElse( 23 Expression.GreaterThan(nArgument, Expression.Constant(1)), 24 block, 25 Expression.Break(label, result) 26 ), 27 label 28 ) 29 );
表达式树 API 在 .NET Core 中较难导航,但没关系。 它们的用途相当复杂:编写在运行时生成代码的代码。 它们必须具有复杂的结构,才能在支持 C# 语言中提供的所有控件结构和尽可能减小 API 表面积之间保持平衡。 这种平衡意味着许多控件结构不是由其 C# 构造表示,而是由表示基础逻辑的构造表示,这些基础逻辑由编译器从这些较高级别的构造生成。
另外,此时存在一些不能通过使用 Expression
类方法直接生成的 C# 表达式。 一般来说,这些将是在 C# 5 和 C# 6 中添加的最新运算符和表达式。 (例如,无法生成 async
表达式,并且无法直接创建新 ?.
运算符。)
成在管理,败在经验;嬴在选择,输在不学! 贵在坚持!
个人作品
BIMFace.SDK.NET
开源地址:https://gitee.com/NAlps/BIMFace.SDK
系列博客:https://www.cnblogs.com/SavionZhang/p/11424431.html
系列视频:https://www.cnblogs.com/SavionZhang/p/14258393.html
技术栈
1、Visual Studio、.NET Core/.NET、MVC、Web API、RESTful API、gRPC、SignalR、Java、Python
2、jQuery、Vue.js、Bootstrap、ElementUI
3、数据库:分库分表、读写分离、SQLServer、MySQL、PostgreSQL、Redis、MongoDB、ElasticSearch、达梦DM
4、架构:DDD、ABP、SpringBoot、jFinal
5、环境:跨平台、Windows、Linux、Nginx
6、移动App:Android、IOS、HarmonyOS、微信小程序、钉钉、uni-app、MAUI
分布式、高并发、云原生、微服务、Docker、CI/CD、DevOps、K8S;Dapr、RabbitMQ、Kafka、RPC、Elasticsearch。
欢迎关注作者头条号 张传宁IT讲堂,获取更多IT文章、视频等优质内容。
出处:www.cnblogs.com/SavionZhang
作者:张传宁 技术顾问、培训讲师、微软MCP、系统架构设计师、系统集成项目管理工程师、科技部创新工程师。
专注于企业级通用开发平台、工作流引擎、自动化项目(代码)生成器、SOA 、DDD、 云原生(Docker、微服务、DevOps、CI/CD);PDF、CAD、BIM 审图等研究与应用。
多次参与电子政务、图书教育、生产制造等企业级大型项目研发与管理工作。
熟悉中小企业软件开发过程:可行调研、需求分析、架构设计、编码测试、实施部署、项目管理。通过技术与管理帮助中小企业实现互联网转型升级全流程解决方案。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
如有问题,可以通过邮件905442693@qq.com联系。共同交流、互相学习。
如果您觉得文章对您有帮助,请点击文章右下角【推荐】。您的鼓励是作者持续创作的最大动力!