如何:使用反射挂接委托
使用反射加载和运行程序集时,不能使用 C# +=
运算符或 Visual Basic AddHandler 语句等语言功能将事件挂钩。 以下过程介绍如何通过反射获取所需的全部类型来将现有方法挂钩到事件,以及如何使用反射发出以创建动态方法并将其挂钩到事件。
备注
有关事件处理委托的其他挂钩方式,请参阅 EventInfo 类的 AddEventHandler 方法的代码示例。
使用反射挂钩委托
加载包含引发事件的类型的程序集。 程序集通常使用 Assembly.Load 方法加载。 为了简化本示例,当前程序集中使用了派生窗体,因此使用 GetExecutingAssembly 方法加载当前程序集。
Assembly^ assem = Example::typeid->Assembly;
Assembly assem = typeof(Example).Assembly;
Dim assem As Assembly = GetType(Example).Assembly
获取表示类型的 Type 对象,并创建一个该类型的实例。 由于窗体具有无参数构造函数,因此下面的代码中使用了 CreateInstance(Type) 方法。 如果要创建的类型没有无参数构造函数,CreateInstance 方法还有其他几种重载可供使用。 新实例存储为类型 Object,以保持对程序集一无所知的假定。 (通过反射可获取程序集中的类型,无需事先知悉其名称。)
Type^ tExForm = assem->GetType("ExampleForm"); Object^ exFormAsObj = Activator::CreateInstance(tExForm);
Type tExForm = assem.GetType("ExampleForm"); Object exFormAsObj = Activator.CreateInstance(tExForm);
Dim tExForm As Type = assem.GetType("ExampleForm") Dim exFormAsObj As Object = _ Activator.CreateInstance(tExForm)
获取表示该事件的 EventInfo 对象,并使用 EventHandlerType 属性来获取用于处理事件的委托类型。 在以下代码中,获取了 Click 事件的 EventInfo。
EventInfo^ evClick = tExForm->GetEvent("Click"); Type^ tDelegate = evClick->EventHandlerType;
EventInfo evClick = tExForm.GetEvent("Click"); Type tDelegate = evClick.EventHandlerType;
Dim evClick As EventInfo = tExForm.GetEvent("Click") Dim tDelegate As Type = evClick.EventHandlerType
获取表示处理事件的方法的 MethodInfo 对象。 本文后面“示例”部分中的完整程序代码包含一个与 EventHandler 委托的签名匹配的方法,该方法处理 Click 事件,但您也可以在运行时生成动态方法。 有关详细信息,请参阅附带的使用动态方法在运行时生成事件处理程序的过程。
MethodInfo^ miHandler = Type::GetType("Example")->GetMethod("LuckyHandler", BindingFlags::NonPublic | BindingFlags::Instance);
MethodInfo miHandler = typeof(Example).GetMethod("LuckyHandler", BindingFlags.NonPublic | BindingFlags.Instance);
Dim miHandler As MethodInfo = _ GetType(Example).GetMethod("LuckyHandler", _ BindingFlags.NonPublic Or BindingFlags.Instance)
使用 CreateDelegate 方法创建委托的实例。 此方法是静态的(在 Visual Basic 中为
Shared
),因此必须提供委托类型。 建议使用带有 MethodInfo 的 CreateDelegate 重载。Delegate^ d = Delegate::CreateDelegate(tDelegate, this, miHandler);
Delegate d = Delegate.CreateDelegate(tDelegate, this, miHandler);
Dim d As [Delegate] = _ [Delegate].CreateDelegate(tDelegate, Me, miHandler)
获取
add
访问器方法,并调用该方法以将事件挂钩。 所有事件都有一个add
访问器和一个remove
访问器,这些访问器由高级语言的语法隐藏。 例如,C# 使用+=
运算符将事件挂钩,Visual Basic 则使用 AddHandler 语句。 以下代码获取 Click 事件的add
访问器,然后以后期绑定方式对其进行调用,并在委托实例中传递。 参数必须作为数组传递。MethodInfo^ addHandler = evClick->GetAddMethod(); array<Object^>^ addHandlerArgs = { d }; addHandler->Invoke(exFormAsObj, addHandlerArgs);
MethodInfo addHandler = evClick.GetAddMethod(); Object[] addHandlerArgs = { d }; addHandler.Invoke(exFormAsObj, addHandlerArgs);
Dim miAddHandler As MethodInfo = evClick.GetAddMethod() Dim addHandlerArgs() As Object = {d} miAddHandler.Invoke(exFormAsObj, addHandlerArgs)
测试事件。 以下代码显示在代码示例中定义的窗体。 单击窗体可调用事件处理程序。
Application::Run((Form^) exFormAsObj);
Application.Run((Form) exFormAsObj);
Application.Run(CType(exFormAsObj, Form))
使用动态方法在运行时生成事件处理程序
使用轻量动态方法和反射发出可在运行时生成事件处理程序方法。 若要构造事件处理程序,需要委托的返回类型和参数类型。 可通过检查委托的
Invoke
方法来获取这些类型。 以下代码使用GetDelegateReturnType
和GetDelegateParameterTypes
方法获取此信息。 本文后面的“示例”部分提供了这些方法的代码。无需命名 DynamicMethod 即可使用空字符串。 在以下代码中,最后一个参数将动态方法与当前类型相关联,允许委托访问
Example
类的所有公共和私有成员。Type^ returnType = GetDelegateReturnType(tDelegate); if (returnType != void::typeid) throw gcnew ApplicationException("Delegate has a return type."); DynamicMethod^ handler = gcnew DynamicMethod("", nullptr, GetDelegateParameterTypes(tDelegate), Example::typeid);
Type returnType = GetDelegateReturnType(tDelegate); if (returnType != typeof(void)) throw new ArgumentException("Delegate has a return type.", nameof(d)); DynamicMethod handler = new DynamicMethod("", null, GetDelegateParameterTypes(tDelegate), typeof(Example));
Dim returnType As Type = GetDelegateReturnType(tDelegate) If returnType IsNot GetType(Void) Then Throw New ArgumentException("Delegate has a return type.", NameOf(d)) End If Dim handler As New DynamicMethod( _ "", _ Nothing, _ GetDelegateParameterTypes(tDelegate), _ GetType(Example) _ )
生成方法体。 此方法加载字符串、调用带有字符串的 MessageBox.Show 方法重载、从堆栈弹出返回值(因为处理程序没有返回类型)并返回这些值。 若要详细了解如何发出动态方法,请参阅如何:定义和执行动态方法。
ILGenerator^ ilgen = handler->GetILGenerator(); array<Type^>^ showParameters = { String::typeid }; MethodInfo^ simpleShow = MessageBox::typeid->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);
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);
Dim ilgen As ILGenerator = handler.GetILGenerator() Dim showParameters As Type() = {GetType(String)} Dim simpleShow As MethodInfo = _ GetType(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
访问器向事件的调用列表添加委托。Delegate^ dEmitted = handler->CreateDelegate(tDelegate); addHandler->Invoke(exFormAsObj, gcnew array<Object^> { dEmitted });
Delegate dEmitted = handler.CreateDelegate(tDelegate); addHandler.Invoke(exFormAsObj, new Object[] { dEmitted });
Dim dEmitted As [Delegate] = handler.CreateDelegate(tDelegate) miAddHandler.Invoke(exFormAsObj, New Object() {dEmitted})
测试事件。 以下代码加载在代码示例中定义的窗体。 单击该窗体可同时调用预定义的事件处理程序和发出的事件处理程序。
Application::Run((Form^) exFormAsObj);
Application.Run((Form) exFormAsObj);
Application.Run(CType(exFormAsObj, Form))
示例
以下代码示例介绍如何使用反射将现有方法挂钩到事件,以及如何使用 DynamicMethod 类在运行时发出方法并将其挂钩到事件。
#using <System.dll>
#using <System.Windows.Forms.dll>
using namespace System;
using namespace System::Reflection;
using namespace System::Reflection::Emit;
using namespace System::Windows::Forms;
public ref class ExampleForm : public Form
{
public:
ExampleForm() : Form()
{
this->Text = "Click me";
}
};
public ref class Example
{
public:
static void Main()
{
Example^ ex = gcnew 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 = Example::typeid->Assembly;
// 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 =
Type::GetType("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();
array<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 != void::typeid)
throw gcnew ApplicationException("Delegate has a return type.");
DynamicMethod^ handler =
gcnew DynamicMethod("",
nullptr,
GetDelegateParameterTypes(tDelegate),
Example::typeid);
// 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();
array<Type^>^ showParameters = { String::typeid };
MethodInfo^ simpleShow =
MessageBox::typeid->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, gcnew array<Object^> { dEmitted });
// Show the form. Clicking on the form causes the two
// delegates to be invoked.
//
Application::Run((Form^) exFormAsObj);
}
void LuckyHandler(Object^ sender, EventArgs^ e)
{
MessageBox::Show("This event handler just happened to be lying around.");
}
array<Type^>^ GetDelegateParameterTypes(Type^ d)
{
if (d->BaseType != MulticastDelegate::typeid)
throw gcnew ApplicationException("Not a delegate.");
MethodInfo^ invoke = d->GetMethod("Invoke");
if (invoke == nullptr)
throw gcnew ApplicationException("Not a delegate.");
array<ParameterInfo^>^ parameters = invoke->GetParameters();
array<Type^>^ typeParameters = gcnew array<Type^>(parameters->Length);
for (int i = 0; i < parameters->Length; i++)
{
typeParameters[i] = parameters[i]->ParameterType;
}
return typeParameters;
}
Type^ GetDelegateReturnType(Type^ d)
{
if (d->BaseType != MulticastDelegate::typeid)
throw gcnew ApplicationException("Not a delegate.");
MethodInfo^ invoke = d->GetMethod("Invoke");
if (invoke == nullptr)
throw gcnew ApplicationException("Not a delegate.");
return invoke->ReturnType;
}
};
int main()
{
Example::Main();
}
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 = typeof(Example).Assembly;
// 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 ArgumentException("Delegate has a return type.", nameof(d));
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 ArgumentException("Not a delegate.", nameof(d));
MethodInfo invoke = d.GetMethod("Invoke");
if (invoke == null)
throw new ArgumentException("Not a delegate.", nameof(d));
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 ArgumentException("Not a delegate.", nameof(d));
MethodInfo invoke = d.GetMethod("Invoke");
if (invoke == null)
throw new ArgumentException("Not a delegate.", nameof(d));
return invoke.ReturnType;
}
}
Imports System.Reflection
Imports System.Reflection.Emit
Imports System.Windows.Forms
Class ExampleForm
Inherits Form
Public Sub New()
Me.Text = "Click me"
End Sub
End Class
Class Example
Public Shared Sub Main()
Dim ex As New Example()
ex.HookUpDelegate()
End Sub
Private Sub HookUpDelegate()
' Load an assembly, for example using the Assembly.Load
' method. In this case, the executing assembly is loaded, to
' keep the demonstration simple.
'
Dim assem As Assembly = GetType(Example).Assembly
' Get the type that is to be loaded, and create an instance
' of it. Activator.CreateInstance also has an overload that
' takes an array of types representing the types of the
' constructor parameters, if the type you are creating does
' not have a parameterless 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.)
'
Dim tExForm As Type = assem.GetType("ExampleForm")
Dim exFormAsObj As Object = _
Activator.CreateInstance(tExForm)
' Get an EventInfo representing the Click event, and get the
' type of delegate that handles the event.
'
Dim evClick As EventInfo = tExForm.GetEvent("Click")
Dim tDelegate As Type = evClick.EventHandlerType
' If you already have a method with the correct signature,
' you can simply get a MethodInfo for it.
'
Dim miHandler As MethodInfo = _
GetType(Example).GetMethod("LuckyHandler", _
BindingFlags.NonPublic Or BindingFlags.Instance)
' Create an instance of the delegate. Using the overloads
' of CreateDelegate that take MethodInfo is recommended.
'
Dim d As [Delegate] = _
[Delegate].CreateDelegate(tDelegate, Me, 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.
'
Dim miAddHandler As MethodInfo = evClick.GetAddMethod()
Dim addHandlerArgs() As Object = {d}
miAddHandler.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.
'
Dim returnType As Type = GetDelegateReturnType(tDelegate)
If returnType IsNot GetType(Void) Then
Throw New ArgumentException("Delegate has a return type.", NameOf(d))
End If
Dim handler As New DynamicMethod( _
"", _
Nothing, _
GetDelegateParameterTypes(tDelegate), _
GetType(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.
'
Dim ilgen As ILGenerator = handler.GetILGenerator()
Dim showParameters As Type() = {GetType(String)}
Dim simpleShow As MethodInfo = _
GetType(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.
'
Dim dEmitted As [Delegate] = handler.CreateDelegate(tDelegate)
miAddHandler.Invoke(exFormAsObj, New Object() {dEmitted})
' Show the form. Clicking on the form causes the two
' delegates to be invoked.
'
Application.Run(CType(exFormAsObj, Form))
End Sub
Private Sub LuckyHandler(ByVal sender As [Object], _
ByVal e As EventArgs)
MessageBox.Show("This event handler just happened to be lying around.")
End Sub
Private Function GetDelegateParameterTypes(ByVal d As Type) _
As Type()
If d.BaseType IsNot GetType(MulticastDelegate) Then
Throw New ArgumentException("Not a delegate.", NameOf(d))
End If
Dim invoke As MethodInfo = d.GetMethod("Invoke")
If invoke Is Nothing Then
Throw New ArgumentException("Not a delegate.", NameOf(d))
End If
Dim parameters As ParameterInfo() = invoke.GetParameters()
' Dimension this array Length - 1, because VB adds an extra
' element to zero-based arrays.
Dim typeParameters(parameters.Length - 1) As Type
For i As Integer = 0 To parameters.Length - 1
typeParameters(i) = parameters(i).ParameterType
Next i
Return typeParameters
End Function
Private Function GetDelegateReturnType(ByVal d As Type) As Type
If d.BaseType IsNot GetType(MulticastDelegate) Then
Throw New ArgumentException("Not a delegate.", NameOf(d))
End If
Dim invoke As MethodInfo = d.GetMethod("Invoke")
If invoke Is Nothing Then
Throw New ArgumentException("Not a delegate.", NameOf(d))
End If
Return invoke.ReturnType
End Function
End Class