表达式目录树,在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方法,整理之后代码如下:

 ParameterExpression parameterExpression = Expression.Parameter(typeof(Dog), "d"); //定义一个变量

 Type typeDog = typeof(Dog);
MethodInfo getName = typeDog.GetMethod("get_Name"); //获取get_Name()方法
MethodInfo getColour = typeDog.GetMethod("get_Colour"); //获取get_Colour()方法
Type typeCat = typeof(Cat);
MethodInfo setName = typeCat.GetMethod("set_Name"); //获取set_Name()方法
MethodInfo setColour = typeCat.GetMethod("set_Colour"); //获取set_Colour()方法 MemberExpression dName = Expression.Property(parameterExpression, getName);//d.Name
MemberAssignment name_dName = Expression.Bind(setName, dName);//Name = d.Name
MemberExpression dColour = Expression.Property(parameterExpression, getColour);//d.Colour
MemberAssignment colour_dColour = Expression.Bind(setColour, dColour);//Colour = d.Colour //new Cat { Name = d.Name, Colour = d.Colour }; 表达式主体完成
MemberInitExpression body = Expression.MemberInit(Expression.New(typeof(Cat)),
new MemberBinding[]
{
name_dName,
colour_dColour
});
//转换为表达式
Expression<Func<Dog, Cat>> expression = Expression.Lambda<Func<Dog, Cat>>(body
, new ParameterExpression[]
{
parameterExpression
}
); Cat c2 = expression.Compile().Invoke(dog);//编译表达式树由描述为可执行代码的 lambda 表达式,并生成一个委托执行

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

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

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

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

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

浅拷贝:

 /// <summary>
/// 动态生成表达式目录树方式 浅拷贝 对象
/// 采用泛型类作为静态缓存,即<TIn,TOut>同一类型只会生成一次表达式目录树。(如果不缓存,和直接存反射没啥区别,不如直接用纯反射的方式深拷贝)
/// </summary>
/// <typeparam name="TIn"></typeparam>
/// <typeparam name="TOut"></typeparam>
public class ExpressionCloneObject<TIn,TOut>
{
private static Func<TIn, TOut> _FUNC = null; static ExpressionCloneObject()
{
_FUNC = _CloneFunc();
} public static TOut Invoke(TIn t)
{
return _FUNC.Invoke(t);
} private static Func<TIn, TOut> _CloneFunc()
{
Type typeT1 = typeof(TIn);
Type typeT2 = typeof(TOut);
ParameterExpression parameterExpression = Expression.Parameter(typeT1, "t");
List<MemberBinding> memberList = new List<MemberBinding>();
//属性
foreach (PropertyInfo propertie in typeT2.GetProperties())
{
PropertyInfo propertieT1 = typeT1.GetProperty(propertie.Name);
if (propertieT1 != null)
{
MethodInfo mSet = typeT2.GetMethod("set_" + propertie.Name, new Type[] { propertie.PropertyType });
memberList.Add(Expression.Bind(mSet, Expression.Property(parameterExpression, propertieT1)));
}
else
{
FieldInfo fieldT1 = typeT1.GetField(propertie.Name);//可能T2中的属性对应T1中的字段
if (fieldT1 != null)
{
MethodInfo mSet = typeT2.GetMethod("set_" + propertie.Name, new Type[] { propertie.PropertyType });
memberList.Add(Expression.Bind(mSet, Expression.Field(parameterExpression, fieldT1)));
}
}
}
//字段
foreach (FieldInfo field in typeT2.GetFields())
{
FieldInfo fieldT1 = typeT1.GetField(field.Name);
if (fieldT1 != null)
{
memberList.Add(Expression.Bind(field, Expression.Field(parameterExpression, fieldT1)));
}
else
{
PropertyInfo propertieT1 = typeT1.GetProperty(field.Name);
if (propertieT1 != null)//可能T2中的字段对应T1中的属性
{
memberList.Add(Expression.Bind(field, Expression.Property(parameterExpression, propertieT1)));
}
}
}
MemberInitExpression body = Expression.MemberInit(
Expression.New(typeT2)
, memberList.ToArray()
);
Expression<Func<TIn, TOut>> expression = Expression.Lambda<Func<TIn, TOut>>(body, new ParameterExpression[] { parameterExpression }); return expression.Compile();
}
}

深拷贝:

 /// <summary>
