C# 使用Emit深克隆
原文:C# 使用Emit深克隆 有人问,复制一个类所有属性到另一个类有多少方法?这也就是问深克隆有多少个方法,容易想的有三个。直接复制,反射复制,序列化复制。但是性能比较快的有表达式树复制 IL复制两个,本文主要讲最后一个 关于表达式树复制,参见 Fast Deep Copy by Expression Trees (C#) - CodeProject 在开始读本文之前,我推荐两个博客 读懂IL代码就这么简单 (一) - Zery - 博客园 秒懂C#通过Emit动态生成代码 - 匠心十年 - 博客园 需要先知道一点IL的,后面才比较容易说,假设大家知道了 IL 是什么, 知道了简单的 IL 如何写,那么开始进行功能的开发。第一步是命名,因为需要把一个类的所有属性复制到另一个类,需要调用方法,而方法需要名字,所以第一步就是命名。 为了创建方法 创建方法的第一个参数很容易看到,我就不解释了,第二个参数就是方法的返回值,因为返回是 void 所以不用写。第三个参数是函数的参数,只需要使用类型,如果有多个参数就是写数组,如果这里发现有看不懂的,请和我说。 但是定义方法后需要写方法内的代码,这时需要使用 ILGenerator ,使用他的 Emit 方法,这个方法的速度很快,使用的时候需要知道 IL 的,如果不知道,没关系,我接下来会仔细说。 需要获得类型的所有属性,虽然这里用了反射,但是只是用一次,因为这里用反射获得方法是在写IL代码,写完可以很多次使用,可能第一次的速度不快,但是之后的速度和自己写代码编译的速度是差不多,所以建议使用这个方法。可以自己去使用 dot trace 去查看性能,我自己看到的是性能很好。 拿出所有属性可以读写的代码 查看 IL 需要先把第一个参数放在左边,第二个参数放在右边,调用第二个参数的 get 设置第一个参数的set对应的属性看起来的正常代码就是 这里的 foo 就是拿到一个属性,随意写的,写出来的 IL 请看下面。 可以从上面的代码 callvirt 使用一个方法,对应压入参数,所以可以通过反射获得方法,然后调用这个方法,于是写成代码请看下面 因为可以把这个拿出转化方法,于是所以的下面给所有代码 如果测试了这个方法,那么会发现,这个方法对于这个方法不可以见的类就会出现 之外,对于静态属性,使用上面代码也是会出错,因为静态的属性的访问没有权限,所以请看修改后的。 需要注意,这里的复制只是复制类的属性,对类的属性内是没有进行复制。如果存在类型 TestA1 ,请看下面代码。 那么在执行下面的代码之后,得到的 TestA1 是相同的。 那么上面的代码在什么时候可以使用?实际如果在一个创建的类需要复制基类的属性,那么使用这个方法是很好,例如在 Model 会创建一些类,而在 ViewModel 有时候需要让这些类添加一些属性,如 例如基类是 如果需要复制一个类到一个新类,可以使用这个代码 http://www.c-sharpcorner.com/uploadfile/puranindia/reflection-and-reflection-emit-in-C-Sharp/ 本文会经常更新,请阅读原文:
public void Clone<T>(T source, T los) 我就使用了下面代码 var dynamicMethod = new DynamicMethod("Clone", null, new[] { typeof(T), typeof(T) });
ILGenerator generator = dynamicMethod.GetILGenerator();
foreach (var temp in typeof(T).GetProperties().Where(temp=>temp.CanRead&&temp.CanWrite))los.foo=source.foo;
Ldarg_1 //los
Ldarg_0 //s
callvirt instance string lindexi.Foo::get_Name()
callvirt instance void lindexi.Foo::set_Name(string)
ret
generator.Emit(OpCodes.Ldarg_1);// los
generator.Emit(OpCodes.Ldarg_0);// s
generator.Emit(OpCodes.Callvirt,temp.GetMethod);
generator.Emit(OpCodes.Callvirt, temp.SetMethod);
private static void CloneObjectWithIL<T>(T source, T los)
{
var dynamicMethod = new DynamicMethod("Clone", null, new[] { typeof(T), typeof(T) });
ILGenerator generator = dynamicMethod.GetILGenerator();
foreach (var temp in typeof(T).GetProperties().Where(temp=>temp.CanRead&&temp.CanWrite))
{
generator.Emit(OpCodes.Ldarg_1);// los
generator.Emit(OpCodes.Ldarg_0);// s
generator.Emit(OpCodes.Callvirt,temp.GetMethod);
generator.Emit(OpCodes.Callvirt, temp.SetMethod);
}
generator.Emit(OpCodes.Ret);
var clone = (Action<T, T>) dynamicMethod.CreateDelegate(typeof(Action<T, T>));
clone(source, los);
}
MethodAccessException,所以传入的类需要这个方法可以直接用。//A.dll
public class Foo
{
}
CloneObjectWithIL(foo1,foo2);
//B.dll
private static void CloneObjectWithIL<T>(T source, T los)
这时无法使用
/// <summary>
/// 提供快速的对象深复制
/// </summary>
public static class Clone
{
/// <summary>
/// 提供使用 IL 的方式快速对象深复制
/// 要求本方法具有T可访问
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source">源</param>
/// <param name="los">从源复制属性</param>
/// <exception cref="MethodAccessException">如果输入的T没有本方法可以访问,那么就会出现这个异常</exception>
// ReSharper disable once InconsistentNaming
public static void CloneObjectWithIL<T>(T source, T los)
{
//参见 [http://lindexi.oschina.io/lindexi/post/C-%E4%BD%BF%E7%94%A8Emit%E6%B7%B1%E5%85%8B%E9%9A%86/](http://lindexi.oschina.io/lindexi/post/C-%E4%BD%BF%E7%94%A8Emit%E6%B7%B1%E5%85%8B%E9%9A%86/ )
if (CachedIl.ContainsKey(typeof(T)))
{
((Action<T, T>) CachedIl[typeof(T)])(source, los);
return;
}
var dynamicMethod = new DynamicMethod("Clone", null, new[] { typeof(T), typeof(T) });
ILGenerator generator = dynamicMethod.GetILGenerator();
foreach (var temp in typeof(T).GetProperties().Where(temp => temp.CanRead && temp.CanWrite))
{
//不复制静态类属性
if (temp.GetAccessors(true)[0].IsStatic)
{
continue;
}
generator.Emit(OpCodes.Ldarg_1);// los
generator.Emit(OpCodes.Ldarg_0);// s
generator.Emit(OpCodes.Callvirt, temp.GetMethod);
generator.Emit(OpCodes.Callvirt, temp.SetMethod);
}
generator.Emit(OpCodes.Ret);
var clone = (Action<T, T>) dynamicMethod.CreateDelegate(typeof(Action<T, T>));
CachedIl[typeof(T)] = clone;
clone(source, los);
}
private static Dictionary<Type, Delegate> CachedIl { set; get; } = new Dictionary<Type, Delegate>();
}
public class TestA1
{
public string Name { get; set; }
}
public class Foo
{
public string Name { get; set; }
public TestA1 TestA1 { get; set; }
}
var foo = new Foo()
{
Name = "123",
TestA1 = new TestA1()
{
Name = "123"
}
};
var foo1 = new Foo();
Clone.CloneObjectWithIL(foo, foo1);
foo1.TestA1.Name == foo.TestA1.Name
foo.Name = "";
foo.TestA1.Name = "lindexi";
foo1.TestA1.Name == foo.TestA1.Name
Checked ,那么需要重新复制 Model 的属性,如果一个个需要自己写属性复制,那么开发速度太慢。所以这时候可以使用这个方法。Base ,继承类是Derived ,请看下面代码public class Base
{
public string BaseField;
}
public class Derived : Base
{
public string DerivedField;
}
Base base = new Base();
//some alother code
Derived derived = new Derived();
CloneObjectWithIL(base, derived);
private static T CloneObjectWithIL<T>(T myObject)
{
Delegate myExec = null;
if (!_cachedIL.TryGetValue(typeof(T), out myExec))
{
// Create ILGenerator
DynamicMethod dymMethod = new DynamicMethod("DoClone", typeof(T), new Type[] { typeof(T) }, true);
ConstructorInfo cInfo = myObject.GetType().GetConstructor(new Type[] { });
ILGenerator generator = dymMethod.GetILGenerator();
LocalBuilder lbf = generator.DeclareLocal(typeof(T));
//lbf.SetLocalSymInfo("_temp");
generator.Emit(OpCodes.Newobj, cInfo);
generator.Emit(OpCodes.Stloc_0);
foreach (FieldInfo field in myObject.GetType().GetFields(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic))
{
// Load the new object on the eval stack... (currently 1 item on eval stack)
generator.Emit(OpCodes.Ldloc_0);
// Load initial object (parameter) (currently 2 items on eval stack)
generator.Emit(OpCodes.Ldarg_0);
// Replace value by field value (still currently 2 items on eval stack)
generator.Emit(OpCodes.Ldfld, field);
// Store the value of the top on the eval stack into the object underneath that value on the value stack.
// (0 items on eval stack)
generator.Emit(OpCodes.Stfld, field);
}
// Load new constructed obj on eval stack -> 1 item on stack
generator.Emit(OpCodes.Ldloc_0);
// Return constructed object. --> 0 items on stack
generator.Emit(OpCodes.Ret);
myExec = dymMethod.CreateDelegate(typeof(Func<T, T>));
_cachedIL.Add(typeof(T), myExec);
}
return ((Func<T, T>)myExec)(myObject);
}

