表达式目录树(Expression)
一:什么是表达式树
Expression我们称为是表达式树,是一种数据结构体,用于存储需要计算,运算的一种结构,这种结构可以只是存储,而不进行运算。通常表达式目录树是配合Lambda一起来使用的,lambda可以是匿名方法,当然也可以使用Expression来动态的创建!下面我们举例来说明什么是表达式目录树。
先创建一个People的实体,下面会用到
/// <summary> /// 实体类 /// </summary> public class People { public int Age { get; set; } public string Name { get; set; } public int Id; }
我们可以通过下面创建表达式目录树,我们称之为A种方式:
Expression<Func<People, bool>> lambda = x => x.Id.ToString().IndexOf("5") >= 0;
我们还可以使用Expression来动态创建,我们称之为B种方式:
var peopleParam = Expression.Parameter(typeof(People), "x");//创建一个x,类型为people //得到x.Id MemberExpression idParam = Expression.Field(peopleParam, "Id"); //得到ToString方法 MethodInfo toStringWay = typeof(int).GetMethod("ToString", new Type[] { }); //得到IndexOf的方法,然后new Type[]这个代表是得到参数为string的一个方法 MethodInfo indexOfWay = typeof(string).GetMethod("IndexOf", new Type[] { typeof(string) }); //通过下面方法得到x.Id.ToString() MethodCallExpression tostringResult = Expression.Call(idParam, toStringWay, new Expression[] { }); //通过下面方法得到x.Id.ToString().IndexOf("5") ,MethodCallExpression继承于Expression MethodCallExpression indexOfResult = Expression.Call(tostringResult, indexOfWay, new Expression[] { Expression.Constant("5") }); //x.Id.ToString().IndexOf("5")>=0 var lambdaBody = Expression.GreaterThanOrEqual(indexOfResult, Expression.Constant(0)); //得到x => x.Id.ToString().IndexOf("5") >= 0,后面的一个参数指的是x,如果有多个则指定多个 Expression<Func<People,bool>> lambdaResult = Expression.Lambda<Func<People, bool>>(lambdaBody, new ParameterExpression[] { peopleParam }); //通过lambdaResult.Compile()得到Func<People,bool>这样的委托,然后Invoke是调用委托 bool result = lambdaResult.Compile().Invoke(new People() { Id = 155 });
A种和B种得到的结果是一致的,只不过第一种是通过lambda匿名方法来构建,第二种是通过动态的Expression来构建。另外下面的原理也是一样的
//普通的Lambda表达式 Func<int,int,int> func = (x,y)=> x + y - 2; //表达式目录树的Lambda表达式声明方式 Expression<Func<int, int, int>> expression = (x, y) => x + y - 2;
//表达式目录树的拼接方式实现 ParameterExpression parameterx = Expression.Parameter(typeof(int), "x"); ParameterExpression parametery = Expression.Parameter(typeof(int), "y"); ConstantExpression constantExpression = Expression.Constant(2, typeof(int)); BinaryExpression binaryAdd = Expression.Add(parameterx, parametery); BinaryExpression binarySubtract = Expression.Subtract(binaryAdd, constantExpression); Expression<Func<int, int, int>> expressionMosaic = Expression.Lambda<Func<int, int, int>>(binarySubtract, new ParameterExpression[] { parameterx, parametery });
int ResultLambda = func(5, 2); int ResultExpression = expression.Compile()(5, 2); int ResultMosaic = expressionMosaic.Compile()(5, 2); Console.WriteLine($"func:{ResultLambda}"); Console.WriteLine($"expression:{ResultExpression}"); Console.WriteLine($"expressionMosaic:{ResultMosaic}");
下面举例说明以下Expression.Block
ParameterExpression varExpr = Expression.Variable(typeof(int), "x"); //add(int x); var ex1 = Expression.Assign(varExpr, Expression.Constant(1)); //x = 1; var ex1 = x; var ex2 = Expression.Add(ex1, Expression.Constant(5)); //var ex2 = ex1 + 5;//6 var ex4 = Expression.Add(ex2, Expression.Constant(9)); //var ex4 = ex2 + 9; //15 var ex5 = Expression.Add(ex4, Expression.Constant(8)); // var ex5 = ex4 + 8; //23 BlockExpression blockExpr = Expression.Block( new ParameterExpression[] { varExpr }, ex1, ex2, ex4, ex5 );
该代码等效于,返回的结果都以最后一个Expression为主,则为ex5这个表达式
public int add(int x) { x = 1; var ex1 = x; var ex2 = ex1 + 5;//6 var ex4 = ex2 + 9; //15 var ex5 = ex4 + 8; //23 return ex5; //23 }
Expression.Block没有返回值
{ Expression A = Expression.Constant("第一大"); Expression B = Expression.Constant("第二大"); Expression ex = Expression.GreaterThan(Expression.Constant(1), Expression.Constant(2)); var method = typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }); var AM = Expression.Call(method, A); var BM = Expression.Call(method, B); var condition = Expression.IfThenElse(ex, AM, BM); var blockExpr = Expression.Block(condition); //IfThenElse是没有返回值的 foreach (var expr in blockExpr.Expressions) Console.WriteLine(expr.ToString()); var lambdaExpression = Expression.Lambda<Action>(blockExpr).Compile(); lambdaExpression(); }
下图是Expression的一些变量
二:表达式目录树与委托
Expression一般都是都是配合委托一起来使用的,比如和委托Action(没有返回值),Func(至少有一个返回参数,且最后一个值为返回参数),Action,Func既可以直接传入一个与之匹配的实体方法,又可以传入lambda表达式这种匿名类(这种是声明lambda表达式的一种快捷方式)。Expression,Action,Func关键词是在.net 3.5之后出现的。Expression<Func<>>是可以转成Func的(通过compile()这个方法转换)。反过来则不行。我们可以理解为Func<>经过定义后,就无法改变它了。而表达式树(Expression<Func<>>则是可以进行变更的。Lambda
使用lambda表达声明表达式目录树的时候注意不能有{},即:
Func<int, int, int> func = (m, n) => m * n + 2;
上面这样是可以的。但是下面这样是不被允许的:
Expression<Func<int, int, int>> exp1 = (m, n) => { return m * n + 2; };//不能有语句体 只能是一行,不能有大括号
下面的例子来解析一下委托和表达式目录树
1 #region PrivateMethod 2 private static void Do1(Func<People, bool> func) 3 { 4 List<People> people = new List<People>(); 5 people.Where(func); 6 } 7 private static void Do1(Expression<Func<People, bool>> func) 8 { 9 List<People> people = new List<People>() 10 { 11 new People(){Id=4,Name="123",Age=4}, 12 new People(){Id=5,Name="234",Age=5}, 13 new People(){Id=6,Name="345",Age=6}, 14 }; 15 16 List<People> peopleList = people.Where(func.Compile()).ToList(); 17 } 18 19 private static IQueryable<People> GetQueryable(Expression<Func<People, bool>> func) 20 { 21 List<People> people = new List<People>() 22 { 23 new People(){Id=4,Name="123",Age=4}, 24 new People(){Id=5,Name="234",Age=5}, 25 new People(){Id=6,Name="345",Age=6}, 26 }; 27 28 return people.AsQueryable<People>().Where(func); 29 } 30 #endregion
然后调用的时候为如下:
1 Expression<Func<People, bool>> lambda1 = x => x.Age > 5; 2 Expression<Func<People, bool>> lambda2 = x => x.Id > 5; 3 Expression<Func<People, bool>> lambda3 = lambda1.And(lambda2); 4 Expression<Func<People, bool>> lambda4 = lambda1.Or(lambda2); 5 Expression<Func<People, bool>> lambda5 = lambda1.Not(); 6 Do1(lambda3); 7 Do1(lambda4); 8 Do1(lambda5);
三:使用Expression来进行不同对象的相同名字的属性映射
如果我们有一个新的对象和People属性基本上一致,如下:
/// <summary> /// 实体类Target /// PeopleDTO /// </summary> public class PeopleCopy { public int Age { get; set; } public string Name { get; set; } public int Id; }
现在我们想要把People的中Age,Name,Id等赋值给PeopleCopy,第一种我们直接想到的是硬编码,然后如下:
People people = new People() { Id = 11, Name = "加菲猫", Age = 31 }; //PeopleCopy copy = (PeopleCopy)people; //这种强制转换肯定是不行的 PeopleCopy peopleCopy = new PeopleCopy() { Id = people.Id, Name = people.Name, Age = people.Age };
但是如果有多个类型转换,要写N次,然后不同用且费力,所以我们会想到通用的方法,比如使用:【反射】,【序列化反序列化】,【缓存+表达式目录】,【泛型+表达式目录】,【AutoMapper】,我们可以用这五种方法都小试一下!
1:反射完成对象属性映射
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ExpressionDemo.MappingExtend { public class ReflectionMapper { /// <summary> /// 反射 /// </summary> /// <typeparam name="TIn"></typeparam> /// <typeparam name="TOut"></typeparam> /// <param name="tIn"></param> /// <returns></returns> public static TOut Trans<TIn, TOut>(TIn tIn) { TOut tOut = Activator.CreateInstance<TOut>(); foreach (var itemOut in tOut.GetType().GetProperties()) { var propIn = tIn.GetType().GetProperty(itemOut.Name); itemOut.SetValue(tOut, propIn.GetValue(tIn)); } foreach (var itemOut in tOut.GetType().GetFields()) { var fieldIn = tIn.GetType().GetField(itemOut.Name); itemOut.SetValue(tOut, fieldIn.GetValue(tIn)); } return tOut; } } }
2:使用序列化和反序列化来完成对象属性映射:
using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ExpressionDemo.MappingExtend { /// <summary> /// 使用第三方序列化反序列化工具 /// /// 还有automapper /// </summary> public class SerializeMapper { /// <summary> /// 序列化反序列化方式 /// </summary> /// <typeparam name="TIn"></typeparam> /// <typeparam name="TOut"></typeparam> public static TOut Trans<TIn, TOut>(TIn tIn) { return JsonConvert.DeserializeObject<TOut>(JsonConvert.SerializeObject(tIn)); } } }
3:缓存+表达式目录树
using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Text; using System.Threading.Tasks; namespace ExpressionDemo.MappingExtend { /// <summary> /// 生成表达式目录树 缓存 /// </summary> public class ExpressionMapper { /// <summary> /// 字典缓存--hash分布 /// </summary> private static Dictionary<string, object> _Dic = new Dictionary<string, object>(); /// <summary> /// 字典缓存表达式树 /// </summary> /// <typeparam name="TIn"></typeparam> /// <typeparam name="TOut"></typeparam> /// <param name="tIn"></param> /// <returns></returns> public static TOut Trans<TIn, TOut>(TIn tIn) { string key = string.Format("funckey_{0}_{1}", typeof(TIn).FullName, typeof(TOut).FullName); if (!_Dic.ContainsKey(key)) { ParameterExpression parameterExpression = Expression.Parameter(typeof(TIn), "p"); List<MemberBinding> memberBindingList = new List<MemberBinding>(); foreach (var item in typeof(TOut).GetProperties()) { MemberExpression property = Expression.Property(parameterExpression, typeof(TIn).GetProperty(item.Name)); MemberBinding memberBinding = Expression.Bind(item, property); memberBindingList.Add(memberBinding); } foreach (var item in typeof(TOut).GetFields()) { MemberExpression property = Expression.Field(parameterExpression, typeof(TIn).GetField(item.Name)); MemberBinding memberBinding = Expression.Bind(item, property); memberBindingList.Add(memberBinding); } MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(typeof(TOut)), memberBindingList.ToArray()); Expression<Func<TIn, TOut>> lambda = Expression.Lambda<Func<TIn, TOut>>(memberInitExpression, new ParameterExpression[] { parameterExpression }); Func<TIn, TOut> func = lambda.Compile();//拼装是一次性的 _Dic[key] = func; } return ((Func<TIn, TOut>)_Dic[key]).Invoke(tIn); } } }
4:泛型+表达式目录树
1 using System; 2 using System.Collections.Generic; 3 using System.Linq.Expressions; 4 5 namespace ExpressionDemo.MappingExtend 6 { 7 /// <summary> 8 /// 生成表达式目录树 泛型缓存 9 /// </summary> 10 /// <typeparam name="TIn"></typeparam> 11 /// <typeparam name="TOut"></typeparam> 12 public class ExpressionGenericMapper<TIn, TOut>//Mapper`2 13 { 14 private static Func<TIn, TOut> _FUNC = null; 15 static ExpressionGenericMapper() 16 { 17 ParameterExpression parameterExpression = Expression.Parameter(typeof(TIn), "p"); 18 List<MemberBinding> memberBindingList = new List<MemberBinding>(); 19 foreach (var item in typeof(TOut).GetProperties()) 20 { 21 MemberExpression property = Expression.Property(parameterExpression, typeof(TIn).GetProperty(item.Name)); //p.Age 22 MemberBinding memberBinding = Expression.Bind(item, property); //Age=p.Age 23 memberBindingList.Add(memberBinding); 24 } 25 foreach (var item in typeof(TOut).GetFields()) 26 { 27 MemberExpression property = Expression.Field(parameterExpression, typeof(TIn).GetField(item.Name)); 28 MemberBinding memberBinding = Expression.Bind(item, property); 29 memberBindingList.Add(memberBinding); 30 } 31 //new PeopleCopy() {Age = p.Age, Name = p.Name, Id = p.Id} 32 MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(typeof(TOut)), memberBindingList.ToArray()); 33 //p => new PeopleCopy() {Age = p.Age, Name = p.Name, Id = p.Id} 34 Expression<Func<TIn, TOut>> lambda = Expression.Lambda<Func<TIn, TOut>>(memberInitExpression, new ParameterExpression[] 35 { 36 parameterExpression 37 }); 38 _FUNC = lambda.Compile();//拼装是一次性的 39 } 40 public static TOut Trans(TIn t) 41 { 42 return _FUNC(t); 43 } 44 } 45 }
5:使用.netFramwork框架自带的AutoMapper,首先我们要nuget添加引用AutoMapper即可直接使用,具体代码为:
1 using AutoMapper; 2 3 namespace ExpressionDemo.MappingExtend 4 { 5 public class AutoMapperTest 6 { 7 public static TOut Trans<TIn, TOut>(TIn tIn) 8 { 9 return Mapper.Instance.Map<TOut>(tIn); 10 } 11 } 12 }