表达式目录树,在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<Func<Dog, Cat>> exp = d => new Cat { Name = d.Name, Colour = d.Colour };

把我家的猫也取相同名字,也染毛色:

Cat c = exp.Compile().Invoke(dog);

借助ILSpy反编译工具,查看表达式Expression<Func<Dog, Cat>> exp = d => new Cat { Name = d.Name, colour = d.Colour };反编译生产的代码

ParameterExpression parameterExpression = Expression.Parameter(typeof(Dog), "d");
    Expression<Func<Dog, Cat>> expression = Expression.Lambda<Func<Dog, Cat>>(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<Func<Dog, Cat>> expression = Expression.Lambda<Func<Dog, Cat>>(body
25 , new ParameterExpression[]
26   {
27       parameterExpression
28   }
29 );
30 
31  Cat c2 = expression.Compile().Invoke(dog);//编译表达式树由描述为可执行代码的 lambda 表达式,并生成一个委托执行

到这里,很多人会有疑问,你都自己写了表达式目录树了,还手动拼装一个一模一样的?而且手动拼装这么麻烦?有什么作用?往下看例子

有天去动物园,看见一只老虎,于是我又想到邻居的狗和我家的猫...于是乎我又想把自家的猫当做老虎了...于是,又写了下面这条表达式

Expression<Func<Tiger, Cat>> exp = d => new Cat { Name = d.Name, Colour = d.Colour };

但是这样子不对啊?难道我每次换个类型,都要写一条表达式吗?这样明显是不合适的,所以我们需要封装一个方法,动态拼装出两个对象属性赋值的完整表达式,这个封装就不详细写了,上面已经写了如何去拼装一个表达式了,虽然只有部分,更多的可以自己写个复杂的表达式查看反编译代码。

这里用到泛型类作为缓存,否则此种写法没意义,还不如直接用反射来写。

单层拷贝:

c# 表达式目录树拷贝对象(根据对象类型动态生成表达式目录树)
 1 /// <summary>
 2     /// 动态生成表达式目录树方式 浅拷贝 对象
 3     /// 采用泛型类作为静态缓存,即<TIn,TOut>同一类型只会生成一次表达式目录树。(如果不缓存,和直接存反射没啥区别,不如直接用纯反射的方式深拷贝)
 4     /// </summary>
 5     /// <typeparam name="TIn"></typeparam>
 6     /// <typeparam name="TOut"></typeparam>
 7     public class ExpressionCloneObject<TIn,TOut>
 8     {
 9         private static Func<TIn, TOut> _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<TIn, TOut> _CloneFunc()
22         {
23             Type typeT1 = typeof(TIn);
24             Type typeT2 = typeof(TOut);
25             ParameterExpression parameterExpression = Expression.Parameter(typeT1, "t");
26             List<MemberBinding> memberList = new List<MemberBinding>();
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<Func<TIn, TOut>> expression = Expression.Lambda<Func<TIn, TOut>>(body, new ParameterExpression[] { parameterExpression });
68 
69             return expression.Compile();
70         }
71     }
View Code

相关文章: