title author date CreateTime categories
C# 使用Emit深克隆
lindexi
2018-08-10 19:16:52 +0800
2018-2-13 17:23:3 +0800
C# dotnet Emit

有人问,复制一个类所有属性到另一个类有多少方法?这也就是问深克隆有多少个方法,容易想的有三个。直接复制,反射复制,序列化复制。但是性能比较快的有表达式树复制 IL复制两个,本文主要讲最后一个

关于表达式树复制,参见 Fast Deep Copy by Expression Trees (C#) - CodeProject

在开始读本文之前,我推荐两个博客 读懂IL代码就这么简单 (一) - Zery - 博客园 秒懂C#通过Emit动态生成代码 - 匠心十年 - 博客园

需要先知道一点IL的,后面才比较容易说,假设大家知道了 IL 是什么, 知道了简单的 IL 如何写,那么开始进行功能的开发。第一步是命名,因为需要把一个类的所有属性复制到另一个类,需要调用方法,而方法需要名字,所以第一步就是命名。

为了创建方法 public void Clone<T>(T source, T los) 我就使用了下面代码

            var dynamicMethod = new DynamicMethod("Clone", null, new[] { typeof(T), typeof(T) });

创建方法的第一个参数很容易看到,我就不解释了,第二个参数就是方法的返回值,因为返回是 void 所以不用写。第三个参数是函数的参数,只需要使用类型,如果有多个参数就是写数组,如果这里发现有看不懂的,请和我说。

但是定义方法后需要写方法内的代码,这时需要使用 ILGenerator ,使用他的 Emit 方法,这个方法的速度很快,使用的时候需要知道 IL 的,如果不知道,没关系,我接下来会仔细说。

            ILGenerator generator = dynamicMethod.GetILGenerator();

需要获得类型的所有属性,虽然这里用了反射,但是只是用一次,因为这里用反射获得方法是在写IL代码,写完可以很多次使用,可能第一次的速度不快,但是之后的速度和自己写代码编译的速度是差不多,所以建议使用这个方法。可以自己去使用 dot trace 去查看性能,我自己看到的是性能很好。

拿出所有属性可以读写的代码foreach (var temp in typeof(T).GetProperties().Where(temp=>temp.CanRead&&temp.CanWrite))

查看 IL 需要先把第一个参数放在左边,第二个参数放在右边,调用第二个参数的 get 设置第一个参数的set对应的属性看起来的正常代码就是

los.foo=source.foo;

这里的 foo 就是拿到一个属性,随意写的,写出来的 IL 请看下面。

Ldarg_1 //los
Ldarg_0 //s
callvirt instance string lindexi.Foo::get_Name()
callvirt instance void lindexi.Foo::set_Name(string)
ret

可以从上面的代码 callvirt 使用一个方法,对应压入参数,所以可以通过反射获得方法,然后调用这个方法,于是写成代码请看下面

                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/
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>();
}

需要注意,这里的复制只是复制类的属性,对类的属性内是没有进行复制。如果存在类型 TestA1 ,请看下面代码。

        public class TestA1
{
public string Name { get; set; }
}

那么在执行下面的代码之后,得到的 TestA1 是相同的。

        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

那么上面的代码在什么时候可以使用?实际如果在一个创建的类需要复制基类的属性,那么使用这个方法是很好,例如在 Model 会创建一些类,而在 ViewModel 有时候需要让这些类添加一些属性,如 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);
}

http://www.c-sharpcorner.com/uploadfile/puranindia/reflection-and-reflection-emit-in-C-Sharp/

https://stackoverflow.com/a/46580446/6116637