///
/// 动态生成表达式目录树方式 深拷贝 对象
/// 采用泛型类作为静态缓存,即<TIn,TOut>同一类型只会生成一次表达式目录树。(如果不缓存,和直接存反射没啥区别,不如直接用纯反射的方式拷贝)
/// (注:对象的属性 不支持数组或集合,原因是对象中的数组或集合的个数可能不一样
/// ,并且这里用泛型缓存,所以以第一次的数组或集合生成的表达式目录树会缓存起来,下次直接拷贝对象如果集合中的个数和缓存中的不一致
/// 会导致拷贝结果不正确。解决方法可以先拷贝属性中的集合,再赋值回到此方法拷贝出来的对象上。或者换种深拷贝方法,深拷贝方式很多,如
/// 序列化、存反射、AutoMapper等等)
/// </summary>
/// <typeparam name="TIn"></typeparam>
/// <typeparam name="TOut"></typeparam>
public class ExpressionDeepCloneObject<TIn,TOut> where TOut : class, new()
{
private static Func<TIn, TOut> _FUNC = null; public static TOut Invoke(TIn t)
{
if(_FUNC == null)
{
_FUNC = _DeepClone(t);
}
return _FUNC.Invoke(t);
} /// <summary>
/// 表达式目录树深拷贝
/// </summary>
/// <typeparam name="TIn">被拷贝对象类型</typeparam>
/// <typeparam name="TOut">新对象类型</typeparam>
/// <param name="tIn">被拷贝对象</param>
/// <returns>表达式目录树Lambda</returns>
private static Func<TIn, TOut> _DeepClone(TIn tIn)
{
TOut tOut = new TOut();
ParameterExpression parameterExpression = Expression.Parameter(typeof(TIn), "t");
Expression<Func<TIn, TOut>> expression = Expression.Lambda<Func<TIn, TOut>>(_DeepClone(tIn, tOut, parameterExpression), new ParameterExpression[] { parameterExpression });
return expression.Compile();
} private static MemberInitExpression _DeepClone(object t1, object t2, Expression parameterExpression)
{
Type typeT1 = t1.GetType();
Type typeT2 = t2.GetType();
List<MemberBinding> memberList = new List<MemberBinding>();
//属性部分
foreach (PropertyInfo propertie in typeT2.GetProperties())
{
PropertyInfo propertieT1 = typeT1.GetProperty(propertie.Name);
if (propertieT1 != null && propertieT1.GetValue(t1) != null)
{
MethodInfo mSet = typeT2.GetMethod("set_" + propertie.Name, new Type[] { propertie.PropertyType });
if (!propertieT1.GetValue(t1).GetType().IsValueType && propertieT1.GetValue(t1).GetType().Name != "String")//引用类型(不包括string)
{
memberList.Add(Expression.Bind(mSet, _DeepClone(propertieT1.GetValue(t1), propertieT1.GetValue(t1), Expression.Property(parameterExpression, propertieT1))));
}
else
{
memberList.Add(Expression.Bind(mSet, Expression.Property(parameterExpression, propertieT1)));
}
}
else
{
FieldInfo fieldT1 = typeT1.GetField(propertie.Name);//可能t2中的属性对应t1中的字段
if (fieldT1 != null && fieldT1.GetValue(t1) != null)
{
MethodInfo mSet = typeT2.GetMethod("set_" + propertie.Name, new Type[] { propertie.PropertyType });
if (!fieldT1.GetValue(t1).GetType().IsValueType && fieldT1.GetValue(t1).GetType().Name != "String")//引用类型(不包括string)
{
memberList.Add(Expression.Bind(mSet, _DeepClone(fieldT1.GetValue(t1), fieldT1.GetValue(t1), Expression.Field(parameterExpression, fieldT1))));
}
else
{
memberList.Add(Expression.Bind(mSet, Expression.Field(parameterExpression, fieldT1)));
}
}
}
}
//字段部分
foreach (FieldInfo field in typeT2.GetFields())
{
FieldInfo fieldT1 = typeT1.GetField(field.Name);
if (fieldT1 != null && fieldT1.GetValue(t1) != null)
{
if (!fieldT1.GetValue(t1).GetType().IsValueType && fieldT1.GetValue(t1).GetType().Name != "String")//引用类型(不包括string)
{
memberList.Add(Expression.Bind(field, _DeepClone(fieldT1.GetValue(t1), fieldT1.GetValue(t1), Expression.Field(parameterExpression, fieldT1))));
}
else
{
memberList.Add(Expression.Bind(field, Expression.Field(parameterExpression, fieldT1)));
}
}
else
{
PropertyInfo propertieT1 = typeT1.GetProperty(field.Name);
if (propertieT1 != null && propertieT1.GetValue(t1) != null)//可能t2中的字段对应t1中的属性
{
if (!propertieT1.GetValue(t1).GetType().IsValueType && propertieT1.GetValue(t1).GetType().Name != "String") //引用类型(不包括string)
{
memberList.Add(Expression.Bind(field, _DeepClone(propertieT1.GetValue(t1), propertieT1.GetValue(t1), Expression.Property(parameterExpression, propertieT1))));
}
else
{
memberList.Add(Expression.Bind(field, Expression.Property(parameterExpression, propertieT1)));
}
}
}
}
MemberInitExpression body = Expression.MemberInit(
Expression.New(typeT2)
, memberList.ToArray()
);
return body;
}
}

当有这个封装之后,只需要如下写,会自动拼装出想要的表达式返回想要的结果

Cat c3 = ExpressionCloneObject<Tiger, Cat>.Invoke(new Tiger { Name = "Tiger", Colour = "黄色" });

