[C#][dynamic] 動的にイベント ハンドラーを追加

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

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

例えば、次のようなクラス ライブラリー (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 が呼ばれているのが判る。