EmitIL - Call method with ref parameter

1

I am trying to write the Following code in MSIL:

class ReferenceTestViewModel : BaseViewModel, ITestViewModel
{
    private int id;

    public int Id
    {
        get { return id; }
        set { this.SetProperty(ref id, value); }
    }
}

SetProperty being a method of its grand-father BaseObservable (BaseViewModel : BaseObservable).

So I compiled this ReferenceTestViewModel and use ILSpy to get the following IL:

.field private int32 id

.method public final hidebysig specialname newslot virtual 
    instance void set_Id (
        int32 'value'
    ) cil managed 
{
    // Method begins at RVA 0x230c
    // Code size 21 (0x15)
    .maxstack 8

    IL_0000: nop
    IL_0001: ldarg.0
    IL_0002: ldarg.0
    IL_0003: ldflda int32 ReferenceTestViewModel::id
    IL_0008: ldarg.1
    IL_0009: ldstr "Id"
    IL_000e: call instance void [OtherAssembly]BaseObservable::SetProperty<int32>(!!0&, !!0, string)
    IL_0013: nop
    IL_0014: ret
} // end of method ReferenceTestViewModel::set_Id

And I finally end-up with the Following C# generator code. Before reading it, please take into account that my whole class was working fine before my attempt to call the SetProperty method. It was just setting the associated backing field (cf my comment: Old way), but it was working just fine. So I am pretty sur the error is in that part.

private static void GenerateSetter(TypeBuilder typeBuilder, string propertyName, Type propertyType, PropertyBuilder propertyBuilder, FieldBuilder backingFieldBuilder)
{
    MethodBuilder setPropMthdBldr =
        typeBuilder.DefineMethod("set_" + propertyName,
          MethodAttributes.Public
        | MethodAttributes.SpecialName
        | MethodAttributes.HideBySig
        | MethodAttributes.Final
        | MethodAttributes.Virtual
        | MethodAttributes.NewSlot,
          null,
          new[] { propertyType });

    ILGenerator setIl = setPropMthdBldr.GetILGenerator();

    // New way : call SetProperty
    setIl.Emit(OpCodes.Nop);
    setIl.Emit(OpCodes.Ldarg_0);
    setIl.Emit(OpCodes.Ldarg_0);
    setIl.Emit(OpCodes.Ldflda, backingFieldBuilder);
    setIl.Emit(OpCodes.Ldarg_1);
    setIl.Emit(OpCodes.Ldstr, propertyName);
    setIl.Emit(OpCodes.Call, typeof(BaseViewModel).GetMethod(nameof(BaseObservable.SetProperty), BindingFlags.Instance | BindingFlags.NonPublic));
    setIl.Emit(OpCodes.Nop);
    setIl.Emit(OpCodes.Ret);

    // Old way : simply set the backing field and return
    //setIl.Emit(OpCodes.Ldarg_0);
    //setIl.Emit(OpCodes.Ldarg_1);
    //setIl.Emit(OpCodes.Stfld, backingFieldBuilder);
    //setIl.Emit(OpCodes.Ret);

    propertyBuilder.SetSetMethod(setPropMthdBldr);
}

For information, here is how I build the backing field and the property:

private static void GenerateProperty(TypeBuilder typeBuilder, string propertyName, Type propertyType, ConstructorBuilder ctorBuilder)
{
    var fieldBuilder = typeBuilder.DefineField("_" + propertyName, propertyType, FieldAttributes.Private);
    var propertyBuilder = typeBuilder.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null);

    GenerateGetter(typeBuilder, propertyBuilder, fieldBuilder);
    GenerateSetter(typeBuilder, propertyName, propertyType, propertyBuilder, fieldBuilder);
}

private static void InitializeProperty(Type propertyType, ConstructorBuilder ctorBuilder, FieldBuilder fieldBuilder)
{
    var propertyCtor = propertyType.GetConstructors().First();
    var emitIL = ctorBuilder.GetILGenerator();

    emitIL.Emit(OpCodes.Ldarg_0);
    emitIL.Emit(OpCodes.Newobj, propertyCtor);
    emitIL.Emit(OpCodes.Stfld, fieldBuilder);
}

private static void GenerateGetter(TypeBuilder typeBuilder, PropertyBuilder propertyBuilder, FieldBuilder backingFieldBuilder)
{
    MethodBuilder getPropMthdBldr = typeBuilder.DefineMethod(
        "get_" + propertyBuilder.Name,
        MethodAttributes.Public
        | MethodAttributes.SpecialName
        | MethodAttributes.HideBySig
        | MethodAttributes.Final
        | MethodAttributes.Virtual
        | MethodAttributes.NewSlot,
        propertyBuilder.PropertyType,
        Type.EmptyTypes);

    ILGenerator getIl = getPropMthdBldr.GetILGenerator();

    getIl.Emit(OpCodes.Ldarg_0);
    getIl.Emit(OpCodes.Ldfld, backingFieldBuilder);
    getIl.Emit(OpCodes.Ret);

    propertyBuilder.SetGetMethod(getPropMthdBldr);
}

And finally, here is the failing test:

var testObject = (ITestViewModel)Activator.CreateInstance(_dynamicType); 

var value = testObject.Id; // works fine
testObject.Id = 42; // throw BadImageFormatException

Here is the stacktrace :

An attempt was made to load a program with an incorrect format. (0x8007000B)

at DynamicITestViewModel.set_Id(Int32 )

c#
reflection.emit
asked on Stack Overflow Nov 25, 2019 by fharreau • edited Jun 20, 2020 by Community

1 Answer

1

From what I can tell, it's to do with SetProperty being a generic method.

The current call instruction is being emitted using a non-typed generic method reference so it would essentially be a call to SetProperty<T>(ref id, value) rather than SetProperty<Int32>(ref id, value).

Modifying the call emit to use the typed generic version of the method (using .MakeGeneric(typeof(int))) should fix it.

I.E.

setIl.Emit(OpCodes.Call, typeof(BaseViewModel).GetMethod(nameof(BaseObservable.SetProperty), BindingFlags.Instance | BindingFlags.NonPublic).MakeGeneric(typeof(int)));

rather than

setIl.Emit(OpCodes.Call, typeof(BaseViewModel).GetMethod(nameof(BaseObservable.SetProperty), BindingFlags.Instance | BindingFlags.NonPublic));
answered on Stack Overflow Nov 26, 2019 by Shane Delany

User contributions licensed under CC BY-SA 3.0