添加在属性运行时更改为属性setter
Add OnpropertyChanged to property setter at runtime
我正在使用反射在运行时创建 classes,基于 ClassName 和属性列表,它们是父 class 的子classes =] "DataObject",它实现了 INotifyPropertyChanged 和 OnPropertyChanged,但是当我尝试通过以下方法设置属性时,我得到一个 "Field token out of range" 异常:
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;
}
这是我希望每个 属性 工作的方式(LastChange 是来自父 class 的静态字符串):
public string Provaa { get { return provaa; }
set { LastChange = ToString(); provaa = value; OnPropertyChanged("Provaa"); } }
这就是翻译成 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
最后,这就是我尝试使用反射重新创建它的方式:
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.Emit(OpCodes.Stsfld, typeof(DataObject).GetField("LastChange", BindingFlags.Static | BindingFlags.Public);
currSetIL.Emit(OpCodes.Ldarg_0);
currSetIL.Emit(OpCodes.Ldarg_1);
currSetIL.Emit(OpCodes.Stfld, field);
currSetIL.Emit(OpCodes.Ldarg_0);
currSetIL.Emit(OpCodes.Ldstr, propertyName);
currSetIL.Emit(OpCodes.Callvirt, typeof(DataObject).GetMethod("OnPropertyChanged", new Type[1] { typeof(string) }));
currSetIL.Emit(OpCodes.Ret);
这是 "CreateClass" 方法的一部分:
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);
ILGenerator currSetIL = currSetPropMthdBldr.GetILGenerator();
currSetIL.Emit(OpCodes.Ldarg_0);
currSetIL.Emit(OpCodes.Callvirt, typeof(Object).GetMethod("ToString"));
currSetIL.Emit(OpCodes.Stsfld, DataObject.LastChange);
currSetIL.Emit(OpCodes.Ldarg_0);
currSetIL.Emit(OpCodes.Ldarg_1);
currSetIL.Emit(OpCodes.Stfld, field);
currSetIL.Emit(OpCodes.Ldarg_0);
currSetIL.Emit(OpCodes.Ldstr, propertyName);
currSetIL.Emit(OpCodes.Callvirt, 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);
}
我怀疑我需要在设置值时指定 class 的程序集名称和模块名称,但不知道如何,尽管 Activator 创建了 class 实例具有所有正确的属性,所以可能是我在构建 SetMethod 时犯了一些错误,有人可以帮助我吗?
编辑父Class:
public class DataObject : INotifyPropertyChanged
{
public static string LastChange = "";
public void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public override string ToString()
{
string tostring = "|";
PropertyInfo[] properties = this.GetType().GetProperties();
foreach (PropertyInfo prop in properties)
{
tostring += " " + prop.GetValue(this, null) + " |";
}
return tostring;
}
public event PropertyChangedEventHandler PropertyChanged;
}
我在您的 setter IL 代码中看到两个错误
这个:
currSetIL.EmitCall(OpCodes.Call, typeof(Object).GetMethod("ToString"), new Type[0]);
需要:
currSetIL.Emit(OpCodes.Callvirt, typeof(Object).GetMethod("ToString"), new Type[0]);
还有这个:
currSetIL.Emit(OpCodes.Ldstr, propertyName);
currSetIL.Emit(OpCodes.Call, typeof(DataObject).GetMethod("OnPropertyChanged", new Type[1] { typeof(string) }));
需要:
currSetIL.Emit(OpCodes.ldarg_0);
currSetIL.Emit(OpCodes.Ldstr, propertyName);
currSetIL.Emit(OpCodes.Callvirt, typeof(DataObject).GetMethod("OnPropertyChanged", new Type[1] { typeof(string) }));
我在第一个案例中看到您将 callvirt
放在评论中,然后将其替换为 call
为什么?它是对虚拟方法的调用。
同样在第二种情况下,它是虚拟调用,您忘记先加载 this
来调用实例方法 OnPropertyChanged
。
这是我目前看到的,如果它修复了请告诉我,如果没有我尝试自己重现它。
更新:
替换为:
currSetIL.Emit(OpCodes.Stsfld, DataObject.LastChange)
有了这个:
currSetIL.Emit(OpCodes.Stsfld, typeof(DataObject).GetField("LastChange", BindingFlags.Static | BindingFlags.Public)
我正在使用反射在运行时创建 classes,基于 ClassName 和属性列表,它们是父 class 的子classes =] "DataObject",它实现了 INotifyPropertyChanged 和 OnPropertyChanged,但是当我尝试通过以下方法设置属性时,我得到一个 "Field token out of range" 异常:
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;
}
这是我希望每个 属性 工作的方式(LastChange 是来自父 class 的静态字符串):
public string Provaa { get { return provaa; }
set { LastChange = ToString(); provaa = value; OnPropertyChanged("Provaa"); } }
这就是翻译成 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
最后,这就是我尝试使用反射重新创建它的方式:
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.Emit(OpCodes.Stsfld, typeof(DataObject).GetField("LastChange", BindingFlags.Static | BindingFlags.Public);
currSetIL.Emit(OpCodes.Ldarg_0);
currSetIL.Emit(OpCodes.Ldarg_1);
currSetIL.Emit(OpCodes.Stfld, field);
currSetIL.Emit(OpCodes.Ldarg_0);
currSetIL.Emit(OpCodes.Ldstr, propertyName);
currSetIL.Emit(OpCodes.Callvirt, typeof(DataObject).GetMethod("OnPropertyChanged", new Type[1] { typeof(string) }));
currSetIL.Emit(OpCodes.Ret);
这是 "CreateClass" 方法的一部分:
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);
ILGenerator currSetIL = currSetPropMthdBldr.GetILGenerator();
currSetIL.Emit(OpCodes.Ldarg_0);
currSetIL.Emit(OpCodes.Callvirt, typeof(Object).GetMethod("ToString"));
currSetIL.Emit(OpCodes.Stsfld, DataObject.LastChange);
currSetIL.Emit(OpCodes.Ldarg_0);
currSetIL.Emit(OpCodes.Ldarg_1);
currSetIL.Emit(OpCodes.Stfld, field);
currSetIL.Emit(OpCodes.Ldarg_0);
currSetIL.Emit(OpCodes.Ldstr, propertyName);
currSetIL.Emit(OpCodes.Callvirt, 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);
}
我怀疑我需要在设置值时指定 class 的程序集名称和模块名称,但不知道如何,尽管 Activator 创建了 class 实例具有所有正确的属性,所以可能是我在构建 SetMethod 时犯了一些错误,有人可以帮助我吗?
编辑父Class:
public class DataObject : INotifyPropertyChanged
{
public static string LastChange = "";
public void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public override string ToString()
{
string tostring = "|";
PropertyInfo[] properties = this.GetType().GetProperties();
foreach (PropertyInfo prop in properties)
{
tostring += " " + prop.GetValue(this, null) + " |";
}
return tostring;
}
public event PropertyChangedEventHandler PropertyChanged;
}
我在您的 setter IL 代码中看到两个错误
这个:
currSetIL.EmitCall(OpCodes.Call, typeof(Object).GetMethod("ToString"), new Type[0]);
需要:
currSetIL.Emit(OpCodes.Callvirt, typeof(Object).GetMethod("ToString"), new Type[0]);
还有这个:
currSetIL.Emit(OpCodes.Ldstr, propertyName);
currSetIL.Emit(OpCodes.Call, typeof(DataObject).GetMethod("OnPropertyChanged", new Type[1] { typeof(string) }));
需要:
currSetIL.Emit(OpCodes.ldarg_0);
currSetIL.Emit(OpCodes.Ldstr, propertyName);
currSetIL.Emit(OpCodes.Callvirt, typeof(DataObject).GetMethod("OnPropertyChanged", new Type[1] { typeof(string) }));
我在第一个案例中看到您将 callvirt
放在评论中,然后将其替换为 call
为什么?它是对虚拟方法的调用。
同样在第二种情况下,它是虚拟调用,您忘记先加载 this
来调用实例方法 OnPropertyChanged
。
这是我目前看到的,如果它修复了请告诉我,如果没有我尝试自己重现它。
更新:
替换为:
currSetIL.Emit(OpCodes.Stsfld, DataObject.LastChange)
有了这个:
currSetIL.Emit(OpCodes.Stsfld, typeof(DataObject).GetField("LastChange", BindingFlags.Static | BindingFlags.Public)