閱讀英文

共用方式為


如何使用反射綁定委派

當您使用反映來載入和執行元件時,無法使用 C# += 運算符或 Visual Basic AddHandler 語句 等語言功能來連結事件。 下列程序示範如何使用反射來取得所有必要類型,並將現有的方法連接到事件,以及如何使用反射發射來建立動態方法,並將其連接至事件。

注意

如需將事件處理委派註冊至另一種方式,請參閱AddEventHandler類別的EventInfo方法的程式碼範例。

使用反射設置委派

  1. 載入含有可以引發事件的類型之組件。 組件通常會使用 Assembly.Load 方法來載入。 為了簡化此範例,會在當前組件中使用衍生類型,因此使用 GetExecutingAssembly 方法來載入當前的組件。

    Assembly assem = typeof(Example).Assembly;
    
  2. 取得一個代表類型的物件,然後創建該類型的實例。 方法 CreateInstance(Type) 用於下列程式代碼中,因為窗體具有無參數建構函式。 如果您建立的類型 CreateInstance 沒有無參數建構函式,則可以使用此方法的其他幾個多載。 新的實例會儲存為類型 Object ,以維持對元件一無所知的假象。 (反射可讓您在元件中取得類型,而不需要事先知道其名稱。)

    Type tExForm = assem.GetType("ExampleForm");
    Object exFormAsObj = Activator.CreateInstance(tExForm);
    
  3. 取得一個代表事件的EventInfo物件,並使用EventHandlerType屬性來獲取用來處理事件的委派類型。 在下列程式碼中,會取得EventInfo事件的Click

    EventInfo evClick = tExForm.GetEvent("Click");
    Type tDelegate = evClick.EventHandlerType;
    
  4. 取得MethodInfo物件,用於表示處理事件的方法。 本文稍後的 Example 區段中的完整程式代碼包含一個符合委派EventHandler簽章的方法,該方法會處理Click事件,但您也可以在執行時產生動態方法。 如需詳細資訊,請參閱隨附的程式,以使用動態方法在運行時間產生事件處理程式。

    MethodInfo miHandler =
        typeof(Example).GetMethod("LuckyHandler",
            BindingFlags.NonPublic | BindingFlags.Instance);
    
  5. 使用 CreateDelegate 方法建立委派的實例。 此方法是靜態的 (Shared 在 Visual Basic 中),因此必須提供委派類型。 建議使用那些接受 CreateDelegateMethodInfo 多載。

    Delegate d = Delegate.CreateDelegate(tDelegate, this, miHandler);
    
  6. 取得 add 的存取子方法,並呼叫該方法以連接事件。 所有事件都有一個add 存取子和一個remove 存取子,這些存取子被高階語言的語法所隱藏。 例如,C# 會使用 += 運算符來連結事件,而 Visual Basic 會使用 AddHandler 語句。 下列程式代碼會取得 add 事件的 Click 存取子,並以晚期綁定方式叫用它,傳入委派實例。 參數必須以陣列的形式傳遞。

    MethodInfo addHandler = evClick.GetAddMethod();
    Object[] addHandlerArgs = { d };
    addHandler.Invoke(exFormAsObj, addHandlerArgs);
    
  7. 測試事件。 下列程式代碼顯示程式代碼範例中定義的表單。 點擊表單會叫用事件處理程式。

    Application.Run((Form) exFormAsObj);
    

使用動態方法在運行時間產生事件處理程式

  1. 在執行時,可以使用輕量動態方法和反射發射來產生事件處理程式方法。 若要建構事件處理程式,您需要委派的傳回類型和參數類型。 藉由檢查委派的 Invoke 方法,即可取得這些。 下列程式代碼會使用 GetDelegateReturnTypeGetDelegateParameterTypes 方法來取得這項資訊。 您可以在本文稍後的一節中找到這些方法的程序代碼。

    不需要為 命名 DynamicMethod,因此可以使用空字串。 在下列程式代碼中,最後一個參數會將動態方法與目前類型產生關聯,讓委派能存取類別 Example 的所有公用和私用成員。

    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));
    
  2. 產生方法主體。 這個方法會載入字串、呼叫接受字串的方法 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);
    
  3. 藉由呼叫其 CreateDelegate 方法來完成動態方法。 使用 add 存取子 (accessor) 將委派新增至事件的調用列表。

    Delegate dEmitted = handler.CreateDelegate(tDelegate);
    addHandler.Invoke(exFormAsObj, new Object[] { dEmitted });
    
  4. 測試事件。 下列程式代碼會載入程式代碼範例中定義的表單。 點擊表單會同時觸發預先定義的事件處理程式和發出的事件處理程式。

    Application.Run((Form) exFormAsObj);
    

範例

下列程式代碼範例示範如何使用反映將現有的方法連結至事件,以及如何使用 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 = 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;
    }
}

另請參閱


其他資源

訓練

學習路徑

實作委派和事件 - Training

瞭解如何使用委派實作晚期系結、如何使用事件來通知其他類別或物件,以及如何管理 C# 應用程式中的事件發行者和訂閱者。