読者です 読者をやめる 読者になる 読者になる

プログラミング C# - 翔ソフトウェア (Sho's)

C#/.NET/ソフトウェア開発など

動的にイベント ハンドラーを追加

今回は、動的にイベント ハンドラーの追加を行ってみたい。

■ 動的に読み込まれるクラス ライブラリー側

例えば、次のようなクラス ライブラリー (ClassLibrary.dll) があるとする。

// クラス ライブラリー側: ClassLibrary.dll
namespace ClassLibrary
{
    public class Data : IEnumerable<string>
    {
        // 更新されると起きるイベント
        public event EventHandler Update;

        List<string> itemList = new List<string>();

        // アイテムの追加
        public void Add(string item)
        {
            itemList.Add(item);
            // 更新イベントを起こす
            if (Update != null)
                Update(this, null);
        }

        // IEnumerable<string> の実装
        public IEnumerator<string> GetEnumerator()
        { return itemList.GetEnumerator(); }

        // IEnumerable<string> の実装
        IEnumerator IEnumerable.GetEnumerator()
        { return GetEnumerator(); }
    }
}

クラス Data は、文字列のコンテナー クラスで、文字列が追加 (Add) されると、Update イベントが起きるようになっている。

■ クラス ライブラリーを動的に読み込む側

別のプロジェクトで、このクラス ライブラリーを動的に読み込んで使いたいとする。

こちらでは、先ず先の Data を表示するための View クラスを用意することにする。

・View クラス
using System;
using System.Collections.Generic;

class View
{
    public IEnumerable<string> DataSource { private get; set; }

    // Update のイベント ハンドラー用
    public void OnUpdate(object sender, EventArgs e)
    {
        Show();
    }

    // DataSource の内容を表示
    void Show()
    {
        Console.WriteLine("View:");
        if (DataSource == null)
            return;
        foreach (var item in DataSource)
            Console.WriteLine("\t{0}", item);
    }
}

表示したい IEnumerable<string> な DataSource を予め設定しておくと、OnUpdate が呼ばれたときにそれを表示する、というクラスだ。

・動的にイベント ハンドラーを設定

それでは、クラス ライブラリーを動的に読み込み、その中の Data クラスのインスタンスを生成し、それにイベント ハンドラーを追加してみよう。

クラス ライブラリーを動的に読み込み、その中の Data クラスのインスタンスを生成
using System;
using System.Collections.Generic;
using System.Reflection;

class Program
{
    static void Main()
    {
        // アセンブリ名を使ってクラス ライブラリーを動的に読み込み
        Assembly assembly = Assembly.Load("ClassLibrary");
        // アセンブリ内のクラス Data の型情報を取得
        Type     dataType = assembly.GetType("ClassLibrary.Data");
        // アセンブリ内のクラス Data のインスタンスを生成
        var      data     = Activator.CreateInstance(dataType);
    }
}
続いて、View のインスタンスを生成して DataSource に data を設定
// ...省略...
class Program
{
    static void Main()
    {
        // ...省略...

        // View のインスタンスを生成して DataSource に data を設定
        var view = new View { DataSource = data as IEnumerable<string> };
    }
}
次に、イベント ハンドラー view.OnUpdate からデリゲートを作成し、それを動的にイベント ハンドラーとして追加
// ...省略...
class Program
{
    static void Main()
    {
        // ...省略...

        // Delegate クラスを利用してイベント ハンドラーである view.OnUpdate からデリゲートを作成
        Delegate eventHandlerDelegate = Delegate.CreateDelegate(typeof(EventHandler), view, "OnUpdate");

        // アセンブリ内のクラスの Update イベントの EventInfo を取得
        EventInfo eventInfo = dataType.GetEvent("Update");
        // EventInfo に対してイベント ハンドラーを追加
        eventInfo.AddEventHandler(data, eventHandlerDelegate);
    }
}
・試してみる

では試してみよう。

data に対して、文字列を追加 (Add) する度に view.OnUpdate が呼ばれれば OK だ。

// ...省略...
class Program
{
    static void Main()
    {
        // ...省略...

        // data.Add("Apple"); を動的に呼び出す
        dataType.InvokeMember("Add", BindingFlags.InvokeMethod, null, data, new object { "Apple"  });
        // data.Add("Banana"); を動的に呼び出す
        dataType.InvokeMember("Add", BindingFlags.InvokeMethod, null, data, new object { "Banana" });
    }
}

結果コンソールには、次のように表示される。

View:
        Apple
View:
        Apple
        Banana

文字列を追加 (Add) する度にイベント ハンドラー view.OnUpdate が呼ばれているのが判る。