方法 : リフレクションを使用してデリゲートをフックする
更新 : 2007 年 11 月
リフレクションを使用して、アセンブリを読み込んで実行する場合、C# の += 演算子や Visual Basic の AddHandler ステートメントのような言語機能を使用してイベントをフックすることはできません。次の手順では、必要なすべての型をリフレクションによって取得することで、既存のメソッドをイベントにフックする方法と、リフレクション出力を使用して動的メソッドを作成し、それをイベントにフックする方法を示します。
メモ : |
---|
イベント処理デリゲートをフックするもう 1 つの方法については、EventInfo クラスの AddEventHandler メソッドのコード例を参照してください。 |
リフレクションを使用してデリゲートをフックするには
イベントを発生させる型を格納するアセンブリを読み込みます。通常、アセンブリは Assembly.Load メソッドで読み込まれます。この例を簡単にするために、現在のアセンブリの派生フォームを使用します。したがって、GetExecutingAssembly メソッドを使用して、現在のアセンブリを読み込みます。
Dim assem As [Assembly] = [Assembly].GetExecutingAssembly()
Assembly assem = Assembly.GetExecutingAssembly();
型を表す Type オブジェクトを取得し、型のインスタンスを作成します。このフォームは既定のコンストラクタを持つため、次のコードでは CreateInstance(Type) メソッドを使用します。作成する型が既定のコンストラクタを持たない場合に使用できる CreateInstance メソッドの他のオーバーロードが複数あります。アセンブリについては何もわからないという架空の状態を保つために、新しいインスタンスは Object 型として格納されます (リフレクションを使用すると、事前に型名がわからなくてもアセンブリ内の型を取得できます)。
Dim tExForm As Type = assem.GetType("ExampleForm") Dim exFormAsObj As Object = _ Activator.CreateInstance(tExForm)
Type tExForm = assem.GetType("ExampleForm"); Object exFormAsObj = Activator.CreateInstance(tExForm);
イベントを表す EventInfo オブジェクトを取得し、EventHandlerType プロパティを使用して、イベントの処理に使用するデリゲートの型を取得します。次のコードでは、Click イベントの EventInfo が取得されます。
Dim evClick As EventInfo = tExForm.GetEvent("Click") Dim tDelegate As Type = evClick.EventHandlerType
EventInfo evClick = tExForm.GetEvent("Click"); Type tDelegate = evClick.EventHandlerType;
イベントを処理するメソッドを表す MethodInfo オブジェクトを取得します。このトピックで後ほど示す「使用例」セクションの完全なプログラム コードには、Click イベントを処理する EventHandler デリゲートのシグネチャと一致するメソッドが含まれていますが、実行時に動的メソッドを生成することもできます。詳細については、「動的メソッドを使用して実行時にイベント ハンドラを生成するには」の手順を参照してください。
Dim miHandler As MethodInfo = _ GetType(Example).GetMethod("LuckyHandler", _ BindingFlags.NonPublic Or BindingFlags.Instance)
MethodInfo miHandler = typeof(Example).GetMethod("LuckyHandler", BindingFlags.NonPublic | BindingFlags.Instance);
CreateDelegate メソッドを使用して、デリゲートのインスタンスを作成します。このメソッドは static (Visual Basic では Shared) であるため、デリゲート型を指定する必要があります。MethodInfo を受け取る CreateDelegate のオーバーロードを使用することをお勧めします。
Dim d As [Delegate] = _ [Delegate].CreateDelegate(tDelegate, Me, miHandler)
Delegate d = Delegate.CreateDelegate(tDelegate, this, miHandler);
add アクセサ メソッドを取得し、このメソッドを呼び出してイベントをフックします。すべてのイベントに、add アクセサと remove アクセサがあります。これらのアクセサは高水準言語の構文によって隠ぺいされます。たとえば、C# では += 演算子を使用してイベントをフックし、Visual Basic では AddHandler ステートメントを使用します。次のコードでは、Click イベントの add アクセサを取得し、遅延バインディングによってこれを呼び出して、デリゲート インスタンスに渡します。引数は配列として渡す必要があります。
Dim miAddHandler As MethodInfo = evClick.GetAddMethod() Dim addHandlerArgs() As Object = { d } miAddHandler.Invoke(exFormAsObj, addHandlerArgs)
MethodInfo addHandler = evClick.GetAddMethod(); Object[] addHandlerArgs = { d }; addHandler.Invoke(exFormAsObj, addHandlerArgs);
イベントをテストします。次のコードで、このコード例で定義したフォームを表示します。フォームをクリックすると、イベント ハンドラが呼び出されます。
Application.Run(CType(exFormAsObj, Form))
Application.Run((Form) exFormAsObj);
動的メソッドを使用して実行時にイベント ハンドラを生成するには
軽量の動的メソッドとリフレクション出力を使用して、イベント ハンドラのメソッドを実行時に生成できます。イベント ハンドラを作成するには、デリゲートの戻り値の型とパラメータの型が必要です。これらは、デリゲートの Invoke メソッドを調べることによって入手できます。次のコードでは、GetDelegateReturnType メソッドと GetDelegateParameterTypes メソッドを使用して、この情報を取得します。これらのメソッドのコードは、このトピックで後ほど示す「使用例」セクションにあります。
DynamicMethod に名前を付ける必要はないため、空の文字列を使用できます。次のコードでは、最後の引数が動的メソッドを現在の型に関連付け、デリゲートが Example クラスのすべてのパブリック メンバとプライベート メンバにアクセスできるようにします。
Dim returnType As Type = GetDelegateReturnType(tDelegate) If returnType IsNot GetType(Void) Then Throw New ApplicationException("Delegate has a return type.") End If Dim handler As New DynamicMethod( _ "", _ Nothing, _ GetDelegateParameterTypes(tDelegate), _ GetType(Example) _ )
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));
メソッド本体を生成します。このメソッドは文字列を読み込み、文字列を受け取る MessageBox.Show メソッドのオーバーロードを呼び出します。次に、戻り値をスタックからポップし (ハンドラには戻り値の型がないため)、返されます。動的メソッドの出力の詳細については、「方法 : 動的メソッドを定義および実行する」を参照してください。
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)
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 アクセサを使用して、イベントの呼び出しリストにデリゲートを追加します。
Dim dEmitted As [Delegate] = handler.CreateDelegate(tDelegate) miAddHandler.Invoke(exFormAsObj, New Object() { dEmitted })
Delegate dEmitted = handler.CreateDelegate(tDelegate); addHandler.Invoke(exFormAsObj, new Object[] { dEmitted });
イベントをテストします。次のコードでは、このコード例で定義したフォームを読み込みます。フォームをクリックすると、定義済みイベント ハンドラと出力されたイベント ハンドラの両方が呼び出されます。
Application.Run(CType(exFormAsObj, Form))
Application.Run((Form) exFormAsObj);
使用例
リフレクションを使用して既存のメソッドをイベントにフックする方法、および DynamicMethod クラスを使用してメソッドを実行時に出力し、イベントにフックする方法を次のコード例に示します。
Imports System
Imports System.Reflection
Imports System.Reflection.Emit
Imports System.Windows.Forms
Class ExampleForm
Inherits Form
Public Sub New()
Me.Text = "Click me"
End Sub 'New
End Class 'ExampleForm
Class Example
Public Shared Sub Main()
Dim ex As New Example()
ex.HookUpDelegate()
End Sub 'Main
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] = [Assembly].GetExecutingAssembly()
' 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 ApplicationException("Delegate has a return type.")
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 ApplicationException("Not a delegate.")
End If
Dim invoke As MethodInfo = d.GetMethod("Invoke")
If invoke Is Nothing Then
Throw New ApplicationException("Not a delegate.")
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 ApplicationException("Not a delegate.")
End If
Dim invoke As MethodInfo = d.GetMethod("Invoke")
If invoke Is Nothing Then
Throw New ApplicationException("Not a delegate.")
End If
Return invoke.ReturnType
End Function
End Class
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;
}
}
コードのコンパイル方法
このコードには、コンパイルに必要な C# の using ステートメント (Visual Basic では Imports) が含まれています。
コマンド ラインからコンパイルするために、追加のアセンブリ参照は必要ありません。この例はコンソール アプリケーションであるため、Visual Studio では、System.Windows.Forms.dll への参照を追加する必要があります。
csc.exe、vbc.exe、または cl.exe を使用して、コマンド ラインでコードをコンパイルします。Visual Studio でコードをコンパイルするには、コンソール アプリケーション プロジェクト テンプレートにコードを配置します。