【问题标题】:Add OnpropertyChanged to property setter at runtime在运行时将 OnpropertyChanged 添加到属性设置器
【发布时间】:2016-11-27 14:20:58
【问题描述】:

我在运行时使用反射创建类,基于 ClassName 和属性列表,它们是父类“DataObject”的子类,它实现了 INotifyPropertyChanged 和 OnPropertyChanged,但是当我尝试通过以下方法我得到一个“字段令牌超出范围”异常:

        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 是来自父类的静态字符串):

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

我怀疑在设置值时我需要指定类的程序集名称和模块名称,但不知道如何,尽管 Activator 创建具有所有正确属性的类实例,所以可能我做了构建 SetMethod 时出现错误,有人可以帮助我吗?

编辑父类:

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

【问题讨论】:

    标签: c# reflection cil


    【解决方案1】:

    我在您的设置器 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)
    

    【讨论】:

    • 嗨,谢谢你的回答,我按照你的建议修改了我的代码,但是在尝试设置值时我仍然得到“字段令牌超出范围”异常,我也添加了父类,如果它可能有用
    • @EatYourBeetS 吸气剂有效吗?如果是,请尝试在不设置静态字段的情况下发出 setter。直到明天我才能自己检查。让我知道它是否有效。
    • 是的,现在它在没有这 3 行的情况下可以工作 currSetIL.Emit(OpCodes.Ldarg_0); currSetIL.Emit(OpCodes.Call, typeof(DataObject).GetMethod("ToString", new Type[0])); currSetIL.Emit(OpCodes.Stsfld, DataObject.LastChange); 我猜第三行尤其是问题
    • @EatYourBeetSVG 是的,当然。我不知道我是怎么错过的。检查更新。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-25
    • 2011-08-28
    • 2012-09-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多