c#表达式树入门,看这个就够了
题记:
由于反射需要大量的性能开销,所以推荐用表达式树或者emit,但是emit 如果不熟悉指令编程的话,使用成本很大,所以优先推荐表达式树,但是网上给出来的文档 都非常的复杂,只是带你使用,刚好我团队的小伙伴也不太理解,所以我来整理一篇简单入门版本的.
问: 反射有3种方式,一个是获取值,一个是赋值,一个是调用方法 (如构造器 静态方法 普通方法等),哪个才是性能元凶
先总结: 表达式树 就是代码的拼接, 所以有以下三个区域
- 入参
- 代码块
- 返回值
- 入参:
Expression.Parameter()
- 代码块
- 常见的代码块 加/减/乘/除/等于/获取字段值/获取属性值/调用方法/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包装,可以省略。
以下是例子的由简单到复杂的汇总,代码在后面的框里面
- Expression<Func<int, int, int>> func = (m, n) => m \* n;
- Expression<Func<T01, String>> func = (T01 m) => m.Name;
- Expression<Action<T01,String>> func = (T01 m, String str) => m.Name = str;
- Expression<Func<String, T01>> expression1 = (String str)=>new T01() { Name = str };
- Expression<Action<T01,T02>> action = (T01 t1, T02 t2)=> t2.Name = t1.Name;
- 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); } }