For deep copies, the usual approach is to serialize the object and then deserialize it into another object. For example, there is a workaround on StackOverflow: https://stackoverflow.com/questions/78536/deep-cloning-objects/78612#78612. This serialized way, for deep copies, is undoubtedly a performance killer.
Today we introduce a deep copy of the framework deepcopy, GitHub address: Https://github.com/ReubenBond/DeepCopy, which was adapted from the Orleans Framework, The implementation logic is very simple.
The implementation of the framework is based on the method of generating the field copy from Il code. The advantage of IL is that you can bypass the syntax rules of C #, such as accessing private objects and readonly
assigning values to fields.
Before introducing the framework, let's introduce the IL-related tools.
Il tools
Even if you are not using IL for the first time, it is not an easy task to confirm what IL code can achieve the desired result. This is the tool to help you with the place. You can write code in C # and then copy it to LINQPad, run and open the Il tab in the output.
It is also a good choice to use an anti-compilation/disassembly program like JetBrains's Dotpeek. You can open the compiled assembly in Dotpeek to display IL.
Finally, ReSharper is an indispensable tool. The resharper comes with a convenient IL viewer.
These tools can help you resolve issues with IL, and you can also access official documentation.
Deepcopy
Deepcopy Essentially, it provides only one method:
public static T Copy<T>(T original);
Deepcopy invoke the sample code:
List<string> original = new List<string>(2); original.Add("A"); original.Add("B"); var result = DeepCopier.Copy(original);
Implementation principle
Copy
Method copies each field in the recursive pass-through object to a new instance of the same type. The first thing to deal with is multiple references to the same object, and if the user provides an object that contains its own reference, the result will also contain a reference to itself. This means that we need to perform a reference trace. This is easy to do: We maintain a Dictionary<object, object>
mapping from the original object to the Copy object. Our primary method Copy<T>(T orig)
will invoke the context method to check the existence of the copied object in the dictionary:
public static T Copy<T>(T original, CopyContext context) { /* TODO: implementation */ }
The copy process is roughly as follows:
- If it is passed
null
in, it is returned null
;
- If the incoming object has been copied, it returns the copied object;
- If an "immutable object" is passed in, the incoming object is returned directly;
- If an array is passed in, each element is copied into a new array and returned;
- Creates a new instance of the incoming type, recursively copying each field from the incoming object to the Copy object and returning it.
The definition of immutable objects is simple: a type is a primitive type,,,, Enum
String
Guid
DateTime
..., or a type that uses a special [Immutable]
tag. A more detailed immutable type can refer to the source code, CopyPolicy.cs.
In addition to the last step above, everything else is simple. The final step is to recursively replicate each field, and you can use reflection to get and set the field values. Reflection is a performance killer, so use IL to implement this step.
Il code implementation
DeepCopy
The main IL code in the CopierGenerator.cs class is in the CreateCopier<T>(Type type)
method. Let's take a step-by-step secret:
First, create an DynamicMethod
object that will save the IL code you created. When you create an DynamicMethod
object, you must tell it what the signature is, and here it is a generic delegate type delegate T DeepCopyDelegate<T>(T original, CopyContext context)
.
var dynamicMethod = new DynamicMethod( type.Name + "DeepCopier", typeof(T), // 委托返回的类型 new[] {typeof(T), typeof(CopyContext)}, // 委托的参数类型。 typeof(CopierGenerator).Module, true); var il = dynamicMethod.GetILGenerator();
Il will become quite complex because it needs to deal with immutable types and value types, and let me explain in 1.1.
// 定义一个变量来保存返回的结果。 il.DeclareLocal(type);
Next, you need to initialize the new instance of the incoming type to the local variable. There are three scenarios to consider, each of which corresponds to a block in the following code:
- The type is a value type (struct). Use
default(T)
an expression to initialize it.
- The type has a parameterless constructor.
new T()
initialize it by calling it.
- There is no parameterless constructor for this type. In this case, we use the. Net framework to resolve, invoke
FormatterServices.GetUninitializedObject(type)
.
Constructs the result object instance.var constructorinfo = type. GetConstructor (type.emptytypes);if (type. Isvaluetype) {Value types can be initialized directly.C #: result = Default (T); Il. Emit (opcodes.ldloca_s, (byte) 0); Il. Emit (opcodes.initobj, type); } else if ( constructorinfo! = null) {// If there is a default constructor, the default parameters are used directly. //C #: result = new T (); IL. Emit (Opcodes.newobj, ConstructorInfo); Il. Emit (OPCODES.STLOC_0); } else {//if there is no default constructor exists, use Getuninitializedobject to create the instance. //C #: result = (T) formatterservices.getuninitializedobject (type); IL. Emit (Opcodes.ldtoken, type); Il. Emit (Opcodes.call, DeepCopier.MethodInfos.GetTypeFromHandle); Il. Emit (Opcodes.call, this.methodinfos.getuninitializedobject); IL. Emit (Opcodes.castclass, type); Il. Emit (OPCODES.STLOC_0); }
Creates a local variable that is used to save the result, which is a new instance of the incoming type. Before we do anything, we must record a reference to the newly created object. Each parameter is pushed sequentially into the stack and used OpCodes.Call
to invoke context.RecordObject(original, result)
. Use OpCodes.Call
to invoke CopyContext.RecordObject
the method, because CopyContext
it is a sealed
class, otherwise it will be used OpCodes.Callvirt
.
Instances of value types do not have problems with multiple references.Therefore, only reference types are recorded in the context.if (! type. isvaluetype) {//Record object reference. //C #: Context. Recordobject (original, result); Il. emit (opcodes. ldarg_1); //parameter: Context il. emit (opcodes. LDARG_0); //parameter number: original IL. emit (opcodes. LDLOC_0); //the variable Il that is used to save the result locally. emit (opcodes. Call, this.methodinfos. Recordobject); }
Enumerates each field on an object and generates code that copies the value of the field into the result variable. The process is as follows:
Copy the value of each field. foreach (var field inThis.copypolicy.Getcopyablefields (Type)) {Loads a reference to the resulting object.if (Type.Isvaluetype) {Value types need to be loaded by address instead of copied onto the stack. Il.Emit (Opcodes.ldloca_s, (Byte)0); }else {IL.Emit (Opcodes.LDLOC_0); }Loads the value of the original Object field. Il.Emit (Opcodes.LDARG_0); Il.Emit (Opcodes.LDFLD, field);If it is an immutable type, it is assigned directly, otherwise a deep copy field is required.if (! This.copypolicy. isshallowcopyable (Field. FieldType) {//copy field using generic method deepcopy.copy<t> (T original, Copycontext context) //C #: copy<t> (field) IL. emit (opcodes. ldarg_1); Il. emit (opcodes. Call, this.methodinfos. Copyinner. makegenericmethod (Field. FieldType)); //assigns the copied value to the field of the result object. Il. emit (opcodes. stfld, field); }
Return the results and CreateDelegate
build the delegate, which you can use directly next.
// C#: return result; il.Emit(OpCodes.Ldloc_0); il.Emit(OpCodes.Ret); return dynamicMethod.CreateDelegate(typeof(DeepCopyDelegate<T>)) as DeepCopyDelegate<T>;
Summarize
This is the internal logic of the framework and, of course, some of the details are omitted, for example: special handling DeepCopier.cs in arrays;
Of course there are a lot of details that need to be optimized, and you can put your valuable ideas on GitHub.
Reference content:
- Https://reubenbond.github.io/posts/codegen-2-il-boogaloo
This article transferred from: http://www.cnblogs.com/tdfblog/p/DeepCopy-By-IL.html
"Go". NET il implements object deep copy