如何:使用反射将委托挂钩
当使用反射来加载和运行程序集时,不能使用 C# += 运算符或 Visual Basic AddHandler 语句等语言功能将事件挂钩。 以下过程显示如何将现有方法挂钩到事件,以及如何使用反射发出来创建匿名承载的动态方法并将其挂钩到事件。 (在 Silverlight 中,所有动态方法都是匿名承载的。)所有必需的信息都通过反射获取。
说明: |
---|
有关挂钩事件处理委托的其他示例,请参见 EventInfo 类的 AddEventHandler 方法的代码示例。 |
使用反射挂钩委托
-
加载包含引发事件的类型的程序集。 程序集通常使用 Assembly.Load 方法加载。 为使此示例简单,将使用当前加载的程序集中的对象。
-
获取某一未知对象,或者创建在您已加载的程序集中找到的类型的对象。 为使示例简单,使用的对象是为示例的输出提供的 TextBlock;但是,TextBlock 存储为 Object 类型,以保持对该对象或其来自的程序集一无所知的状态。
-
获取表示该事件的 EventInfo 对象,并使用 EventHandlerType 属性来获取用于处理事件的委托类型。 在下面的代码中,获取了 MouseLeftButtonUp 事件的 EventInfo。 此示例按名称查找事件,但可以使用 Type.GetEvents 方法来查看存在哪些事件。
-
获取表示处理事件的方法的 MethodInfo 对象。 本主题后面示例部分中的完整程序代码包含一个名为 LuckyHandler 的方法,该方法与处理 MouseLeftButtonUp 事件的委托的签名兼容。 (附带的使用动态方法在运行时生成事件处理程序过程说明如何在运行时生成动态事件处理程序。)
说明: 如果某一委托的参数类型是某一方法的参数的子类,并且该方法的返回类型是该委托的返回类型的子类,则该方法与该委托兼容。 在此情况下,该委托类型的参数MouseButtonEventHandler 是该方法的参数 LuckyHandler 的子类。 请参见 Delegate。
-
使用 CreateDelegate 方法创建委托的实例。 此方法是静态的(在 Visual Basic 中为 Shared),所以必须将委托类型作为参数提供。 我们建议使用带有 MethodInfo 的CreateDelegate 重载。
-
获取 add 访问器方法,并调用该方法以将事件挂钩。 所有事件都具有一个 add 访问器或 remove 访问器,这些访问器被高级语言的语法隐藏。 例如,C# 使用 += 运算符将事件挂钩,而 Visual Basic 则使用 AddHandler 语句。 下面的代码获取 MouseLeftButtonUp 事件的 add 访问器并以后期绑定方式调用它,并在委托实例中传递。 参数必须作为数组传递。
说明: 一个略微简单的技术是使用 EventInfo.AddEventHandler 处理程序方法,如附带的使用动态方法在运行时生成事件处理程序过程所示。
-
测试事件。 下面的代码显示针对 TextBlock 的一条消息,并且请求您通过单击演示事件处理程序。
使用动态方法在运行时生成事件处理程序
-
使用轻量动态方法和反射发出可在运行时生成事件处理程序方法。 若要构造事件处理程序,您需要知道返回类型和委托的参数类型。 可以通过检查委托的 Invoke 方法来获取这些类型。 下面的代码使用 GetDelegateReturnType 和 GetDelegateParameterTypes 方法通过反射获取此信息。 在本主题后面的示例部分中可以找到这些方法的代码。
不需要命名 DynamicMethod,所以可以使用空字符串。
-
生成方法体。 此方法加载字符串、调用带有单个字符串的 Show 方法重载、从堆栈弹出返回值(因为处理程序没有返回类型)并返回这些值。 若要了解有关发出动态方法的更多信息,请参见如何:定义和执行动态方法。
ILGenerator ilgen = handler.GetILGenerator(); Type[] showParameters = { typeof(string) }; MethodInfo simpleShow = typeof(System.Windows.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 访问器向事件的调用列表中添加委托。
-
测试事件。 下面的代码显示针对 TextBlock 的一条消息,并且请求您通过单击演示事件处理程序。
下面的代码示例显示如何使用反射将现有方法挂钩到事件,以及如何使用 DynamicMethod 类在运行时发出方法并将其挂钩到事件。
using System; using System.Reflection; using System.Reflection.Emit; class Example { publicstaticvoid Demo(System.Windows.Controls.TextBlock outputBlock) { // Get the type whose event is to be handled. This example// uses the TextBlock that is provided. 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.)//object obj = outputBlock; // Get an EventInfo that represents the MouseLeftButtonUp // event, and get the type of delegate that handles the event.// EventInfo evMouseUp = outputBlock.GetType().GetEvent("MouseLeftButtonUp"); Type tDelegate = evMouseUp.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.Static); // Create an instance of the delegate. Using the overloads// of CreateDelegate that take MethodInfo is recommended.// Delegate d = Delegate.CreateDelegate(tDelegate, 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.// Note that you can also use EventInfo.AddEventHandler, and // avoid making a late-bound call.// MethodInfo miAddHandler = evMouseUp.GetAddMethod(); object[] addHandlerArgs = { d }; miAddHandler.Invoke(obj, 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. // Type returnType = GetDelegateReturnType(tDelegate); if (returnType != typeof(void)) { thrownew InvalidOperationException("Delegate has a return type."); } DynamicMethod handler = new DynamicMethod("", null, GetDelegateParameterTypes(tDelegate)); // 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(System.Windows.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 EventInfo.AddEventHandler method to add the // delegate to the invocation list for the event. Alternatively// you could use the "add" accessor, as shown earlier in this// example. Delegate dEmitted = handler.CreateDelegate(tDelegate); evMouseUp.AddEventHandler(obj, dEmitted); // Clicking on the TextBlock now causes the two delegates to// be invoked.// outputBlock.Text += "Click here to invoke the two delegates.\n"; } privatestaticvoid LuckyHandler(object sender, EventArgs e) { System.Windows.MessageBox.Show( "This event handler just happened to be lying around."); } privatestatic Type[] GetDelegateParameterTypes(Type d) { if (d.BaseType != typeof(MulticastDelegate)) { thrownew InvalidOperationException("Not a delegate."); } MethodInfo invoke = d.GetMethod("Invoke"); if (invoke == null) { thrownew InvalidOperationException("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; } privatestatic Type GetDelegateReturnType(Type d) { if (d.BaseType != typeof(MulticastDelegate)) { thrownew InvalidOperationException("Not a delegate."); } MethodInfo invoke = d.GetMethod("Invoke"); if (invoke == null) { thrownew InvalidOperationException("Not a delegate."); } return invoke.ReturnType; } }
-
代码包含编译所需的 C# using 语句(在 Visual Basic 中为 Imports)。
-
该代码包含一个具有一个参数的 static(在 Visual Basic 中为 Shared)Demo 方法和一个用于显示输出的 TextBlock。 有关作为基于 Silverlight 的简单应用程序的一部分生成该代码的说明,请参见生成使用 Demo 方法和 TextBlock 控件的示例。 可以将本示例中的所有代码放到一个源文件中。