Search code examples
c#reflection.emitil

C# Reflection IL - Understanding how values are copied


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

Solution

  • First you have

    generator.Emit(OpCodes.Ldloc, cloneVariable);
    generator.Emit(OpCodes.Ldarg_0);
    

    Your stack contains (cloneVariable, myObject)

    enerator.Emit(OpCodes.Ldfld, field);
    

    This one pops object reference, retrieves the value from the field and pushes the value onto the stack

    Your stack contains (cloneVariable, myObject.field)

    generator.Emit(OpCodes.Stfld, field);
    

    This one pops object reference and value and stores the value in the object's field. The result is equivalent to C#'s

    cloneVariable.field = myObject.field