表达式目录树,在C#中用Expression标识,这里就不介绍表达式目录树是什么了,有兴趣可以自行百度搜索,网上资料还是很多的。
这里主要分享的是如何动态构建表达式目录树。
构建表达式目录树的代码挺简单的,但是感觉不容易记住,我这边主要是根据反编译工具ILSpy来查看自己写已经写好的一个表达式(反编译里面的代码就是拆解表达式后再拼装的,下面会给例子),然后再稍微改动一下,这样有助于理解如何去手动拼装一个表达式。
既然是拷贝对象,先来一个最简单的对象拷贝的表达式:
假设有类:
public class Cat { public string Name { get; set; } public string Colour { get; set; } } public class Dog { public string Name { get; set; } public string Colour { get; set; } }
有一天,我发现邻居的猫和我家的狗毛色长得一样,邻居的狗名字叫的很好听,颜色也很好看,于是,我也想给我家的猫取相同的名字,把毛色也染成相同的颜色:
Dog dog = new Dog { Name = "二哈", Colour = "黄色" }; Cat cat = new Cat { Name = dog.Name, Colour = dog.Colour};
上面两句代码,转换成表达式目录树:
//定义一个表达式 Expression> exp = d => new Cat { Name = d.Name, Colour = d.Colour };
把我家的猫也取相同名字,也染毛色:
Cat c = exp.Compile().Invoke(dog);
借助ILSpy反编译工具,查看表达式Expression
ParameterExpression parameterExpression = Expression.Parameter(typeof(Dog), "d"); Expression> expression = Expression.Lambda >(Expression.MemberInit(Expression.New(typeof(Cat)), new MemberBinding[] { Expression.Bind((MethodInfo)MethodBase.GetMethodFromHandle(ldtoken(set_Name())), Expression.Property(parameterExpression, (MethodInfo)MethodBase.GetMethodFromHandle(ldtoken(get_Name())))), Expression.Bind((MethodInfo)MethodBase.GetMethodFromHandle(ldtoken(set_Colour())), Expression.Property(parameterExpression, (MethodInfo)MethodBase.GetMethodFromHandle(ldtoken(get_Colour())))) }), new ParameterExpression[] { parameterExpression });
分析此代码,(MethodInfo)MethodBase.GetMethodFromHandle(ldtoken(set_Name())) 类似这种代码我们编译器是不认识的,替换成反射获取Set_Name方法,整理之后代码如下:
1 ParameterExpression parameterExpression = Expression.Parameter(typeof(Dog), "d"); //定义一个变量 2 3 Type typeDog = typeof(Dog); 4 MethodInfo getName = typeDog.GetMethod("get_Name"); //获取get_Name()方法 5 MethodInfo getColour = typeDog.GetMethod("get_Colour"); //获取get_Colour()方法 6 Type typeCat = typeof(Cat); 7 MethodInfo setName = typeCat.GetMethod("set_Name"); //获取set_Name()方法 8 MethodInfo setColour = typeCat.GetMethod("set_Colour"); //获取set_Colour()方法 9 10 11 MemberExpression dName = Expression.Property(parameterExpression, getName);//d.Name 12 MemberAssignment name_dName = Expression.Bind(setName, dName);//Name = d.Name 13 MemberExpression dColour = Expression.Property(parameterExpression, getColour);//d.Colour 14 MemberAssignment colour_dColour = Expression.Bind(setColour, dColour);//Colour = d.Colour 15 16 //new Cat { Name = d.Name, Colour = d.Colour }; 表达式主体完成 17 MemberInitExpression body = Expression.MemberInit(Expression.New(typeof(Cat)), 18 new MemberBinding[] 19 { 20 name_dName, 21 colour_dColour 22 }); 23 //转换为表达式 24 Expression> expression = Expression.Lambda >(body 25 , new ParameterExpression[] 26 { 27 parameterExpression 28 } 29 ); 30 31 Cat c2 = expression.Compile().Invoke(dog);//编译表达式树由描述为可执行代码的 lambda 表达式,并生成一个委托执行
到这里,很多人会有疑问,你都自己写了表达式目录树了,还手动拼装一个一模一样的?而且手动拼装这么麻烦?有什么作用?往下看例子
有天去动物园,看见一只老虎,于是我又想到邻居的狗和我家的猫...于是乎我又想把自家的猫当做老虎了...于是,又写了下面这条表达式
Expression> exp = d => new Cat { Name = d.Name, Colour = d.Colour };
但是这样子不对啊?难道我每次换个类型,都要写一条表达式吗?这样明显是不合适的,所以我们需要封装一个方法,动态拼装出两个对象属性赋值的完整表达式,这个封装就不详细写了,上面已经写了如何去拼装一个表达式了,虽然只有部分,更多的可以自己写个复杂的表达式查看反编译代码。
这里用到泛型类作为缓存,否则此种写法没意义,还不如直接用反射来写。
浅拷贝:
1 ///2 /// 动态生成表达式目录树方式 浅拷贝 对象 3 /// 采用泛型类作为静态缓存,即 同一类型只会生成一次表达式目录树。(如果不缓存,和直接存反射没啥区别,不如直接用纯反射的方式深拷贝) 4 /// 5 /// 6 /// 7 public class ExpressionCloneObject 8 { 9 private static Func _FUNC = null; 10 11 static ExpressionCloneObject() 12 { 13 _FUNC = _CloneFunc(); 14 } 15 16 public static TOut Invoke(TIn t) 17 { 18 return _FUNC.Invoke(t); 19 } 20 21 private static Func _CloneFunc() 22 { 23 Type typeT1 = typeof(TIn); 24 Type typeT2 = typeof(TOut); 25 ParameterExpression parameterExpression = Expression.Parameter(typeT1, "t"); 26 List memberList = new List (); 27 //属性 28 foreach (PropertyInfo propertie in typeT2.GetProperties()) 29 { 30 PropertyInfo propertieT1 = typeT1.GetProperty(propertie.Name); 31 if (propertieT1 != null) 32 { 33 MethodInfo mSet = typeT2.GetMethod("set_" + propertie.Name, new Type[] { propertie.PropertyType }); 34 memberList.Add(Expression.Bind(mSet, Expression.Property(parameterExpression, propertieT1))); 35 } 36 else 37 { 38 FieldInfo fieldT1 = typeT1.GetField(propertie.Name);//可能T2中的属性对应T1中的字段 39 if (fieldT1 != null) 40 { 41 MethodInfo mSet = typeT2.GetMethod("set_" + propertie.Name, new Type[] { propertie.PropertyType }); 42 memberList.Add(Expression.Bind(mSet, Expression.Field(parameterExpression, fieldT1))); 43 } 44 } 45 } 46 //字段 47 foreach (FieldInfo field in typeT2.GetFields()) 48 { 49 FieldInfo fieldT1 = typeT1.GetField(field.Name); 50 if (fieldT1 != null) 51 { 52 memberList.Add(Expression.Bind(field, Expression.Field(parameterExpression, fieldT1))); 53 } 54 else 55 { 56 PropertyInfo propertieT1 = typeT1.GetProperty(field.Name); 57 if (propertieT1 != null)//可能T2中的字段对应T1中的属性 58 { 59 memberList.Add(Expression.Bind(field, Expression.Property(parameterExpression, propertieT1))); 60 } 61 } 62 } 63 MemberInitExpression body = Expression.MemberInit( 64 Expression.New(typeT2) 65 , memberList.ToArray() 66 ); 67 Expression > expression = Expression.Lambda >(body, new ParameterExpression[] { parameterExpression }); 68 69 return expression.Compile(); 70 } 71 }
深拷贝:
1 ///2 /// 3 /// 动态生成表达式目录树方式 深拷贝 对象 4 /// 采用泛型类作为静态缓存,即 同一类型只会生成一次表达式目录树。(如果不缓存,和直接存反射没啥区别,不如直接用纯反射的方式拷贝) 5 /// (注:对象的属性 不支持数组或集合,原因是对象中的数组或集合的个数可能不一样 6 /// ,并且这里用泛型缓存,所以以第一次的数组或集合生成的表达式目录树会缓存起来,下次直接拷贝对象如果集合中的个数和缓存中的不一致 7 /// 会导致拷贝结果不正确。解决方法可以先拷贝属性中的集合,再赋值回到此方法拷贝出来的对象上。或者换种深拷贝方法,深拷贝方式很多,如 8 /// 序列化、存反射、AutoMapper等等) 9 /// 10 /// 11 /// 12 public class ExpressionDeepCloneObject where TOut : class, new() 13 { 14 private static Func _FUNC = null; 15 16 public static TOut Invoke(TIn t) 17 { 18 if(_FUNC == null) 19 { 20 _FUNC = _DeepClone(t); 21 } 22 return _FUNC.Invoke(t); 23 } 24 25 /// 26 /// 表达式目录树深拷贝 27 /// 28 /// 被拷贝对象类型 29 /// 新对象类型 30 /// 被拷贝对象 31 /// 表达式目录树Lambda 32 private static Func _DeepClone(TIn tIn) 33 { 34 TOut tOut = new TOut(); 35 ParameterExpression parameterExpression = Expression.Parameter(typeof(TIn), "t"); 36 Expression > expression = Expression.Lambda >(_DeepClone(tIn, tOut, parameterExpression), new ParameterExpression[] { parameterExpression }); 37 return expression.Compile(); 38 } 39 40 private static MemberInitExpression _DeepClone(object t1, object t2, Expression parameterExpression) 41 { 42 Type typeT1 = t1.GetType(); 43 Type typeT2 = t2.GetType(); 44 List memberList = new List (); 45 //属性部分 46 foreach (PropertyInfo propertie in typeT2.GetProperties()) 47 { 48 PropertyInfo propertieT1 = typeT1.GetProperty(propertie.Name); 49 if (propertieT1 != null && propertieT1.GetValue(t1) != null) 50 { 51 MethodInfo mSet = typeT2.GetMethod("set_" + propertie.Name, new Type[] { propertie.PropertyType }); 52 if (!propertieT1.GetValue(t1).GetType().IsValueType && propertieT1.GetValue(t1).GetType().Name != "String")//引用类型(不包括string) 53 { 54 memberList.Add(Expression.Bind(mSet, _DeepClone(propertieT1.GetValue(t1), propertieT1.GetValue(t1), Expression.Property(parameterExpression, propertieT1)))); 55 } 56 else 57 { 58 memberList.Add(Expression.Bind(mSet, Expression.Property(parameterExpression, propertieT1))); 59 } 60 } 61 else 62 { 63 FieldInfo fieldT1 = typeT1.GetField(propertie.Name);//可能t2中的属性对应t1中的字段 64 if (fieldT1 != null && fieldT1.GetValue(t1) != null) 65 { 66 MethodInfo mSet = typeT2.GetMethod("set_" + propertie.Name, new Type[] { propertie.PropertyType }); 67 if (!fieldT1.GetValue(t1).GetType().IsValueType && fieldT1.GetValue(t1).GetType().Name != "String")//引用类型(不包括string) 68 { 69 memberList.Add(Expression.Bind(mSet, _DeepClone(fieldT1.GetValue(t1), fieldT1.GetValue(t1), Expression.Field(parameterExpression, fieldT1)))); 70 } 71 else 72 { 73 memberList.Add(Expression.Bind(mSet, Expression.Field(parameterExpression, fieldT1))); 74 } 75 } 76 } 77 } 78 //字段部分 79 foreach (FieldInfo field in typeT2.GetFields()) 80 { 81 FieldInfo fieldT1 = typeT1.GetField(field.Name); 82 if (fieldT1 != null && fieldT1.GetValue(t1) != null) 83 { 84 if (!fieldT1.GetValue(t1).GetType().IsValueType && fieldT1.GetValue(t1).GetType().Name != "String")//引用类型(不包括string) 85 { 86 memberList.Add(Expression.Bind(field, _DeepClone(fieldT1.GetValue(t1), fieldT1.GetValue(t1), Expression.Field(parameterExpression, fieldT1)))); 87 } 88 else 89 { 90 memberList.Add(Expression.Bind(field, Expression.Field(parameterExpression, fieldT1))); 91 } 92 } 93 else 94 { 95 PropertyInfo propertieT1 = typeT1.GetProperty(field.Name); 96 if (propertieT1 != null && propertieT1.GetValue(t1) != null)//可能t2中的字段对应t1中的属性 97 { 98 if (!propertieT1.GetValue(t1).GetType().IsValueType && propertieT1.GetValue(t1).GetType().Name != "String") //引用类型(不包括string) 99 { 100 memberList.Add(Expression.Bind(field, _DeepClone(propertieT1.GetValue(t1), propertieT1.GetValue(t1), Expression.Property(parameterExpression, propertieT1)))); 101 } 102 else 103 { 104 memberList.Add(Expression.Bind(field, Expression.Property(parameterExpression, propertieT1))); 105 } 106 } 107 } 108 } 109 MemberInitExpression body = Expression.MemberInit( 110 Expression.New(typeT2) 111 , memberList.ToArray() 112 ); 113 return body; 114 } 115 }
当有这个封装之后,只需要如下写,会自动拼装出想要的表达式返回想要的结果
Cat c3 = ExpressionCloneObject.Invoke(new Tiger { Name = "Tiger", Colour = "黄色" });