I'm trying to improve the performance of a certain part of my program which involves deep cloning the same object graph over and over across multiple threads. Currently I use serialization which is a nice easy implementation but I'd like something faster. I came across the idea of IL cloning and am trying to work with some code found here (Whizzo's Blog).
I don't really get IL as yet, so I'm hoping someone can help a little bit and explain some of the stuff to me (I imagine this is the first question of several).
The question here (and b.t.w if anyone has any good links explaining opcodes and reflection.emit a bit more that would be great, MSDN doesn't give a lot of detail) is how are the values copied? I can see that a new object is constructed and popped from the stack
generator.Emit(OpCodes.Newobj, cInfo);
generator.Emit(OpCodes.Stloc, cloneVariable);
Then a little bit later given a field value of interest, the value is somehow copied. I don't understand how we go back to the original object and grab it's value when the original object doesn't seem to be referenced? Or is this some magic of the LocalBuilder (I'm not 100% sure what it does):
// I *THINK* this Pushes the cloneVariable on the stack, loads an argument (from where?) and sets the field value based on the FieldInfo??
generator.Emit(OpCodes.Ldloc, cloneVariable);
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldfld, field);
generator.Emit(OpCodes.Stfld, field);
I've modified the code slightly as I always want a Deep clone and I want it based on serialized fields:
private static T CloneObjectWithILDeep(T myObject)
{
Delegate myExec = null;
if (!_cachedILDeep.TryGetValue(typeof(T), out myExec))
{
// Create ILGenerator
DynamicMethod dymMethod = new DynamicMethod("DoDeepClone", typeof(T), new Type[] { typeof(T) }, Assembly.GetExecutingAssembly().ManifestModule, true);
ILGenerator generator = dymMethod.GetILGenerator();
LocalBuilder cloneVariable = generator.DeclareLocal(myObject.GetType());
ConstructorInfo cInfo = myObject.GetType().GetConstructor(Type.EmptyTypes);
generator.Emit(OpCodes.Newobj, cInfo);
generator.Emit(OpCodes.Stloc, cloneVariable);
foreach (FieldInfo field in typeof(T).GetFields(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public))
{
if(field.IsNotSerialized)
continue;
if (field.FieldType.IsValueType || field.FieldType == typeof(string))
{
generator.Emit(OpCodes.Ldloc, cloneVariable);
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldfld, field);
generator.Emit(OpCodes.Stfld, field);
}
else if (field.FieldType.IsClass)
{
CopyReferenceType(generator, cloneVariable, field);
}
}
generator.Emit(OpCodes.Ldloc_0);
generator.Emit(OpCodes.Ret);
myExec = dymMethod.CreateDelegate(typeof(Func<T, T>));
_cachedILDeep.Add(typeof(T), myExec);
}
return ((Func<T, T>)myExec)(myObject);
}