https://lindexi.gitee.io/lindexi/post/C-%E4%BD%BF%E7%94%A8Emit%E6%B7%B1%E5%85%8B%E9%9A%86.html
,以避免陈旧错误知识的误导,同时有更好的阅读体验。
本作品采用
知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议
进行许可。欢迎转载、使用、重新发布,但务必保留文章署名林德熙(包含链接:
https://lindexi.gitee.io
),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请
与我联系
。
C# 使用Emit深克隆的更多相关文章
- 2018-8-10-C#-使用Emit深克隆
title author date CreateTime categories C# 使用Emit深克隆 lindexi 2018-08-10 19:16:52 +0800 2018-2-13 17: ...
- dotnet 从入门到放弃的 500 篇文章合集
本文是记录我从入门到放弃写的博客 博客包括 C#.WPF.UWP.dotnet core .git 和 VisualStudio 和一些算法,所有博客使用 docx 保存 下载:dotnet 从入门到 ...
- 2018-2-13-wpf-绑定-TextLength-
title author date CreateTime categories wpf 绑定 TextLength lindexi 2018-2-13 17:23:3 +0800 2018-2-13 ...
- 2018-8-10-dotnet-从入门到放弃的-500-篇文章合集
title author date CreateTime categories dotnet 从入门到放弃的 500 篇文章合集 lindexi 2018-08-10 19:16:52 +0800 2 ...
- 自用的基于Emit的C#下DataTable转实体类方法
之前一直在做WebForm的开发,数据绑定时直接DataTable绑定Gridview很方便,但是最近开始往MVC转,数据列表的传递和页面展示基本上是以List为主,像下面这样,遍历实体类的各个字段去 ...
- angularjs $emit $on $broadcast 父子 兄弟之间传值
父子之间 <div ng-controller="ParentCtrl"> <div ng-controller="ChildCtrl"> ...
- angularjs $broadcast $emit $on 事件触发controller间的值传递
如何在作用域之间通信呢? 1.创建一个单例服务,然后通过这个服务处理所有子作用域的通信. 2.通过作用域中的事件处理通信.但是这种方法有一些限制:例如,你并不能广泛的将事件传播到所有监控的作用域中.你 ...
- 基础才是重中之重~Emit动态构建方法(参数和返回值)
回到目录 对于Emit我们知道它的可以动态构建程序集,类型,方法,属性等,或者说只要手动使用C#创建的东西使用Emit也都可以动态创建它们,Emit由于它的特别之处,所以在很多领域得到了广泛的应用,像 ...
- 再谈使用Emit把Datatable转换为对象集合(List<T>)
一.前因和存在的问题 前面我写了一篇<使用Emit把Datatable转换为对象集合(List<T>)>的博文,其实起源于我自己编写的一个orm工具(见前面几篇博文有介绍),里 ...
随机推荐
- vue使用(三)
本节目标:获取后端api数据 需求:一个按钮,点击之后将服务器上的数据获取到,并显示出来 方法一: 1. 准备工作, (1)安装官方插件 vuedemo02>npm install vue-re ...
- JS预解释的总结
预解释阶段发生在创建了堆内存,让代码执行之前,对当前作用域中带var和function的进行预解释 在浏览器解析执行代码的时候,会提前把带var和function的代码声明或定义,提前放在作用域的最前 ...
- JS里的函数的call()与back()方法
function cat(){} cat.prototype={ food:"fish", say: function(){ alert("I love "+t ...
- AspJpeg2.0组件教程完整版 aspjpeg教程...
AspJpeg是一款功能强大的基于Microsoft IIS环境的图片处理组件,网络上对其进行详细和深入介绍的中文文章并不多,即使有一般也只是牵涉到图片缩略图和图片水印,这与其为英文版本有着密切的关系 ...
- Oracle中暂时表空间的清理
作者:iamlaosong Oracle暂时表空间主要用来做查询和存放一些缓冲区数据. 暂时表空间消耗的主要原因是须要对查询的中间结果进行排序.暂时表空间的主要作用: 索引create或rebuild ...
- 10.5 android输入系统_Reader线程_使用EventHub读取事件和核心类及配置文件_实验_分析
4. Reader线程_使用EventHub读取事件 使用inotify监测/dev/input下文件的创建和删除 使用epoll监测有无数据上报 细节: a.fd1 = inotify_init(& ...
- WCF 设计和实现服务协定(01)
作者:jiankunking 出处:http://blog.csdn.net/jiankunking WCF 术语: • 消息 – 消息是一个独立的数据单元,它可能由几个部分组成,包含消息正文和消息头 ...
- [Angular] Show a loading indicator in Angular using *ngIf/else, the as keyword and the async pipe
The network may be unreliable and loading data may take time. Thus it is important to give the user ...
- php面试题5
php面试题5 一.总结 二.php面试题5 1. 什么事面向对象?主要特征是什么?1) 面向对象是程序的一种设计方式,它利于提高程序的重用性,是程序结构更加清晰.2) 主要特征:封装.继承.多态 2 ...
- POJ 1751 Highways (ZOJ 2048 ) MST
http://poj.org/problem?id=1751 http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=2048 题目大 ...