C # Use the deep clone method of Emit,
Someone asked, How many methods are there to copy all the attributes of a class to another class? This is how many methods are available for deep cloning, and three methods are easy to think about. Direct replication, reflection replication, and serialized replication. However, Expression Tree replication and IL replication are the two most efficient ones. This article focuses on the last one.
If you know what IL is and how to write a simple IL, you can start to develop functions. The first step is to name the class. to copy all the attributes of a class to another class, you must call the method and the method must be named. Therefore, the first step is to name the class.
To create a public void Clone method (T source, T los) I used the following code:
var dynamicMethod = new DynamicMethod("Clone", null, new[] { typeof(T), typeof(T) });
The first parameter of the method is easy to see. I will not explain it. The second parameter is the return value of the method, because the return value is void, so no need to write it. The third parameter is the function parameter. You only need to use the type. If multiple parameters are written to an array, please let me know.
However, after defining a method, you need to write the code in the method. At this time, you need to use ILGenerator and his Emit method. This method is fast and you need to know IL when using it. If you do not know, it doesn't matter. I will talk about it later.
ILGenerator generator = dynamicMethod.GetILGenerator();
All attributes of the type need to be obtained. Although reflection is used here, reflection is only used once, because the method obtained by reflection is to write the IL code, which can be used many times after writing, the speed may not be fast for the first time, but the later speed is similar to the speed at which you compile your own code. Therefore, we recommend that you use this method. You can use dot trace to view the performance. What I see is that the performance is good.
Code foreach (var temp in typeof (T). GetProperties (). Where (temp => temp. CanRead & temp. CanWrite) with all the attributes that can be read and written ))
To view the IL, you need to put the first parameter on the left, the second parameter on the right, and call the get of the second parameter to set the set corresponding to the first parameter. The normal code that looks like this is
los.foo=source.foo;
Foo here gets an attribute, which can be written at will. For the written IL, see the following.
Ldarg_1 //losLdarg_0 //scallvirt instance string lindexi.Foo::get_Name()callvirt instance void lindexi.Foo::set_Name(string)ret
You can use a method from the above Code callvirt, corresponding to the input parameter, so you can get the method through reflection, and then call this method, so write the code as below
generator.Emit(OpCodes.Ldarg_1);// los generator.Emit(OpCodes.Ldarg_0);// s generator.Emit(OpCodes.Callvirt,temp.GetMethod); generator.Emit(OpCodes.Callvirt, temp.SetMethod);
Because we can take this out of the conversion method, so the following gives all the code
private static void CloneObjectWithIL
(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
) dynamicMethod.CreateDelegate(typeof(Action
)); clone(source, los); }
If you test this method, you will find that MethodAccessException occurs for classes that are not visible to this method. Therefore, the passed-in class requires this method to be used directly.
// A. dllpublic class Foo {} CloneObjectWithIL (foo1, foo2); // B. dll private static void CloneObjectWithIL
(T source, T los) cannot be used at this time
In addition, for static attributes, using the above Code will also cause errors, because the access to static attributes has no permission, please refer to the modified.
////// Provides fast object deep replication ///Public static class Clone {////// Provides the method of using IL to quickly copy objects in depth. // requires that this method be T accessible //////
///Source ///Copy attributes from the source ///
If the input T does not have this method for access, this exception will occur.
// ReSharper disable once InconsistentNaming public static void CloneObjectWithIL
(T source, T los) {// see https://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
) 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) {// do not copy the static class property 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
) DynamicMethod. CreateDelegate (typeof (Action
); CachedIl [typeof (T)] = clone; clone (source, los);} private static Dictionary
CachedIl {set; get;} = new Dictionary
();}
Note that the copy operation only copies the attributes of the class, but does not copy the attributes of the class. If the TestA1 type exists, see the following code.
public class TestA1 { public string Name { get; set; } }
After executing the following code, the TestA1 is the same.
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
When can the above Code be used? Actually, if you need to copy the attributes of the base class in a created class, it is good to use this method. For example, some classes will be created in the Model, in ViewModel, you sometimes need to add some attributes to these classes, such as Checked. You need to re-copy the attributes of the Model. If you need to write the attributes one by one, the development speed is too slow. So this method can be used at this time.
For example, if the Base class is Base and the inheritance class is Derived, see the following code:
public class Base{ public string BaseField;}public class Derived : Base{ public string DerivedField;}Base base = new Base();//some alother codeDerived derived = new Derived();CloneObjectWithIL(base, derived);
If you need to copy a class to a new class, you can use this code
private static T CloneObjectWithIL
(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
)); _cachedIL.Add(typeof(T), myExec); } return ((Func
)myExec)(myObject); }