我正在尝试在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))
应该可以解决。
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));