Derek Ziemba
Derek Ziemba

Reputation: 2663

Using ILGenerator to generate method to copy and convert object properties gives InvalidProgramException

I'm trying to generate a method that will copy properties from one generic object to another and if the property type is different, I want it to use a TypeConverter to convert the property value.
Eventually I want to scale this up so I can auto map IDataRecords directly to objects. But right now I'm having an issue with copying properties from one object to the other when the property type is different. The generated method works fine if all the property types are the same.

I don't understand why it can't be generated because I wrote an example method in C# then looked at the IL, and the IL I'm generating should be the same.

Below is a simplified version, full code can be found at https://gist.github.com/DerekZiemba/468b84d7b5a5a289470859e261f17217

public static void CopyExample(StreetAddress target, DBLocation src) {
    target.Locality = src.Locality;
    target.Latitude = (float)TypeConversion.Float32Converter.ConvertFrom(src.Latitude);
}

Here's the IL generated by the CopyExample:

.method public hidebysig static void 
  CopyExample(
     class ExampleILGeneratorShallowCopy.StreetAddress target, 
     class ExampleILGeneratorShallowCopy.DBLocation src
  ) cil managed 
{
  .maxstack 8

  // [36 4 - 36 35]
  IL_0000: ldarg.0      // target
  IL_0001: ldarg.1      // src
  IL_0002: callvirt     instance string ExampleILGeneratorShallowCopy.DBLocation::get_Locality()
  IL_0007: callvirt     instance void ExampleILGeneratorShallowCopy.StreetAddress::set_Locality(string)

  // [37 4 - 37 87]
  IL_000c: ldarg.0      // target
  IL_000d: ldsfld       class [System.ComponentModel.TypeConverter]System.ComponentModel.SingleConverter ExampleILGeneratorShallowCopy.TypeConversion::Float32Converter
  IL_0012: ldarg.1      // src
  IL_0013: callvirt     instance float64 ExampleILGeneratorShallowCopy.DBLocation::get_Latitude()
  IL_0018: box          [System.Runtime]System.Double
  IL_001d: callvirt     instance object [System.ComponentModel.TypeConverter]System.ComponentModel.TypeConverter::ConvertFrom(object)
  IL_0022: unbox.any    [System.Runtime]System.Single
  IL_0027: callvirt     instance void ExampleILGeneratorShallowCopy.StreetAddress::set_Latitude(float32)

  // [38 3 - 38 4]
  IL_002c: ret          

} // end of method ExampleILGeneratorShallowCopy::CopyExample

And here's the ILGenerator method that should produce the same output as the example above:

public delegate void CopyIntoDelegate<T, S>(T location, S src);

public static CopyIntoDelegate<StreetAddress, DBLocation> GenerateExactExample() {
    Type targetType = typeof(StreetAddress);
    Type srcType = typeof(DBLocation);

    PropertyInfo targetLocality = targetType.GetProperty("Locality");
    PropertyInfo srcLocality = srcType.GetProperty("Locality");
    PropertyInfo targetLatitude = targetType.GetProperty("Latitude");
    PropertyInfo srcLatitude = srcType.GetProperty("Latitude");

    DynamicMethod dynmethod = new DynamicMethod("ExactExample", typeof(void), new Type[2]{ targetType, srcType }, true);
    ILGenerator gen = dynmethod.GetILGenerator();

    gen.Emit(OpCodes.Ldarg_0);
    gen.Emit(OpCodes.Ldarg_1);
    gen.Emit(OpCodes.Callvirt, srcLocality.GetMethod);
    gen.Emit(OpCodes.Callvirt, targetLocality.SetMethod);

    TypeConverter converter = TypeConversion.Float32Converter;
    FieldInfo converterField = typeof(TypeConversion).GetField(nameof(TypeConversion.Float32Converter));
    MethodInfo convertFrom = converter.GetType().GetMethod(nameof(TypeConverter.ConvertFrom), new [] { typeof(object) });

    gen.Emit(OpCodes.Ldarg_0);
    gen.Emit(OpCodes.Ldsfld, converterField);           
    gen.Emit(OpCodes.Ldarg_1);
    gen.Emit(OpCodes.Callvirt, srcLatitude.GetMethod);
    gen.Emit(OpCodes.Box, srcLatitude.PropertyType);
    gen.Emit(OpCodes.Callvirt, convertFrom);
    gen.Emit(OpCodes.Unbox_Any);
    gen.Emit(OpCodes.Callvirt, targetLatitude.SetMethod);

    gen.Emit(OpCodes.Ret);

    Delegate del = dynmethod.CreateDelegate(typeof(CopyIntoDelegate<StreetAddress, DBLocation>));
    return (CopyIntoDelegate<StreetAddress, DBLocation>)del;
}

Upvotes: 0

Views: 369

Answers (0)

Related Questions