c#表达式树入门,看这个就够了

news/2025/1/13 8:01:14/文章来源:https://www.cnblogs.com/yichaohong/p/18518890
题记:
由于反射需要大量的性能开销,所以推荐用表达式树或者emit,但是emit 如果不熟悉指令编程的话,使用成本很大,所以优先推荐表达式树,但是网上给出来的文档 都非常的复杂,只是带你使用,刚好我团队的小伙伴也不太理解,所以我来整理一篇简单入门版本的.
问: 反射有3种方式,一个是获取值,一个是赋值,一个是调用方法 (如构造器 静态方法 普通方法等),哪个才是性能元凶
先总结: 表达式树 就是代码的拼接, 所以有以下三个区域
- 入参
- 代码块
- 返回值
  1. 入参:
    Expression.Parameter()
  2. 代码块
  • 常见的代码块 加/减/乘/除/等于/获取字段值/获取属性值/调用方法/new对象
  • Expression.Property()获取某对象字段的值  test.M
  • Expression.Assign(), 给对象属性或字段赋值 test.M = ""
  • Expression.Multiply(),加减乘除 的方法
  • Expression.Call(), 调用某对象的方法,可以是静态
  • Expression.Constant() 常值 比如  1  其实应该是 Constant("This is t2 name", typeof(String));
  • Expression.New()创建对象
  • Expression.NotEqual ELSE For循环等等,这个入门后 自己查资料即可
  • Expression.Convert(),强制性转换 如 Expression.Convert()配合Call

  3. 出参 或者 返回值

  • 返回值 其实就是2个:
  • 无返回 值即 可执行的代码块{}, 
  • 返回一个值 如 new对象,入参的对象,常量值,或者 List数组
  • Expression.Block()
  • Expression.MemberInit()
  • Expression.ListInit()
  其实理论上来说,表达式树没有固定的返回的值,因为 表达式树中不允许出现return,只是调用Compile()的推测,所以其实 返回值 只有一个Block**,其他全部是 代码块,只不过我觉得这时候应该区分出来,这个也是所有的表达式树在调用Lamda 的时候标准写法都是 Block包装,可以省略。
以下是例子的由简单到复杂的汇总,代码在后面的框里面
  1. Expression<Func<int, int, int>> func = (m, n) => m \* n;
  2. Expression<Func<T01, String>> func = (T01 m) => m.Name;
  3. Expression<Action<T01,String>> func = (T01 m, String str) => m.Name = str;
  4. Expression<Func<String, T01>> expression1 = (String str)=>new T01() { Name = str };
  5. Expression<Action<T01,T02>> action = (T01 t1, T02 t2)=> t2.Name = t1.Name;
  6. Fun<T01, T02> func = T01 t1 => new T02(){ Name = t1.Name}
//1.Expression<Func<int, int, int>> func = (m, n) => m \* n;
//入参
ParameterExpression t1 = Expression.Parameter(typeof(Int32), "t1");
ParameterExpression t2 = Expression.Parameter(typeof(Int32), "t2");
//代码块
BinaryExpression multiply = Expression.Multiply(t1, t2);
//返回值Block
BlockExpression block = Expression.Block(multiply);
//编译
Expression<Func<int, int, int>> expression =  Expression.Lambda<Func<int, int, int>>(block, t1, t2);
Func<int, int, int> func = expression.Compile();

  

//上面说了 表达式树不允许出现显示的代码块 不允许出现return,所以注意以下错误的写法```c#
Expression<Func<int, int,int>> expression = (m,n)=> m * n;
//以下是错误形式
Expression<Action<int, int>> expression = (m,n)=> { };
Expression<Action<int, int>> expression = (m,n)=> {m + n };
Expression<Func<int, int,int>> expression = (m,n)=> { return m + n; };

  

public class T01
{public String Name { get; set; }
}public class T02
{public String Name { get; set; }
}

  

//操作 Expression<Func<T01, String>> func = (T01 m) => m.Name;//入参
ParameterExpression m = Expression.Parameter(typeof(T01), "m");
//代码块
MemberExpression left = Expression.Property(m, typeof(T01).GetProperty(nameof(T01.Name)));
//错误的写法是 
//Expression.Property(null, typeof(T01).GetProperty(nameof(T01.Name))),
//因为有些以为 我都 传了typeof(Test) 肯定获取到对应属性的值,
//其实表达式树是 编译代码的,所以每一步都得实现,少一个不可。
//这里的 是 m.Name 其实分为3步  m  .   Name,都必须有//返回值
BlockExpression block1 = Expression.Block(left);
Expression<Func<T01, String>> expression = Expression.Lambda<Func<T01, String>>(block1, m);
Func<T01, String> func = expression.Compile();

  

 //Expression<Action<T01>> func = (T01 m) => m.Name = "new name";//入参
