通用的事件处理程序工厂






4.83/5 (19投票s)
一个工厂,它动态创建辅助类来挂接任何签名的事件。
引言
.NET Framework最强大的功能之一是反射。它使我们能够动态加载任何程序集,枚举其类类型,甚至实例化一个类并调用其属性和方法。我们可以在不知道要使用的程序集的任何先验知识的情况下做到这一切。
当然,在实践中,我们大多知道一些关于要使用的程序集的信息,以便合理地使用它们。这可以是类的名称、方法的名称,或者附加到类或方法上的自定义属性。例如,测试框架NUnit会解析程序集中的类和方法属性,以确定要执行的测试。
然而,有一点模糊了.NET反射的美好前景:事件!想象一下,你有一个未知的程序集“UnknownAssembly.dll”,它包含一个未知的类“EventEmitter
”。这个类可以发出一些事件,你想在其中一个事件被触发时收到通知。你甚至想知道事件参数的值。
通常,人们会为该事件附加一个事件处理程序,当事件被触发时,该事件处理程序将被调用。但是,要附加到事件的事件处理程序必须具有该事件的正确签名。如果你在编写代码时知道要处理的事件的委托,这就不是问题。你只需编写相应的事件处理程序。但如果你不得不处理未知事件,你就会遇到问题。
通用事件处理程序
我最近的一个项目中遇到了这个问题,并试图找到一个通用的解决方案。我的目标是将所有事件映射到一个单一的事件处理方法,该方法的签名是
public delegate void CommonEventDelegate(Type EventType, object[] Arguments);
这样,就可以访问所有与事件相关的信息。EventType
包含已触发事件的类型信息,而Arguments
对象数组包含已触发事件的参数。如果没有参数的事件,该数组将为空。
为了弥合特定事件和通用事件处理程序之间的差距,我开发了一个类工厂,它在运行时为给定的事件创建一个特殊的辅助类并实例化它。
对于具有委托类型的事件
public delegate void MyEventDelegate(int Counter, string Name, DateTime Time);
生成的辅助类的C#表示将如下所示
public class MyEventHelper
{
private Type type;
public event CommonEventDelegate CommonEvent;
public MyEventHelper(EventInfo Info)
{
type = Info.EventHandlerType;
}
public void MyEventHandler(int Counter, string Name, DateTime Time)
{
object[] args = new object[3];
args[0] = Counter;
args[1] = Name;
args[2] = Time;
// Check, if event handler is attached to event
if (CommonEvent != null)
CommonEvent(type, args);
}
}
通用事件处理程序库由一个EventHandlerFactory
类组成,该类保存所有已创建的辅助类并防止重复创建辅助类。真正起作用的是EventHandlerTypeEmiter
类。它创建一个动态程序集和一个动态模块来托管创建的辅助类。
AssemblyName myAsmName = new AssemblyName();
myAsmName.Name = assemblyName + "Helper";
asmBuilder = Thread.GetDomain().DefineDynamicAssembly(myAsmName,
AssemblyBuilderAccess.Run);
helperModule = asmBuilder.DefineDynamicModule(assemblyName + "Module", true);
然后它创建新的辅助类类型及其字段
TypeBuilder helperTypeBld = helperModule.DefineType(HandlerName +
"Helper", TypeAttributes.Public);
// Define fields
FieldBuilder typeField = helperTypeBld.DefineField("eventType",
typeof(Type), FieldAttributes.Private);
FieldBuilder eventField = helperTypeBld.DefineField("CommonEvent",
typeof(CommonEventHandlerDlgt),
FieldAttributes.Private);
EventBuilder commonEvent = helperTypeBld.DefineEvent("CommonEvent",
EventAttributes.None,
typeof(CommonEventHandlerDlgt));
现在,可以创建构造函数方法
Type objType = Type.GetType("System.Object");
ConstructorInfo objCtor = objType.GetConstructor(new Type[0]);
// Build constructor with arguments (Type)
Type[] ctorParams = new Type[] { typeof(EventInfo) };
ConstructorBuilder ctor = helperTypeBld.DefineConstructor(MethodAttributes.Public,
CallingConventions.Standard, ctorParams);
最后,构造函数的代码通过ILGenerator
类发出
// Call constructor of base class
ILGenerator ctorIL = ctor.GetILGenerator();
ctorIL.Emit(OpCodes.Ldarg_0);
ctorIL.Emit(OpCodes.Call, objCtor);
// store first argument to typeField
ctorIL.Emit(OpCodes.Ldarg_0);
ctorIL.Emit(OpCodes.Ldarg_1);
ctorIL.Emit(OpCodes.Stfld, typeField);
// return
ctorIL.Emit(OpCodes.Ret);
正如可以看到的,这些只是构成构造函数代码的几个IL语句。生成类型化事件处理程序的代码要长一些,可以在提供的源代码中查看。
演示解决方案
除了通用事件处理程序的源代码,你还可以下载一个演示解决方案,它由三个项目组成
- CommonEventHandler:通用事件处理程序库。
- CommonEventHandlerTest:一个测试项目,展示了如何在典型场景中使用该库。
- UnknownAssembly:一个包含一个发出未知类型事件的类的程序集。
运行CommonEventHandlerTest.exe时,请确保UnknownAssembly.dll位于可执行文件的同一目录中。在“CommonEventHandlerTest”项目中,一个名为TestDllEvents()
的方法在运行时加载“UnknownAssembly.dll”程序集。我们假设,我们只知道这个未知程序集必须有一个名为“EmitEvents()
”的类。我们不需要知道该类发出多少种类型的事件。使用反射,会解析DLL中的所有类型,直到找到一个名为“EmitEvents()
”的类。
foreach (Type type in unknownAssembly.GetTypes())
{
MethodInfo mi = type.GetMethod("EmitEvents");
if (mi != null)
{
// We have found the class we were looking for!
…
}
}
类型被构造……
// First, instantiate the class
// Get the default constructor ...
ConstructorInfo ci = type.GetConstructor(BindingFlags.Instance |
BindingFlags.Public,
null, CallingConventions.HasThis,
new Type[0], null);
if (ci == null)
continue; // Do nothing, if no default constructor found
// ... and create the class by invoking the default constructor
object unknownClass = ci.Invoke(new object[0]);
我们的EventHandlerFactory
也……
// Then, create the Event Handler Factory
EventHandlerFactory factory = new EventHandlerFactory("EventHandlerTest");
现在,我们可以读取我们未知类中定义的事件,并为它们创建特定的事件处理程序。使用Delegate
类,我们为每个创建的事件处理程序助手创建一个委托,并将该委托链接到事件。
// So, now search the events, that class can emit
EventInfo[] events = type.GetEvents();
// and create a customized event handler for them ...
foreach (EventInfo info in events)
{
// Create the event handler
object eventHandler = factory.GetEventHandler(info);
// Create a delegate, which points to the custom event handler
Delegate customEventDelegate =
Delegate.CreateDelegate(info.EventHandlerType, eventHandler,
"CustomEventHandler");
// Link event handler to event
info.AddEventHandler(unknownClass, customEventDelegate);
之后,我们仍然需要将我们自己的通用事件处理程序“MyEventCallback
”链接到我们的辅助类发出的通用事件。
// Map our own event handler to the common event
EventInfo commonEventInfo =
eventHandler.GetType().GetEvent("CommonEvent");
Delegate commonDelegate =
Delegate.CreateDelegate(commonEventInfo.EventHandlerType,
this, "MyEventCallback");
commonEventInfo.AddEventHandler(eventHandler, commonDelegate);
}
现在,所有准备工作都已完成,可以调用UnknownAssembly的“EmitEvents
”方法,看看我们的通用事件处理程序“MyEventCallback
”是否被正确调用。
// We can now call the 'EmitEvent' method with no arguments
mi.Invoke(unknownClass, new object[0]);
结论
通用事件处理程序库帮助我们处理任意事件。不再需要在编译时提供相应的事件处理方法。相反,可以在运行时创建必要的事件处理程序类,并将事件路由到一个通用、定义良好的事件处理程序。提供的代码当然可以进行优化。欢迎评论。玩得开心!