このSystem.Linq.Expressions.Expression
クラスを使用して、イベントのシグネチャに一致する動的ハンドラーを生成できます。このハンドラーに、を呼び出すだけですConsole.WriteLine
。
このメソッド(必要な特定のオーバーロードへのリンクを提供している)を使用して、または、より可能性が高いのは正しいタイプExpression.Lambda
を生成できます。Func<>
Action<>
イベントのデリゲートタイプを反映して(Invoke
@Davioで言及されているようにそのメソッドを取得)、すべての引数を引き出しParameterExpression
、ラムダメソッドに提供する各引数にsを作成します。
これは、標準の単体テストに貼り付けることができる完全なソリューションです。後で、フォローアップ編集で説明します。
public class TestWithEvents
{
//just using random delegate signatures here
public event Action Handler1;
public event Action<int, string> Handler2;
public void RaiseEvents(){
if(Handler1 != null)
Handler1();
if(Handler2 != null)
Handler2(0, "hello world");
}
}
public static class DynamicEventBinder
{
public static Delegate GetHandler(System.Reflection.EventInfo ev) {
string name = ev.Name;
// create an array of ParameterExpressions
// to pass to the Expression.Lambda method so we generate
// a handler method with the correct signature.
var parameters = ev.EventHandlerType.GetMethod("Invoke").GetParameters().
Select((p, i) => Expression.Parameter(p.ParameterType, "p" + i)).ToArray();
// this and the Compile() can be turned into a one-liner, I'm just
// splitting it here so you can see the lambda code in the Console
// Note that we use the Event's type for the lambda, so it's tightly bound
// to that event.
var lambda = Expression.Lambda(ev.EventHandlerType,
Expression.Call(typeof(Console).GetMethod(
"WriteLine",
BindingFlags.Public | BindingFlags.Static,
null,
new[] { typeof(string) },
null), Expression.Constant(name + " was fired!")), parameters);
//spit the lambda out (for bragging rights)
Console.WriteLine(
"Compiling dynamic lambda {0} for event \"{1}\"", lambda, name);
return lambda.Compile();
}
//note - an unsubscribe might be handy - which would mean
//caching all the events that were subscribed for this object
//and the handler. Probably makes more sense to turn this type
//into an instance type that binds to a single object...
public static void SubscribeAllEvents(object o){
foreach(var e in o.GetType().GetEvents())
{
e.AddEventHandler(o, GetHandler(e));
}
}
}
[TestMethod]
public void TestSubscribe()
{
TestWithEvents testObj = new TestWithEvents();
DynamicEventBinder.SubscribeAllEvents(testObj);
Console.WriteLine("Raising events...");
testObj.RaiseEvents();
//check the console output
}
概要-いくつかのイベントがあるタイプから始め(私は使用してAction
いますが、何でも機能するはずです)、サブスクライバーを持つすべてのイベントをテストファイアするために使用できるメソッドがあります。
次に、DynamicEventBinder
2つのメソッドを持つクラスにGetHandler
移動します。-特定のタイプの特定のイベントのハンドラーを取得します。そしてSubscribeAllEvents
、そのタイプの特定のインスタンスのすべてのイベントをバインドします。これは、すべてのイベントをループし、AddEventHandler
それぞれを呼び出しGetHandler
、ハンドラーを取得するために呼び出します。
このGetHandler
方法は、肉と骨がどこにあるかということであり、私が概要で提案したとおりに実行されます。
デリゲート型には、Invoke
バインド可能なハンドラーの署名をミラーリングするコンパイラーによってコンパイルされたメンバーがあります。そのため、そのメソッドを反映し、そのメソッドが持つパラメーターを取得して、それぞれにLinqParameterExpression
インスタンスを作成します。パラメータに名前を付けるのは良いことです。重要なのはここでのタイプです。
次に、本体が基本的に次の1行のラムダを作成します。
Console.WriteLine("[event_name] was fired!");
(ここで、イベントの名前は動的コードに取り込まれ、コードに関する限り定数文字列に組み込まれることに注意してください)
ラムダをビルドするときは、ビルドするExpression.Lambda
デリゲートのタイプ(イベントのデリゲートタイプに直接バインドされる)もメソッドに通知し、ParameterExpression
前に作成した配列を渡すことで、その数のメソッドを生成します。パラメーター。このメソッドを使用しCompile
て動的コードを実際にコンパイルします。これにより、をDelegate
引数として使用できるようになりますAddEventHandler
。
これが私たちが行ったことを説明することを心から願っています-もしあなたが式と動的コードを使ったことがないのなら、それは気が遠くなるようなものになるでしょう。確かに、私が一緒に働いている人々の中には、単にこれをブードゥーと呼んでいる人もいます。