2018-8-10-C#-使用Emit深克隆的更多相关文章

  1. 申请Office 365一年免费的开发者账号攻略(2018年10月份版本)

    要进行Office 365开发,当然需要有完整的Office 365环境才可以.为了便于广大开发人员快速地启动这项工作,微软官方给所有开发人员提供了免费的一年开发者账号   那么如何申请Office ...

  2. IntelliJ IDEA 最新激活码(截止到2018年10月14日)

    IntelliJ IDEA 注册码: EB101IWSWD-eyJsaWNlbnNlSWQiOiJFQjEwMUlXU1dEIiwibGljZW5zZWVOYW1lIjoibGFuIHl1IiwiYX ...

  3. 新手C#SQL Server使用记录2018.08.10

    主键(PrimaryKey):主键就是每个数据行(记录)的唯一标识,不会有重复值的列(字段)才能当做主键.一个表可以没有主键,但是这样会很难处理表,因此一般情况表都要设置主键. 主键有两张选用策略,分 ...

  4. 01 mybatis框架整体概况(2018.7.10)-

    01 mybatis框架整体概况(2018.7.10)- F:\廖雪峰 JavaEE 企业级分布式高级架构师课程\廖雪峰JavaEE一期\第一课(2018.7.10) maven用的是3.39的版本 ...

  5. 北京化工大学2018年10月程序设计竞赛部分题解(A,C,E,H)

    目录 北京化工大学2018年10月程序设计竞赛部分题解(A,C,E,H) 竞赛事件相关 竞赛链接 竞赛题目 总结 北京化工大学2018年10月程序设计竞赛部分题解(A,C,E,H) 竞赛事件相关 竞赛 ...

  6. 富士康的盈利秒杀99%的A股公司:3星|《三联生活周刊》2018年10期

    三联生活周刊·最美的数学:天才为何成群到来(2018年10期) 本期专题是数学和成都,我都跳过去没看.其他内容也还有点意思. 总体评价3星. 以下是本期一些内容的摘抄,#号后面是kindle电子版中的 ...

  7. Burn Down Chart(2018.6.4~2018.6.10)

    Burn Down Chart (2018.6.4~2018.6.10) 娄雨禛[前端部分] 曾子轩[后端部分+燃尽图] 前端 1. 娄雨禛+李鑫 1)在总工程中完成跳转,实现图片显示,并发布到Git ...

  8. C# 使用Emit深克隆

    原文:C# 使用Emit深克隆 有人问,复制一个类所有属性到另一个类有多少方法?这也就是问深克隆有多少个方法,容易想的有三个.直接复制,反射复制,序列化复制.但是性能比较快的有表达式树复制 IL复制两 ...

  9. Java分布式互联网架构/微服务/高性能/springboot/springcloud 2018年10月17日直播内容

    2018年10月17日直播内容 大规模并发必备的消息中间件技术ActiveMq 网盘链接: https://pan.baidu.com/s/1GlxsZ2JnrvX- YN16-S7lQw 提取码: ...

随机推荐

  1. idea 项目热部署设置

    1.引入pom.xml() <!-- 热部署(必须) --> <dependency> <groupId>org.springframework.boot</ ...

  2. Directx11教程(45) alpha blend(2)

    原文:Directx11教程(45) alpha blend(2)     在myTutorialD3D11_40中,我们在场景中再添加一个box,并把box放在水里,实现半透明的效果.如下图所示: ...

  3. 2019-8-31-C#-标准性能测试高级用法

    title author date CreateTime categories C# 标准性能测试高级用法 lindexi 2019-08-31 16:55:58 +0800 2018-07-08 0 ...

  4. oracle如何利用hostname方式连接数据库

    host name方式只支持tcp/ip协议的小局域网 修改listener.ora中的如下信息 (SID_DESC = (GLOBAL_DBNAME = ur_hostname) --你的机器名 ( ...

  5. 封装好的PDO类

    封装PDO类,方便使用: <?php header('content-type:text/html;charset=utf-8'); /** * 封装PDODB类 */ // 加载接口 // i ...

  6. 五.获得MYSQL数据库自动生成的主键

    测试脚本如下: 1 create table test1 2 ( 3 id int primary key auto_increment, 4 name varchar(20) 5 ); 测试代码: ...

  7. KiCad 5.1.0 镜像圆弧后错位问题

    KiCad 5.1.0 镜像圆弧后错位问题 看官方回复这个问题将在 5.1.3 进行修复,因为这段时间在举行 KiCon 活动. 看到这个问题并不是非常严重,不是致命的,所以已经从 5.1.0 跳到 ...

  8. 关于inflate的第3个参数

    关于inf 方法 inflate(int resource, ViewGroup root, boolean attachToRoot) 中,前连个参数都好理解,我比较费解的是第3个参数. 文档中的解 ...

  9. 通过反射拿到构造方法 Day25

    package com.sxt.constructor; /* * 反射 * Class类拿到构造方法 */ import java.lang.reflect.Constructor; public ...

  10. Java练习 SDUT-1200_汉诺塔

    汉诺塔 Time Limit: 1000 ms Memory Limit: 65536 KiB Problem Description 汉诺塔(又称河内塔)问题是印度的一个古老的传说. 开天辟地的神勃 ...