WPF c# 使用Emit自动创建通知类

发布时间 2023-08-08 12:53:07作者: trykle

参考
概念参考自 https://www.codewrecks.com/post/old/2008/08/implement-inotifypropertychanged-with-dynamic-code-generation/
和DevExpress的MVVM框架 Runtime-generated POCO View Models
代码实现来自ChatGPT 抽卡

原始类

public class Class1
{
    public virtual int MyProperty { get; set; }
}

希望调用一行代码后自动帮我创建的派生类

public class Class2 : Class1, INotifyPropertyChanged
{
    public override int MyProperty
    {
        get { return base.MyProperty; }
        set
        {
            base.MyProperty = value;
            if (PropertyChanged != null)
            {
                PropertyChanged.Invoke(this, new PropertyChangedEventArgs("MyProperty"));
            } 
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

Emit动态创建实现

注意: .netcore 没有保存Emit程序集和AppDomain的实现

/// <summary>
/// 根据原始类自动创建通知类
/// </summary>
public static class NotifyTypeEmit
{
	public static T CreateInstance<T>()
	{
		return (T)CreateInstance(typeof(T));
	}
	public static object CreateInstance(Type baseType)
	{
		var type = CreateType(baseType);
		return Activator.CreateInstance(type);
	}


	/// <summary>
	///根据基础类创建派生类,自带缓存
	/// </summary>
	/// <param name="baseType"></param>
	/// <returns></returns>
	public static Type CreateType(Type baseType)
	{
		if (!s_extensionTypes.ContainsKey(baseType))
		{
			var type = _CreateType(baseType);
			s_extensionTypes.TryAdd(baseType, type);
		}

		return s_extensionTypes[baseType];
	}

	private static ConcurrentDictionary<Type, Type> s_extensionTypes
		= new ConcurrentDictionary<Type, Type>();


	private static Type _CreateType(Type baseType)
	{
		var assemblyName = new AssemblyName("TrykleMvvmToolkit.NotifyTypeEmit.Assembly");
		//var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave);
		// .net 4.0 可以用这个代替
		var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave);
		var moduleBuilder = assemblyBuilder.DefineDynamicModule("TrykleMvvmToolkit.NotifyTypeEmit.Module", "NortiyTypeEmit_Test.dll");

		var typeBuilder = moduleBuilder.DefineType($"{baseType.Name}_EXTENSION", TypeAttributes.Public | TypeAttributes.Class, baseType, new[] { typeof(INotifyPropertyChanged) });
		// 实现 INotifyPropertyChanged 接口
		var eventBuilder = typeBuilder.DefineEvent("PropertyChanged", EventAttributes.None, typeof(PropertyChangedEventHandler));
		var eventFieldBuilder = typeBuilder.DefineField("PropertyChanged", typeof(PropertyChangedEventHandler), FieldAttributes.Private);
		//
		var interfaceMethodInfo = typeof(INotifyPropertyChanged).GetMethod("add_PropertyChanged");
		var addMethod = typeBuilder.DefineMethod(interfaceMethodInfo.Name, MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.NewSlot | MethodAttributes.Virtual, typeof(void), new Type[] { typeof(PropertyChangedEventHandler) });
		var ilAddMethod = addMethod.GetILGenerator();
		ilAddMethod.Emit(OpCodes.Ldarg_0);
		ilAddMethod.Emit(OpCodes.Ldarg_0);
		ilAddMethod.Emit(OpCodes.Ldfld, eventFieldBuilder);
		ilAddMethod.Emit(OpCodes.Ldarg_1);
		ilAddMethod.Emit(OpCodes.Call, typeof(Delegate).GetMethod("Combine", new Type[] { typeof(Delegate), typeof(Delegate) }));
		ilAddMethod.Emit(OpCodes.Castclass, typeof(PropertyChangedEventHandler));
		ilAddMethod.Emit(OpCodes.Stfld, eventFieldBuilder);
		ilAddMethod.Emit(OpCodes.Ret);
		typeBuilder.DefineMethodOverride(addMethod, interfaceMethodInfo);
		eventBuilder.SetAddOnMethod(addMethod);
		//
		interfaceMethodInfo = typeof(INotifyPropertyChanged).GetMethod("remove_PropertyChanged");
		var removeMethod = typeBuilder.DefineMethod(interfaceMethodInfo.Name, MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.NewSlot | MethodAttributes.Virtual, typeof(void), new Type[] { typeof(PropertyChangedEventHandler) });
		var ilRemoveMethod = removeMethod.GetILGenerator();
		ilRemoveMethod.Emit(OpCodes.Ldarg_0);
		ilRemoveMethod.Emit(OpCodes.Ldarg_0);
		ilRemoveMethod.Emit(OpCodes.Ldfld, eventFieldBuilder);
		ilRemoveMethod.Emit(OpCodes.Ldarg_1);
		ilRemoveMethod.Emit(OpCodes.Call, typeof(Delegate).GetMethod("Remove", new Type[] { typeof(Delegate), typeof(Delegate) }));
		ilRemoveMethod.Emit(OpCodes.Castclass, typeof(PropertyChangedEventHandler));
		ilRemoveMethod.Emit(OpCodes.Stfld, eventFieldBuilder);
		ilRemoveMethod.Emit(OpCodes.Ret);
		typeBuilder.DefineMethodOverride(removeMethod, interfaceMethodInfo);
		eventBuilder.SetRemoveOnMethod(removeMethod);

		foreach (var propertyInfo in baseType.GetProperties(BindingFlags.Public | BindingFlags.Instance))
		{
			if (propertyInfo.GetGetMethod() == null || (propertyInfo.GetSetMethod() == null))
			{
				continue;
			}
			var propertyName = propertyInfo.Name;
			var getPropertyName = $"get_{propertyName}";
			var setPropertyName = $"set_{propertyName}";
			var propertyType = propertyInfo.PropertyType;

			var propertyBuilder = typeBuilder.DefineProperty(propertyName, PropertyAttributes.None, propertyType, null);
			var methodAttributes = MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.Virtual;

			var getterMethodBuilder = typeBuilder.DefineMethod(getPropertyName, methodAttributes, propertyType, Type.EmptyTypes);
			var getterIL = getterMethodBuilder.GetILGenerator();
			getterIL.Emit(OpCodes.Ldarg_0);
			getterIL.Emit(OpCodes.Call, baseType.GetProperty(propertyName).GetGetMethod());
			getterIL.Emit(OpCodes.Ret);
			propertyBuilder.SetGetMethod(getterMethodBuilder);

			var setterMethodBuilder = typeBuilder.DefineMethod(setPropertyName, methodAttributes, null, new[] { propertyType });
			var setterIL = setterMethodBuilder.GetILGenerator();
			setterIL.Emit(OpCodes.Ldarg_0);
			setterIL.Emit(OpCodes.Ldarg_1);
			setterIL.Emit(OpCodes.Call, baseType.GetProperty(propertyName).GetSetMethod());
			setterIL.Emit(OpCodes.Ldarg_0);
			setterIL.Emit(OpCodes.Ldfld, eventFieldBuilder);
			var label = setterIL.DefineLabel();
			setterIL.Emit(OpCodes.Brfalse_S, label);
			setterIL.Emit(OpCodes.Ldarg_0);
			setterIL.Emit(OpCodes.Ldfld, eventFieldBuilder);
			setterIL.Emit(OpCodes.Ldarg_0);
			setterIL.Emit(OpCodes.Ldstr, propertyName);
			setterIL.Emit(OpCodes.Newobj, typeof(PropertyChangedEventArgs).GetConstructor(new[] { typeof(string) })
			);
			setterIL.Emit(OpCodes.Callvirt, typeof(PropertyChangedEventHandler).GetMethod("Invoke"));
			setterIL.MarkLabel(label);
			setterIL.Emit(OpCodes.Ret);
			propertyBuilder.SetSetMethod(setterMethodBuilder);
		}

		//end
		typeBuilder.CreateType();
		assemblyBuilder.Save("NortiyTypeEmit_Test.dll");
		return typeBuilder;
	}
}

测试

static void Main(string[] args)
{ 
    var cls1 = NotifyTypeEmit.CreateInstance<Class1>();
    (cls1 as INotifyPropertyChanged).PropertyChanged += (sender, prop) =>
     {
         Console.WriteLine($"Property changed: {prop.PropertyName}");
     };

    cls1.MyProperty = 2;
    Console.Read(); 
}