ParameterExpression m = Expression.Parameter(typeof(T01), "m");
//代码块
//拼接 m.Name
MemberExpression left = Expression.Property(m, typeof(T01).GetProperty(nameof(T01.Name)));
// m.Name = "new name"
BinaryExpression assignExpression1 = Expression.Assign(left, Expression.Constant("new name", typeof(String)));
//返回值 此处无
BlockExpression block = Expression.Block(assignExpression1);
Expression<Action<T01>> expression = Expression.Lambda<Action<T01>>(block, m);
Action<T01> func = expression.Compile();
T01 t01 = new T01() { Name = "old name" };
func(t01);
Console.WriteLine(t01.Name);//new name

  

// Expression<Action<T01,String>> func = (T01 m, String str) => m.Name = str;//入参
ParameterExpression m = Expression.Parameter(typeof(T01), "m");
ParameterExpression str = Expression.Parameter(typeof(String), "str");
//代码块
//拼接 m.Name
MemberExpression left = Expression.Property(m, typeof(T01).GetProperty(nameof(T01.Name)));
// m.Name = str
//Assign 赋值的
BinaryExpression assignExpression1 = Expression.Assign(left, left);
//返回值
BlockExpression block = Expression.Block(assignExpression1);
Expression<Action<T01, String>> expression = Expression.Lambda<Action<T01, String>>(block, m, str);
Action<T01, String> func = expression.Compile();
T01 t01 = new T01() { Name = "old name" };
func(t01,"new name");
Console.WriteLine(t01.Name);//new name

  

//Expression<Func<String, T01>> expression1 = (String str)=>new T01() { Name = str };
//入参
ParameterExpression str = Expression.Parameter(typeof(String), "str");
//代码块
MemberAssignment bind = Expression.Bind(typeof(T01).GetProperty(nameof(T01.Name)), str);
//返回值
MemberInitExpression newT01 = Expression.MemberInit(Expression.New(typeof(T01)), bind);
BlockExpression block = Expression.Block(newT01);
Expression<Func<String, T01>> expression = Expression.Lambda<Func<String, T01>>(block,str);
Func<String, T01> func = expression.Compile();
T01 t01 = new T01() { Name = "old name" };
t01 = func("new name");
Console.WriteLine(t01.Name);//new name

  

这里有个问题是  new T01(){}; 
  4.1. 这里是对象初始化模块,而不是 var t01 = new T02();t02.Name="";
所以一定得区分,**而对象初始化模块 用得 Bind() 关系处理的,需要和Assign()区分开来
  4.2. 注意Expression.MemberInit(Expression.New(typeof(T01))) == Expression.New(typeof(T01)), 如果没有绑定关系 其实可以去掉 MemberInit()的写法,就和 可以去掉所有的Block一样,以及 Expression.Property()只传递一个 name 值一样,其实里面做了简化
Expression<Action<T01,T02>> action = (T01 t1, T02 t2)=> t2.Name = t1.Name;//入参
ParameterExpression t1 = Expression.Parameter(typeof(T01), "t1");
ParameterExpression t2 = Expression.Parameter(typeof(T02), "t2");
//代码块
//第二步,t2.Name 拆解为 参数t2, 属性符号 . 属性名称 Name
var member1 = Expression.Property(t2, nameof(T02.Name));
//拆解 t1.Name
var member2 = Expression.Property(t1, nameof(T01.Name));
//将member2赋值给member1
var member = Expression.Assign(member1, member2);Expression block = Expression.Block(member);
Expression<Action<T01, T02>> expression = Expression.Lambda<Action<T01, T02>>(block, t1, t2);
var func = expression.Compile();
T01 t01 = new T01() { Name = "This is t1 name" };
T02 t02 = new T02() { Name = "This is t2 name" };func(t01, t02);
Console.WriteLine(t02.Name); //输出的是 This is t1 name; 

  

Fun<T01, T02> func = T01 t1 => new T02(){ Name = t1.Name}的转换器//定义入参
ParameterExpression t1 = Expression.Parameter(typeof(T01), "t1");
//代码块
//拆解 t1.Name
var member2 = Expression.Property(t1, nameof(T01.Name));
//因为这里是new,所以 这里实际是 绑定 T02.Name与 T01.Name的关系
var member = Expression.Bind(typeof(T02).GetProperty(nameof(T02.Name)), member2);
//当创建对象的时候,依据绑定关系 new T02()
//当然也可以 var member = Expression.Bind(typeof(T02).GetProperty(nameof(T02.Name)), Expression.Constant("xxxxx"));
//如果没有 member 那么可以直接 New() 不需要MemberInit()
Expression block = Expression.Block(Expression.MemberInit(Expression.New(typeof(T02)), member));
Expression<Func<T01, T02>> expression = Expression.Lambda<Func<T01, T02>>(block, t1);
var func = expression.Compile();
T01 t01 = new T01() { Name = "This is t1 name" };
T02 t02 = new T02() { Name = "This is t2 name" };t02 = func(t01);
Console.WriteLine(t02.Name);//这里输出的  "This is t1 name"

  

