如何:使用反射将委托挂钩
当使用反射来加载和运行程序集时,不能使用 C# += 运算符或 Visual Basic AddHandler 语句等语言功能将事件挂钩。 以下过程显示如何通过用反射获取所需的全部类型将现有方法挂钩到事件,以及如何使用反射发出来创建动态方法并将其挂钩到事件。
说明 |
---|
有关事件处理委托的其他挂钩方式,请参见 EventInfo 类的 AddEventHandler 方法的代码示例。 |
使用反射挂钩委托
-
加载包含引发事件的类型的程序集。 程序集通常使用 Assembly.Load 方法加载。 为了使此示例尽量简单,在当前程序集中使用了派生窗体,所以使用GetExecutingAssembly 方法来加载当前程序集。
-
获取表示类型的 Type 对象,并创建一个该类型的实例。 由于窗体具有默认构造函数,所以在下面的代码中使用了 CreateInstance(Type) 方法。 如果您创建的类型没有默认构造函数,CreateInstance 方法还有其他几种重载可供使用。 新实例存储为类型 Object,以保持对程序集一无所知的状态。(通过反射可以获取程序集中的类型,而无需事先了解其名称。)
-
获取表示该事件的 EventInfo 对象,并使用 EventHandlerType 属性来获取用于处理事件的委托类型。 在下面的代码中,获取了 Click 事件的 EventInfo。
-
获取表示处理事件的方法的 MethodInfo 对象。 本主题后面 Example部分中的完整程序代码包含一个与 EventHandler 委托的签名匹配的方法,该方法处理Click 事件,但您也可以在运行时生成动态方法。 有关详细信息,请参见附带的使用动态方法在运行时生成事件处理程序过程。
-
使用 CreateDelegate 方法创建委托的实例。 此方法是静态的(在 Visual Basic 中为 Shared),所以必须提供委托类型。 建议使用带有 MethodInfo 的CreateDelegate 重载。
-
获取 add 访问器方法,并调用该方法以将事件挂钩。 所有事件都具有一个 add 访问器或 remove 访问器,这些访问器被高级语言的语法隐藏。 例如,C# 使用 += 运算符将事件挂钩,而 Visual Basic 则使用 AddHandler 语句。 下面的代码获取 Click 事件的 add 访问器并以后期绑定方式调用它,并在委托实例中传递。 参数必须作为数组传递。
-
测试事件。 下面的代码显示了在代码示例中定义的窗体。 单击该窗体将调用事件处理程序。
使用动态方法在运行时生成事件处理程序
-
使用轻量动态方法和反射发出可在运行时生成事件处理程序方法。 若要构造事件处理程序,您需要知道返回类型和委托的参数类型。 可以通过检查委托的Invoke 方法来获取这些类型。 下面的代码使用 GetDelegateReturnType 和 GetDelegateParameterTypes 方法获取此信息。 在本主题后面的示例部分中可以找到这些方法的代码。
不需要命名 DynamicMethod,所以可以使用空字符串。 在下面的代码中,最后一个参数将动态方法与当前类型相关联,从而允许委托访问 Example 类的所有公共和私有成员。
-
生成方法体。 此方法加载字符串、调用带有字符串的 MessageBox.Show 方法重载、从堆栈弹出返回值(因为处理程序没有返回类型)并返回这些值。 若要了解有关发出动态方法的更多信息,请参见如何:定义和执行动态方法。
ILGenerator ilgen = handler.GetILGenerator(); Type[] showParameters = { typeof(String) }; MethodInfo simpleShow = typeof(MessageBox).GetMethod("Show", showParameters); ilgen.Emit(OpCodes.Ldstr, "This event handler was constructed at run time."); ilgen.Emit(OpCodes.Call, simpleShow); ilgen.Emit(OpCodes.Pop); ilgen.Emit(OpCodes.Ret);
-
通过调用动态方法的 CreateDelegate 方法完成动态方法。 使用 add 访问器向事件的调用列表中添加委托。
-
测试事件。 下面的代码将加载在代码示例中定义的窗体。 单击该窗体将同时调用预定义的事件处理程序和发出的事件处理程序。
下面的代码示例显示如何使用反射将现有方法挂钩到事件,以及如何使用 DynamicMethod 类在运行时发出方法并将其挂钩到事件。
using System; using System.Reflection; using System.Reflection.Emit; using System.Windows.Forms; class ExampleForm : Form { public ExampleForm() : base() { this.Text = "Click me"; } } class Example { public static void Main() { Example ex = new Example(); ex.HookUpDelegate(); } private void HookUpDelegate() { // Load an assembly, for example using the Assembly.Load // method. In this case, the executing assembly is loaded, to // keep the demonstration simple. // Assembly assem = Assembly.GetExecutingAssembly(); // Get the type that is to be loaded, and create an instance // of it. Activator.CreateInstance has other overloads, if // the type lacks a default constructor. The new instance // is stored as type Object, to maintain the fiction that // nothing is known about the assembly. (Note that you can // get the types in an assembly without knowing their names // in advance.) // Type tExForm = assem.GetType("ExampleForm"); Object exFormAsObj = Activator.CreateInstance(tExForm); // Get an EventInfo representing the Click event, and get the // type of delegate that handles the event. // EventInfo evClick = tExForm.GetEvent("Click"); Type tDelegate = evClick.EventHandlerType; // If you already have a method with the correct signature, // you can simply get a MethodInfo for it. // MethodInfo miHandler = typeof(Example).GetMethod("LuckyHandler", BindingFlags.NonPublic | BindingFlags.Instance); // Create an instance of the delegate. Using the overloads // of CreateDelegate that take MethodInfo is recommended. // Delegate d = Delegate.CreateDelegate(tDelegate, this, miHandler); // Get the "add" accessor of the event and invoke it late- // bound, passing in the delegate instance. This is equivalent // to using the += operator in C#, or AddHandler in Visual // Basic. The instance on which the "add" accessor is invoked // is the form; the arguments must be passed as an array. // MethodInfo addHandler = evClick.GetAddMethod(); Object[] addHandlerArgs = { d }; addHandler.Invoke(exFormAsObj, addHandlerArgs); // Event handler methods can also be generated at run time, // using lightweight dynamic methods and Reflection.Emit. // To construct an event handler, you need the return type // and parameter types of the delegate. These can be obtained // by examining the delegate's Invoke method. // // It is not necessary to name dynamic methods, so the empty // string can be used. The last argument associates the // dynamic method with the current type, giving the delegate // access to all the public and private members of Example, // as if it were an instance method. // Type returnType = GetDelegateReturnType(tDelegate); if (returnType != typeof(void)) throw new ApplicationException("Delegate has a return type."); DynamicMethod handler = new DynamicMethod("", null, GetDelegateParameterTypes(tDelegate), typeof(Example)); // Generate a method body. This method loads a string, calls // the Show method overload that takes a string, pops the // return value off the stack (because the handler has no // return type), and returns. // ILGenerator ilgen = handler.GetILGenerator(); Type[] showParameters = { typeof(String) }; MethodInfo simpleShow = typeof(MessageBox).GetMethod("Show", showParameters); ilgen.Emit(OpCodes.Ldstr, "This event handler was constructed at run time."); ilgen.Emit(OpCodes.Call, simpleShow); ilgen.Emit(OpCodes.Pop); ilgen.Emit(OpCodes.Ret); // Complete the dynamic method by calling its CreateDelegate // method. Use the "add" accessor to add the delegate to // the invocation list for the event. // Delegate dEmitted = handler.CreateDelegate(tDelegate); addHandler.Invoke(exFormAsObj, new Object[] { dEmitted }); // Show the form. Clicking on the form causes the two // delegates to be invoked. // Application.Run((Form) exFormAsObj); } private void LuckyHandler(Object sender, EventArgs e) { MessageBox.Show("This event handler just happened to be lying around."); } private Type[] GetDelegateParameterTypes(Type d) { if (d.BaseType != typeof(MulticastDelegate)) throw new ApplicationException("Not a delegate."); MethodInfo invoke = d.GetMethod("Invoke"); if (invoke == null) throw new ApplicationException("Not a delegate."); ParameterInfo[] parameters = invoke.GetParameters(); Type[] typeParameters = new Type[parameters.Length]; for (int i = 0; i < parameters.Length; i++) { typeParameters[i] = parameters[i].ParameterType; } return typeParameters; } private Type GetDelegateReturnType(Type d) { if (d.BaseType != typeof(MulticastDelegate)) throw new ApplicationException("Not a delegate."); MethodInfo invoke = d.GetMethod("Invoke"); if (invoke == null) throw new ApplicationException("Not a delegate."); return invoke.ReturnType; } }