c# 表达式目录树拷贝对象(根据对象类型动态生成表达式目录树)的更多相关文章

  1. 利用StringList对象来管理这些动态生成的对象

    如果程序需要动态创建大量的对象,那么我们可以利用StringList对象来管理这些动态生成的对象.1.创建StringList对象:OBJ := TStringList.Create; 2.保存动态生 ...

  2. 泛型方法动态生成表达式树 Expression

    public string GetGridJSON(TraderInfo model) { IQueryable<TraderInfo> Temp = db.TraderInfo; if ...

  3. LOJ2269 [SDOI2017] 切树游戏 【FWT】【动态DP】【树链剖分】【线段树】

    题目分析: 好题.本来是一道好的非套路题,但是不凑巧的是当年有一位国家集训队员正好介绍了这个算法. 首先考虑静态的情况.这个的DP方程非常容易写出来. 接着可以注意到对于异或结果的计数可以看成一个FW ...

  4. 表达式树扩展 动态生成表达式树插件 Sy.ExpressionBuilder。

    CURD中,基础查询我感觉还是很烦人的一个浪费时间的工作,我经历过远古时代的GetAll(string name,int age),这种方式写服务的时候真的是心中一万个草泥马飞过,后面逐渐的变成了传一 ...

  5. ahjesus动态生成表达式树

    直接上方法,看的懂的拿去用,看不懂的找资料看懂 , , Double floorprice = , Double topprice = , string brandstr = "" ...

  6. C#3.0新增功能10 表达式树 06 生成表达式

    连载目录    [已更新最新开发文章,点击查看详细] 到目前为止,你所看到的所有表达式树都是由 C# 编译器创建的. 你所要做的是创建一个 lambda 表达式,将其分配给一个类型为 Expressi ...

  7. LinqToDB 源码分析——生成表达式树

    当我们知道了Linq查询要用到的数据库信息之后.接下就是生成对应的表达式树.在前面的章节里面笔者就已经介绍过.生成表达式树是事实离不开IQueryable<T>接口.而处理表达式树离不开I ...

  8. [bzoj 3531][SDOI2014]旅行(树链剖分+动态开点线段树)

    题目:http://www.lydsy.com:808/JudgeOnline/problem.php?id=3531 分析: 对于每个颜色(颜色<=10^5)都建立一颗线段树 什么!那么不是M ...

  9. 有趣的线段树模板合集(线段树,最短/长路,单调栈,线段树合并,线段树分裂,树上差分,Tarjan-LCA,势能线段树,李超线段树)

    线段树分裂 以某个键值为中点将线段树分裂成左右两部分,应该类似Treap的分裂吧(我菜不会Treap).一般应用于区间排序. 方法很简单,就是把分裂之后的两棵树的重复的\(\log\)个节点新建出来, ...

随机推荐

  1. Centos7搭建FTP服务详细过程

    Centos7搭建FTP服务详细过程https://blog.csdn.net/sinat_30802291/article/details/81706152

  2. odoo开发笔记--一个模块显示两个一级菜单

    场景描述: 在已启动开发的模块中,odoo顶部一级菜单只有一个“会员管理”,需求是:在同一级顶部菜单,增加新菜单“产品管理”.举例如图:       处理方式: 按照odoo的机制,实现这种效果,可以 ...

  3. mysql监控工具sqlprofiler,类似sqlserver的profiler工具安装(一)

    最近无意发现了mysql的客户端监控工具“Nero Profile SQL”,刚开始还不知道怎么使用,经过半小时摸索,现将使用步骤写下来. 背景:开发的时候,如果数据存储层这块使用EF,或者其他orm ...

  4. cordova添加plugin的多种方式

    #在线安装 cordova create chankoujie com.example.chankoujie ChanKouJie cordova plugin add cordova-plugin- ...

  5. OpenShift环境中手工模式添加etcd server

    模拟备份和恢复,在现有的集群环境,单master(etcd), infra和node上面添加另外一台机器作为etcd Server. 基于OpenShift 3.11版本,详情可以参考 https:/ ...

  6. Nginx 反向代理 一个IP代理多个域名,不区分端口,类似windows虚拟机。

    简介: IP有限,所以我们以前使用端口来区分不同的虚拟主机,提供不同的WEB服务. 小范围还凑活,一旦规模扩大,地址记不住了吧?端口记不住了吧? 这个时候我们可以使用DNS,域名解析,毕竟记名字比记I ...

  7. ELK - nginx 日志分析及绘图

    1. 前言 先上一张整体的效果图: 上面这张图就是通过 ELK 分析 nginx 日志所得到的数据,通过 kibana 的功能展示出来的效果图.是不是这样对日志做了解析,想要知道的数据一目了然.接下来 ...

  8. Java设置文件权限

    今天遇到一个问题: java写的API,ppt转图片生成的目录及文件 在使用php调用API完成后,再使用php进行删除时,遇到了删除失败的问题(php删除的部分  查看) 部署的环境是Ubuntu ...

  9. Linq调试实时输出信息扩展方法(摘抄)

    原文在此 [译]如何在C#中调试LINQ查询 原linq语句: var res = employees .Where(e => e.Gender == "Male") .Ta ...

  10. Pytorch1.3源码解析-第一篇

    pytorch$ tree -L 1 . ├── android ├── aten ├── benchmarks ├── binaries ├── c10 ├── caffe2 ├── CITATIO ...