代码之家  ›  专栏  ›  技术社区  ›  Guillermo Espert

无法在系统中实现INNotifyProperty。反思。Emit

  •  1
  • Guillermo Espert  · 技术社区  · 1 年前

    我正在尝试实现一个可以绑定到Avalonia DataGrid的动态类。为了使用DataGrid版本,这个类应该实现INtifyProperty。经过搜索,似乎最好的选择是使用System。反思。发送包裹。

    我能够在动态类和该类的实例中生成动态属性。此外,我可以创建一个ObservableCollection并将其绑定到DataGrid,用二元类的实例填充它,数据就会在DataGrid中正确显示。

    我的问题是INNotifyProperty的实现。实际上,当我从动态属性的set方法调用OnPropertyChanged方法时,我遇到了一个错误。

    这是我的动态课堂工厂:

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Linq;
    using System.Reflection;
    using System.Reflection.Emit;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace MyApp.App.Reflection
    {
        public class DynamicClassFactory
        {
            AssemblyName _assemblyName;
    
            public DynamicClassFactory(string className)
            {
                _assemblyName = new AssemblyName(className);
            }
    
            public dynamic? CreateObject(string[] propertyNames, Type[] types)
            {
                if (propertyNames.Length != types.Length)
                {
                    throw new ArgumentException("The number of property names should match their corresponding types number");
                }
    
                TypeBuilder dynamicClass = CreateClass();
                CreateConstructor(dynamicClass);
                var onPropertyChangedMethod = ImplementINotifyPropertyChanged(dynamicClass);
    
                for (int i = 0; i < propertyNames.Length; i++)
                {
                    CreateProperties(dynamicClass, propertyNames[i], types[i], onPropertyChangedMethod);
                }
    
                Type type = dynamicClass.CreateType();
                return Activator.CreateInstance(type);
            }
    
            private TypeBuilder CreateClass()
            {
                AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(_assemblyName, AssemblyBuilderAccess.Run);
                ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("DynamicModule");
                TypeBuilder typeBuilder = moduleBuilder.DefineType(_assemblyName.FullName, 
                                                                    TypeAttributes.Public
                                                                    | TypeAttributes.Class
                                                                    | TypeAttributes.AutoClass
                                                                    | TypeAttributes.AnsiClass
                                                                    | TypeAttributes.BeforeFieldInit
                                                                    | TypeAttributes.AutoLayout, null);
    
                typeBuilder.AddInterfaceImplementation(typeof(INotifyPropertyChanged));
    
                return typeBuilder;
            }
    
            private void CreateConstructor(TypeBuilder typeBuilder)
            {
                typeBuilder.DefineDefaultConstructor(MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName);
            }
    
            private MethodBuilder ImplementINotifyPropertyChanged(TypeBuilder typeBuilder)
            {
                var evtField = typeBuilder.DefineField("PropertyChanged", typeof(PropertyChangedEventHandler), FieldAttributes.Private);
                var evtBuilder = typeBuilder.DefineEvent("PropertyChanged", EventAttributes.None, typeof(PropertyChangedEventHandler));
                var addMethod = typeBuilder.DefineMethod("add_PropertyChanged",
                                                        MethodAttributes.Public | MethodAttributes.Virtual,
                                                        typeof(void),
                                                        new Type[] { typeof(PropertyChangedEventHandler) });
    
                var addIl = addMethod.GetILGenerator();
                addIl.Emit(OpCodes.Ldarg_0);
                addIl.Emit(OpCodes.Ldarg_0);
                addIl.Emit(OpCodes.Ldfld, evtField);
                addIl.Emit(OpCodes.Ldarg_1);
                addIl.Emit(OpCodes.Call, 
                            typeof(Delegate).GetMethod("Combine", new Type[] { typeof(Delegate), typeof(Delegate) }));
                addIl.Emit(OpCodes.Castclass, typeof(PropertyChangedEventHandler));
                addIl.Emit(OpCodes.Stfld, evtField);
                addIl.Emit(OpCodes.Ret);
    
                var removeMethod = typeBuilder.DefineMethod("remove_PropertyChanged",
                                                            MethodAttributes.Public | MethodAttributes.Virtual,
                                                            typeof(void),
                                                            new Type[] { typeof(PropertyChangedEventHandler) });
    
                var removeIl = removeMethod.GetILGenerator();
                removeIl.Emit(OpCodes.Ldarg_0);
                removeIl.Emit(OpCodes.Ldarg_0);
                removeIl.Emit(OpCodes.Ldfld, evtField);
                removeIl.Emit(OpCodes.Ldarg_1);
                removeIl.Emit(OpCodes.Call,
                            typeof(Delegate).GetMethod("Remove", new Type[] { typeof(Delegate), typeof(Delegate) }));
                removeIl.Emit(OpCodes.Castclass, typeof(PropertyChangedEventHandler));
                removeIl.Emit(OpCodes.Stfld, evtField);
                removeIl.Emit(OpCodes.Ret);
    
                evtBuilder.SetAddOnMethod(addMethod);
                evtBuilder.SetRemoveOnMethod(removeMethod);
    
                var onPropertyChangedMethod = typeBuilder.DefineMethod("OnPropertyChanged",
                                                                    MethodAttributes.Family | MethodAttributes.Virtual,
                                                                    typeof(void),
                                                                    new Type[] { typeof(string) });
    
                var onPropertyChangedIl = onPropertyChangedMethod.GetILGenerator();
                var retLabel = onPropertyChangedIl.DefineLabel();
    
                onPropertyChangedIl.Emit(OpCodes.Ldarg_0);
                onPropertyChangedIl.Emit(OpCodes.Ldfld, evtField);
                onPropertyChangedIl.Emit(OpCodes.Dup);
                onPropertyChangedIl.Emit(OpCodes.Brfalse_S, retLabel);
                onPropertyChangedIl.Emit(OpCodes.Ldarg_0);
                onPropertyChangedIl.Emit(OpCodes.Ldarg_1);
                onPropertyChangedIl.Emit(OpCodes.Newobj, typeof(PropertyChangedEventArgs).GetConstructor(new Type[] { typeof(string) }));
                onPropertyChangedIl.Emit(OpCodes.Callvirt, typeof(PropertyChangedEventHandler).GetMethod("Invoke", new Type[] { typeof(object), typeof(PropertyChangedEventArgs) }));
                onPropertyChangedIl.MarkLabel(retLabel);
                onPropertyChangedIl.Emit(OpCodes.Ret);
    
                
                return onPropertyChangedMethod;
            }
    
            private void CreateProperties(TypeBuilder typeBuilder, string propertyName, Type type, MethodBuilder onPropertyChangedMethod)
            {
                FieldBuilder fieldBuilder = typeBuilder.DefineField("_" + propertyName, type, FieldAttributes.Private);
    
                PropertyBuilder propertyBuilder = typeBuilder.DefineProperty(propertyName, PropertyAttributes.HasDefault, type, null);
                MethodBuilder getPropMthdBldr = typeBuilder.DefineMethod("get_" + propertyName, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, type, Type.EmptyTypes);
                ILGenerator getIl = getPropMthdBldr.GetILGenerator();
    
                getIl.Emit(OpCodes.Ldarg_0);
                getIl.Emit(OpCodes.Ldfld, fieldBuilder);
                getIl.Emit(OpCodes.Ret);
    
                MethodBuilder setPropMthdBldr = typeBuilder.DefineMethod("set_" + propertyName, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, null, new Type[] { type });
    
                ILGenerator setIl = setPropMthdBldr.GetILGenerator();
                setIl.Emit(OpCodes.Ldarg_0);
                setIl.Emit(OpCodes.Ldarg_1);
                setIl.Emit(OpCodes.Stfld, fieldBuilder);
                setIl.Emit(OpCodes.Ldarg_0);
                setIl.Emit(OpCodes.Ldstr, propertyName);
                setIl.Emit(OpCodes.Call, onPropertyChangedMethod);
                setIl.Emit(OpCodes.Ret);
    
                propertyBuilder.SetGetMethod(getPropMthdBldr);
                propertyBuilder.SetSetMethod(setPropMthdBldr);
            }
        }
    }
    

    这是创建实例的视图模型类:

    using MyApp.App.Reflection;
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace MyApp.App.Components
    {
        public class DataViewerControlViewModel
        {
            public ObservableCollection<dynamic> Data { get; } = new ObservableCollection<dynamic>();
    
            public DataViewerControlViewModel()
            {
                DynamicClassFactory dynamicClassFactory = new DynamicClassFactory("DynamicRecordsClass");
                var dynamicClass1 = dynamicClassFactory.CreateObject(new string[] { "ID", "Name", "Age", "IsAdmin" }, new Type[] { typeof(int), typeof(string), typeof(int), typeof(bool) });
    
                dynamicClass1.ID = 1;
                dynamicClass1.Name = "John Doe";
                dynamicClass1.Age = 30;
                dynamicClass1.IsAdmin = true;
    
                Data.Add(dynamicClass1);
    
                var dynamicClass2 = dynamicClassFactory.CreateObject(new string[] { "ID", "Name", "Age", "IsAdmin" }, new Type[] { typeof(int), typeof(string), typeof(int), typeof(bool) });
    
                dynamicClass2.ID = 2;
                dynamicClass2.Name = "Jane Doe";
                dynamicClass2.Age = 25;
                dynamicClass2.IsAdmin = false;
    
                Data.Add(dynamicClass2);
    
                Data.CollectionChanged += Data_CollectionChanged;
            }
    
            private void Data_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
            {
                if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Replace)
                {
    
                }
            }
        }
    }
    
    

    事实上,我被困在这一点上:

                //....
    
                setIl.Emit(OpCodes.Ldarg_0);
                setIl.Emit(OpCodes.Ldstr, propertyName);
                setIl.Emit(OpCodes.Call, onPropertyChangedMethod);
    
                //....
    

    程序编译时没有任何错误。但在运行时,它会产生一个错误: System.InvalidProgramException: 'Common Language Runtime detected an invalid program.' ,在第一次分配时: dynamicClass1.ID = 1; 。如果我注释掉这些行,程序将按预期运行,显然没有属性更改通知。

    问题在哪里?我定义了错误的INtifyProperty实现?我在set方法中定义了对notify事件的错误调用?

    1 回复  |  直到 1 年前
        1
  •  1
  •   canton7    1 年前
    onPropertyChangedIl.Emit(OpCodes.Ldarg_0);
    onPropertyChangedIl.Emit(OpCodes.Ldfld, evtField);
    onPropertyChangedIl.Emit(OpCodes.Dup);
    onPropertyChangedIl.Emit(OpCodes.Brfalse_S, retLabel);
    onPropertyChangedIl.Emit(OpCodes.Ldarg_0);
    onPropertyChangedIl.Emit(OpCodes.Ldarg_1);
    onPropertyChangedIl.Emit(OpCodes.Newobj, typeof(PropertyChangedEventArgs).GetConstructor(new Type[] { typeof(string) }));
    onPropertyChangedIl.Emit(OpCodes.Callvirt, typeof(PropertyChangedEventHandler).GetMethod("Invoke", new Type[] { typeof(object), typeof(PropertyChangedEventArgs) }));
    onPropertyChangedIl.MarkLabel(retLabel);
    onPropertyChangedIl.Emit(OpCodes.Ret);
    

    如果 evtField null ,然后分支到 retLabel ,但你仍然有 this 当你回来的时候放在架子上。你需要爆掉它。

    这个 IL that the compiler generates here 略有不同:

    IL_0000: ldarg.0
    IL_0001: ldfld class [System.ObjectModel]System.ComponentModel.PropertyChangedEventHandler C::PropertyChanged
    IL_0006: dup
    IL_0007: brtrue.s IL_000b
    
    IL_0009: pop
    IL_000a: ret
    
    IL_000b: ldarg.0
    IL_000c: ldarg.1
    IL_000d: newobj instance void [System.ObjectModel]System.ComponentModel.PropertyChangedEventArgs::.ctor(string)
    IL_0012: callvirt instance void [System.ObjectModel]System.ComponentModel.PropertyChangedEventHandler::Invoke(object, class [System.ObjectModel]System.ComponentModel.PropertyChangedEventArgs)
    IL_0017: ret
    

    看看它是如何从方法中有两个退出路径的,如果事件为null,则采取的退出路径有一个额外的 pop .

    (The Debug IL 略有不同:这保持了单身 ret 在方法的末尾,但添加了一个额外的分支来插入 流行音乐 ).