EmitIL-带有ref参数的调用方法

问题描述 投票:1回答:1

我正在尝试在MSIL中编写以下代码:

class ReferenceTestViewModel : BaseViewModel, ITestViewModel
{
    private int id;

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

[SetProperty是其祖父BaseObservableBaseViewModel : BaseObservable)的方法。

所以我编译了此ReferenceTestViewModel,然后使用ILSpy获得以下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

然后,我最终得到了以下C#生成器代码。在阅读它之前,请考虑在尝试调用SetProperty方法之前我的整个类都运行良好。它只是设置关联的后备字段(请参阅我的评论:旧方法),但是效果很好。所以我很想知道错误在于那一部分。

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

有关信息,这是我如何构建背景字段和属性:

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

最后,这是失败的测试:

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

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

这里是堆栈跟踪:

试图加载格式错误的程序。 (0x8007000B)

在DynamicITestViewModel.set_Id(Int32)

c# reflection.emit
1个回答
0
投票

据我所知,这与SetProperty是通用方法有关。

[当前的调用指令正在使用非类型化的泛型方法引用来发出,因此它实质上是对SetProperty<T>(ref id, value)的调用,而不是对SetProperty<Int32>(ref id, value)的调用。

将调用发射修改为使用方法的类型化通用版本(使用.MakeGeneric(typeof(int))应该可以解决。

I.E。

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

而不是

setIl.Emit(OpCodes.Call, typeof(BaseViewModel).GetMethod(nameof(BaseObservable.SetProperty), BindingFlags.Instance | BindingFlags.NonPublic));
© www.soinside.com 2019 - 2024. All rights reserved.