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 )
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));
User contributions licensed under CC BY-SA 3.0