dimanche 27 novembre 2016

Add OnpropertyChanged to property setter at runtime

I'm creating classes at runtime using reflection, based on ClassName and a List of Properties, they're subclasses of the parent class "DataObject", which implements INotifyPropertyChanged and OnPropertyChanged, but when I try to set the properties through the following method I get a "Field token out of range" exception:

        private void dataGrid_AddingNewItem(object sender, AddingNewItemEventArgs e)
    {
        object obj = Activator.CreateInstance(currentType);

        PropertyInfo[] properties = obj.GetType().GetProperties();
        try
        {
            foreach (PropertyInfo prop in properties)
            {
                if (prop.PropertyType == typeof(string) && prop.CanWrite)
                { prop.SetValue(obj, "-", null); } 
                //else
                //{ prop.SetValue(obj, 0, null); }
            }
        }
        catch (Exception ex)
        {
            if (ex.InnerException != null)
            {
                throw ex.InnerException;
            }
        }

        e.NewItem = obj;
    }

this is how I would want each property to work (LastChange is a static string from the parent class):

public string Provaa { get { return provaa; } 
set { LastChange = ToString(); provaa = value; OnPropertyChanged("Provaa"); } }

and this is how that is translated to Msil:

.method public hidebysig specialname instance void 
    set_Provaa(string 'value') cil managed
{
// Code size       32 (0x20)
.maxstack  8
IL_0000:  nop
IL_0001:  ldarg.0
IL_0002:  callvirt   instance string [mscorlib]System.Object::ToString()
IL_0007:  stsfld     string EYBDataManager.DataObject::LastChange
IL_000c:  ldarg.0
IL_000d:  ldarg.1
IL_000e:  stfld      string EYBDataManager.Prova::provaa
IL_0013:  ldarg.0
IL_0014:  ldstr      "Provaa"
IL_0019:  call       instance void EYBDataManager.DataObject::OnPropertyChanged(string)
IL_001e:  nop
IL_001f:  ret
} // end of method Prova::set_Provaa

And lastly this is how I'm attempting to recreate that using reflection:

MethodBuilder currSetPropMthdBldr = typeBuilder.DefineMethod("set_value", GetSetAttr, null, new Type[] { prop.ActualType });
ILGenerator currSetIL = currSetPropMthdBldr.GetILGenerator();
currSetIL.Emit(OpCodes.Ldarg_0);
//currSetIL.Emit(OpCodes.Callvirt, typeof(Object).GetMethod("ToString"));
currSetIL.EmitCall(OpCodes.Call, typeof(Object).GetMethod("ToString"), new Type[0]);
currSetIL.Emit(OpCodes.Stsfld, DataObject.LastChange);
currSetIL.Emit(OpCodes.Ldarg_0);
currSetIL.Emit(OpCodes.Ldarg_1);
currSetIL.Emit(OpCodes.Stfld, field);
currSetIL.Emit(OpCodes.Ldstr, propertyName);
currSetIL.Emit(OpCodes.Call, typeof(DataObject).GetMethod("OnPropertyChanged", new Type[1] { typeof(string) }));
currSetIL.Emit(OpCodes.Ret);

which is part of the "CreateClass" method:

public static void CreateClass(string className, List<PropertyTemplate> properties)
{
AssemblyName assemblyName = new AssemblyName();
assemblyName.Name = "tmpAssembly";
AssemblyBuilder assemblyBuilder = System.Threading.Thread.GetDomain().DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
ModuleBuilder module = assemblyBuilder.DefineDynamicModule("tmpModule");
TypeBuilder typeBuilder = module.DefineType(className, TypeAttributes.Public | TypeAttributes.Class, typeof(DataObject));

foreach (PropertyTemplate prop in properties)
{
    string propertyName = prop.Name;
    FieldBuilder field = typeBuilder.DefineField("p_" + propertyName, prop.ActualType, FieldAttributes.Private);
    PropertyBuilder property = typeBuilder.DefineProperty(propertyName, PropertyAttributes.None, prop.ActualType, new Type[] { prop.ActualType });
    MethodAttributes GetSetAttr = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig;

    MethodBuilder currGetPropMthdBldr = typeBuilder.DefineMethod("get_value", GetSetAttr, prop.ActualType, Type.EmptyTypes);
    ILGenerator currGetIL = currGetPropMthdBldr.GetILGenerator();
    currGetIL.Emit(OpCodes.Ldarg_0);
    currGetIL.Emit(OpCodes.Ldfld, field);
    currGetIL.Emit(OpCodes.Ret);


    MethodBuilder currSetPropMthdBldr = typeBuilder.DefineMethod("set_value", GetSetAttr, null, new Type[] { prop.ActualType });
    ILGenerator currSetIL = currSetPropMthdBldr.GetILGenerator();
    currSetIL.Emit(OpCodes.Ldarg_0);
    //currSetIL.Emit(OpCodes.Callvirt, typeof(Object).GetMethod("ToString"));
    currSetIL.EmitCall(OpCodes.Call, typeof(Object).GetMethod("ToString"), new Type[0]);
    currSetIL.Emit(OpCodes.Stsfld, DataObject.LastChange);
    currSetIL.Emit(OpCodes.Ldarg_0);
    currSetIL.Emit(OpCodes.Ldarg_1);
    currSetIL.Emit(OpCodes.Stfld, field);
    currSetIL.Emit(OpCodes.Ldstr, propertyName);
    currSetIL.Emit(OpCodes.Call, typeof(DataObject).GetMethod("OnPropertyChanged", new Type[1] { typeof(string) }));
    currSetIL.Emit(OpCodes.Ret);

    property.SetGetMethod(currGetPropMthdBldr);
    property.SetSetMethod(currSetPropMthdBldr);
}

Type genType = typeBuilder.CreateType();
if (Templates.ContainsKey(className))
    Templates[className] = genType;
else
    Templates.Add(className, genType);
}

I suspect I'd need to specify the assembly name and the module name of the class when setting the value, but don't know how, though the Activator creates the class instance with all the correct properties, so probably I made some mistake while building the SetMethod, could someone help me?





Aucun commentaire:

Enregistrer un commentaire