**注意 这里是 Bind() 而不是 Assign(), 那这里可以用 Assign()吗?**

 答案是不行.因为 Assign 的写法就是  T02 t02 = new T02(); t02.Name = t01.Name;return t02; 这个原因上面解释过, 这里在强调下, 表达式树 不允许代码块,不允许return,只允许单操作.

最后 在强调下,表达式树 其实 就是 入参 出参 返回的代码拼接,然后执行成 我们想要的代码,虽然看起来复杂,但是实际就是一步步来的.
 

留个作业,

1. 将上面的 Fun<T01, T02> func 改成通用的写法. 下面直接贴了,我在本地没调试,大家可以自己调试下
2. 如何实现 Expression.Call 的执行方式 
3. 将注释上面的功能都实现一遍
public class MapHelper
{private static Dictionary<String, Object> dicFunc = new Dictionary<string, Object>();/// 没有判断泛型 IsGenericType/// 没操作 属性 不同类型 转不同类型的操作(重新调用Map类即可), ps => IEnumerable 转换需要调用 Expression.Call( IEnumerable.Select(Map) )来处理转换条件/// 没操作 IsNullable 转为值类型数据转换  需要 Expression.PropertyType("value")来强制性赋值到 基础类型/// 没操作 特性转换/// 可以理解成只做 类型相同字段 的  值赋值转换public static void Map<T1, T2>(T1 source, T2 destination) where T1 : class, new() where T2 : class, new(){Type tIn = typeof(T1);Type tResult = typeof(T2);String key = $"{tIn}_Convert_{tResult}";if (!dicFunc.TryGetValue(key, out var func)){lock (dicFunc){if (!dicFunc.TryGetValue(key, out func)){ParameterExpression t1 = Expression.Parameter(tIn);ParameterExpression t2 = Expression.Parameter(tResult);List<Expression> list = new List<Expression>();Dictionary<PropertyInfo, PropertyInfo> map = new Dictionary<PropertyInfo, PropertyInfo>();foreach (var item in tResult.GetProperties().Where(x => x.PropertyType.IsPublic && x.CanWrite && x.CanRead)){String name = item.Name;var p1 = tIn.GetProperty(name);if (p1 == null) continue;if (!p1.PropertyType.IsPublic || !p1.CanWrite || !p1.CanRead || p1.PropertyType != item.PropertyType) continue;MemberExpression mIn = Expression.Property(t1, p1);MemberExpression mResult = Expression.Property(t2, item);var memberBinding = Expression.Assign(mResult, mIn);list.Add(memberBinding);}Expression<Action<T1, T2>> lambda = Expression.Lambda<Action<T1, T2>>(Expression.Block(list), t1, t2);var action = lambda.Compile();dicFunc[key] = action;func = action;}}}((Action<T1, T2>)func)(source, destination);}public static TResult Map<TIn, TResult>(TIn source) where TIn : class, new() where TResult : class, new(){Type tIn = typeof(TIn);Type tResult = typeof(TResult);String key = $"{tIn}_New_{tResult}";if (!dicFunc.TryGetValue(key, out var func)){lock (dicFunc){if (!dicFunc.TryGetValue(key, out func)){ParameterExpression t1 = Expression.Parameter(tIn);List<MemberBinding> list = new List<MemberBinding>();foreach (var item in tResult.GetProperties().Where(x => x.PropertyType.IsPublic && x.CanWrite && x.CanRead)){String name = item.Name;var p1 = tIn.GetProperty(name);if (p1 == null) continue;if (!p1.PropertyType.IsPublic || !p1.CanWrite || !p1.CanRead || p1.PropertyType != item.PropertyType) continue;MemberExpression property1 = Expression.Property(t1, p1);var memberBinding = Expression.Bind(item, property1);list.Add(memberBinding);}Expression<Func<TIn, TResult>> lambda;if (list.Count == 0){lambda = Expression.Lambda<Func<TIn, TResult>>(Expression.Block(Expression.Constant(null, tResult)), t1);}else{lambda = Expression.Lambda<Func<TIn, TResult>>(Expression.MemberInit(Expression.New(tResult), list), t1);}var action = lambda.Compile();dicFunc[key] = action;func = action;}}}return ((Func<TIn, TResult>)func)(source);}
}
View Code

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/825195.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

