繁体   English   中英

EmitIL - 使用 ref 参数调用方法

[英]EmitIL - Call method with ref parameter

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

class ReferenceTestViewModel : BaseViewModel, ITestViewModel
{
    private int id;

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

SetProperty是其祖父BaseObservable ( BaseViewModel : 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)

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

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

修改调用发出以使用该方法的类型化泛型版本(使用.MakeGeneric(typeof(int)) )应该可以修复它。

IE

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM