有人问,复制一个类全部属性到另外一个类有多少方法?这也就是问深克隆有多少个方法,容易想的有三个。直接复制,反射复制,序列化复制。可是性能比较快的有表达式树复制 IL复制两个,本文主要讲最后一个html
关于表达式树复制,参见 Fast Deep Copy by Expression Trees (C#) - CodeProjectgit
在开始读本文以前,我推荐两个博客 读懂IL代码就这么简单 (一) - Zery - 博客园 秒懂C#经过Emit动态生成代码 - 匠心十年 - 博客园数组
须要先知道一点IL的,后面才比较容易说,假设你们知道了 IL 是什么, 知道了简单的 IL 如何写,那么开始进行功能的开发。第一步是命名,由于须要把一个类的全部属性复制到另外一个类,须要调用方法,而方法须要名字,因此第一步就是命名。函数
为了建立方法 public void Clone<T>(T source, T los)
我就使用了下面代码post
var dynamicMethod = new DynamicMethod("Clone", null, new[] { typeof(T), typeof(T) });
建立方法的第一个参数很容易看到,我就不解释了,第二个参数就是方法的返回值,由于返回是 void 因此不用写。第三个参数是函数的参数,只须要使用类型,若是有多个参数就是写数组,若是这里发现有看不懂的,请和我说。性能
可是定义方法后须要写方法内的代码,这时须要使用 ILGenerator ,使用他的 Emit 方法,这个方法的速度很快,使用的时候须要知道 IL 的,若是不知道,不要紧,我接下来会仔细说。测试
ILGenerator generator = dynamicMethod.GetILGenerator();
须要得到类型的全部属性,虽然这里用了反射,可是只是用一次,由于这里用反射得到方法是在写IL代码,写完能够不少次使用,可能第一次的速度不快,可是以后的速度和本身写代码编译的速度是差很少,因此建议使用这个方法。能够本身去使用 dot trace 去查看性能,我本身看到的是性能很好。ui
拿出全部属性能够读写的代码foreach (var temp in typeof(T).GetProperties().Where(temp=>temp.CanRead&&temp.CanWrite))
url
查看 IL 须要先把第一个参数放在左边,第二个参数放在右边,调用第二个参数的 get 设置第一个参数的set对应的属性看起来的正常代码就是spa
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/](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/