黑马PM-电商项目-电商后台

电商后台的核心作用及架构电商后台基础支撑

MEAS-Measurement: Sensors

Measurement: Sensors是一本开放获取期刊,对来自这个高度热门和多学科学科学科的所有相关领域的原创、高质量贡献开放。邀请提交关于科学、工程和技术的理论、研究、开发、制造和应用的各个方面的投稿,这些领域包括当今传感器和传感器系统。我们鼓励作者提交有关该领域的新材…

TI-Trends in Immunotherapy

Trends in Immunotherapy 是一本开放获取的同行评审期刊,涵盖与所有基于免疫系统的领域相关的各个学科。TI 的目标受众包括来自学术界、医疗行业、教育界等的科研人员、专业从业人员和医学学者。它提供了一个论坛来分享学术著作,以科学与医学相结合的方式推进免疫疗法。 发表…

为什么 C++ 编译速度比 Java 慢得多

### 为什么 C++ 编译速度比 Java 慢得多 在探讨为什么 C++ 编译速度比 Java 慢得多时,我们可以归纳出几个核心原因:C++的编译模型更为复杂、模板元编程、宏处理以及链接时间。其中,C++的编译模型更为复杂这一点尤为突出。C++需要处理的细节更多,如模板实例化、头文件的重复…

赠送天翼云电脑,解决一点园子的商业化烦恼

救园成功后,我们一边开启AI之旅,一边尝试做一些当前力所能及的商业化项目,增加园子的收入来源。 这个月开始尝试做一些天翼云电脑的代理业务,先给大家赠送一些天翼云电脑体验一下。 赠送方案:终身PLUS会员赠送1年4核8G云电脑(限1个名额) 终身会员赠送3个月4核8G云电脑(…

VR游戏和传统游戏在体验上有多大差异_1

​​虚拟现实(VR)游戏和传统视频游戏在玩家体验上呈现显著差异,这些差异主要体现在以下等方面:1.沉浸感和现实感;2.交互方式和控制;3.硬件需求和可访问性;4.游戏设计和内容;5.社交互动;6.身体参与和舒适度。VR游戏通过创新的技术提供了更为沉浸式的游戏体验,而传统游…

阿里云服务器的端口有什么用

阿里云服务器的端口在网络通信中起着重要的作用。大数据平台通常包括数据采集、数据存储、数据处理和数据分析等模块。这种平台的使阿里云服务器上的端口用于接收和发送网络数据包,以实现不同应用程序之间的通信。用,帮助企业从大数据中获取价值,提升业务效率,优化决策过程…

为什么别人的简历可以一击即中,你的就石沉大海?

很多小伙伴平均每天投20份简历,但是最终收获offer的比率却少的可怜。为什么别人的简历可以一击即中,你的就石沉大海?这年头找工作就像找对象一样——难! 很多小伙伴平均每天投20份简历,但是最终收获offer的比率却少的可怜。为什么别人的简历可以一击即中,你的就石沉大海?…

使用PHP构建命令行应用的技巧

### 使用PHP构建命令行应用的技巧 在开头,我们直接回答使用PHP构建命令行应用的技巧:选择合适的库、理解命令行界面(CLI)的基本原理、熟悉PHP CLI的内置功能、编写可维护的代码、进行彻底的测试。其中,选择合适的库是基础且关键的一步。使用如Symfony Console或Laravel Ze…

【VMware by Broadcom】新闻:Broadcom 宣布恢复 VMware vSphere Enterprise Plus 许可证。

VMware 被 Broadcom 收购后,发生了非常多的变化,其中变化之一就是,将以前众多的产品组件打包成了“四”个套件进行销售,并且将永久授权改成了订阅模式,你可以通过以下链接了解具体详情:VMware Cloud Foundation(VCF) VMware vSphere Foundation(VVF) VMware vSphere …

开源 PHP 商城项目 CRMEB 安装和使用教程

说到电商系统,很多人第一反应可能是 Shopify 或 Magento。没错,这些平台确实功能强大,但是...它们也太强大了,不仅复杂还昂贵,对于刚起步的创业者来说简直是压力山大。 但是从零开始开发一个完整的电商系统不仅耗时耗力,还需要考虑各种复杂的业务场景和技术挑战。 那么,…

Java 传参时,如何做到两个 String 实参的实际值交换_3

### Java 传参时,如何做到两个 String 实参的实际值交换 在Java中,所有的参数传递都是值传递,这意味着方法接收的是实参值的一个副本。对于基本数据类型,这个副本是实际值;对于对象,副本是引用的一个拷贝。因此,直接在方法内部交换两个 `String` 实参的实际值是不可能的…