2008-09-05 23 views
30

C#イベントを動的にサブスクライブすると、オブジェクトインスタンスとイベント名を含むString名が与えられ、そのイベントにサブスクライブして何かを実行すると(たとえばコンソールに書き込む)、そのイベントは解雇されましたか?C#動的イベントサブスクリプション

Reflectionを使用しているように見えますが、これは不可能です。可能な場合はReflection.Emitを使用する必要はありません。これは現在のところ(私にとっては)唯一の方法だと思われます。

/EDIT:私はイベントのために必要なデリゲートのシグネチャを知らない、これが問題の核心は、

/EDIT 2:デリゲートcontravarianceが良い計画のように思えるが、Iこのソリューションを使用するために必要な前提を作ることができません

答えて

28

あなたはどのタイプのイベントのためのイベントハンドラとして引数なしで空のメソッドを使用して式ツリーをコンパイルすることができます。他のイベントハンドラタイプに対応するには、イベントハンドラのパラメータを何とかイベントにマッピングする必要があります。

using System; 
using System.Linq; 
using System.Linq.Expressions; 
using System.Reflection; 

class ExampleEventArgs : EventArgs 
{ 
    public int IntArg {get; set;} 
} 

class EventRaiser 
{ 
    public event EventHandler SomethingHappened; 
    public event EventHandler<ExampleEventArgs> SomethingHappenedWithArg; 

    public void RaiseEvents() 
    { 
     if (SomethingHappened!=null) SomethingHappened(this, EventArgs.Empty); 

     if (SomethingHappenedWithArg!=null) 
     { 
      SomethingHappenedWithArg(this, new ExampleEventArgs{IntArg = 5}); 
     } 
    } 
} 

class Handler 
{ 
    public void HandleEvent() { Console.WriteLine("Handler.HandleEvent() called.");} 
    public void HandleEventWithArg(int arg) { Console.WriteLine("Arg: {0}",arg); } 
} 

static class EventProxy 
{ 
    //void delegates with no parameters 
    static public Delegate Create(EventInfo evt, Action d) 
    { 
     var handlerType = evt.EventHandlerType; 
     var eventParams = handlerType.GetMethod("Invoke").GetParameters(); 

     //lambda: (object x0, EventArgs x1) => d() 
     var parameters = eventParams.Select(p=>Expression.Parameter(p.ParameterType,"x")); 
     var body = Expression.Call(Expression.Constant(d),d.GetType().GetMethod("Invoke")); 
     var lambda = Expression.Lambda(body,parameters.ToArray()); 
     return Delegate.CreateDelegate(handlerType, lambda.Compile(), "Invoke", false); 
    } 

    //void delegate with one parameter 
    static public Delegate Create<T>(EventInfo evt, Action<T> d) 
    { 
     var handlerType = evt.EventHandlerType; 
     var eventParams = handlerType.GetMethod("Invoke").GetParameters(); 

     //lambda: (object x0, ExampleEventArgs x1) => d(x1.IntArg) 
     var parameters = eventParams.Select(p=>Expression.Parameter(p.ParameterType,"x")).ToArray(); 
     var arg = getArgExpression(parameters[1], typeof(T)); 
     var body = Expression.Call(Expression.Constant(d),d.GetType().GetMethod("Invoke"), arg); 
     var lambda = Expression.Lambda(body,parameters); 
     return Delegate.CreateDelegate(handlerType, lambda.Compile(), "Invoke", false); 
    } 

    //returns an expression that represents an argument to be passed to the delegate 
    static Expression getArgExpression(ParameterExpression eventArgs, Type handlerArgType) 
    { 
     if (eventArgs.Type==typeof(ExampleEventArgs) && handlerArgType==typeof(int)) 
     { 
      //"x1.IntArg" 
      var memberInfo = eventArgs.Type.GetMember("IntArg")[0]; 
      return Expression.MakeMemberAccess(eventArgs,memberInfo); 
     } 

     throw new NotSupportedException(eventArgs+"->"+handlerArgType); 
    } 
} 


static class Test 
{ 
    public static void Main() 
    { 
     var raiser = new EventRaiser(); 
     var handler = new Handler(); 

     //void delegate with no parameters 
     string eventName = "SomethingHappened"; 
     var eventinfo = raiser.GetType().GetEvent(eventName); 
     eventinfo.AddEventHandler(raiser,EventProxy.Create(eventinfo,handler.HandleEvent)); 

     //void delegate with one parameter 
     string eventName2 = "SomethingHappenedWithArg"; 
     var eventInfo2 = raiser.GetType().GetEvent(eventName2); 
     eventInfo2.AddEventHandler(raiser,EventProxy.Create<int>(eventInfo2,handler.HandleEventWithArg)); 

     //or even just: 
     eventinfo.AddEventHandler(raiser,EventProxy.Create(eventinfo,()=>Console.WriteLine("!"))); 
     eventInfo2.AddEventHandler(raiser,EventProxy.Create<int>(eventInfo2,i=>Console.WriteLine(i+"!"))); 

     raiser.RaiseEvents(); 
    } 
} 
+0

地獄は、表現木がとても冷たいです。私はReflection.Emitを介して一度同様のコードを書いた。何が痛い。 –

+0

コードの大きな部分。引数をサポートするためにそれを変更する方法を示すことができますか?私は引数を使ってメソッドを取得するように変更しましたが、スコープ ''から参照される 'System.String'型の変数 'x'を取得しますが、デリゲートを作成しようとすると定義されません。ありがとう –

+0

完了 - 別の例を追加しました。 –

0

依存性注入を使用して、あなたが望むものを達成することができます。たとえばMicrosoft Composite UI app blockを使用すると、今ここにあなたが解決する必要があるとしている問題であることを行っているリフレクション

var o = new SomeObjectWithEvent; 
o.GetType().GetEvent("SomeEvent").AddEventHandler(...); 

http://msdn.microsoft.com/en-us/library/system.reflection.eventinfo.addeventhandler.aspx

を使用してイベントをサブスクライブすることが可能である

2

を説明したまさにん。各イベントハンドラに必要な代理人は、異なる署名を持ちます。これらのメソッドを動的に作成するには、Reflection.Emitを使用するか、特定のデリゲートに自分自身を限定してコンパイルされたコードで処理できるようにする必要があります。

これが役に立ちます。これは、サブスクリプションを行いませんが、呼び出すメソッドに与える

//reflect out the method to fire as a delegate 
EventHandler eventDelegate = 
    (EventHandler) Delegate.CreateDelegate(
     typeof(EventHandler), //type of event delegate 
     objectWithEventSubscriber, //instance of the object with the matching method 
     eventSubscriberMethodName, //the name of the method 
     true); 

-1

はあなたのようなものを意味しています。

編集:ポストはこの答えの後に明らかになった

あなたは種類がわからない場合、私の例が助けにはなりません。

しかし、.Netのすべてのイベントは、デフォルトのイベントパターンに従う必要があります。したがって、これを実行している限り、基本的なEventHandlerで動作します。

1
public TestForm() 
{ 
    Button b = new Button(); 

    this.Controls.Add(b); 

    MethodInfo method = typeof(TestForm).GetMethod("Clickbutton", 
    BindingFlags.NonPublic | BindingFlags.Instance); 
    Type type = typeof(EventHandler); 

    Delegate handler = Delegate.CreateDelegate(type, this, method); 

    EventInfo eventInfo = cbo.GetType().GetEvent("Click"); 

    eventInfo.AddEventHandler(b, handler); 

} 

void Clickbutton(object sender, System.EventArgs e) 
{ 
    // Code here 
} 
8

それは完全に一般的な解決策ではないですが、すべてのイベントは、フォームTがいるEventArgsから派生 のボイドはFoo(オブジェクトo、Tの引数)のものであるならば、あなたが逃げるためにデリゲートcontravarianceを使用することができますそれ。 (KeyDownイベントの署名をクリックしてのものと同じではないです)このように:

public Form1() 
    { 
     Button b = new Button(); 
     TextBox tb = new TextBox(); 

     this.Controls.Add(b); 
     this.Controls.Add(tb); 
     WireUp(b, "Click", "Clickbutton"); 
     WireUp(tb, "KeyDown", "Clickbutton"); 
    } 

    void WireUp(object o, string eventname, string methodname) 
    { 
     EventInfo ei = o.GetType().GetEvent(eventname); 

     MethodInfo mi = this.GetType().GetMethod(methodname, BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic); 

     Delegate del = Delegate.CreateDelegate(ei.EventHandlerType, this, mi); 

     ei.AddEventHandler(o, del); 

    } 
    void Clickbutton(object sender, System.EventArgs e) 
    { 
     MessageBox.Show("hello!"); 
    } 
2

LinFuを試してみてください - それはあなたが実行時に任意のイベントにバインドすることができますユニバーサルイベントハンドラがあります。たとえば、ここにあなたがダイナミックボタンのClickイベントにハンドラをバインドすることができます:

 
// Note: The CustomDelegate signature is defined as: 
// public delegate object CustomDelegate(params object[] args); 
CustomDelegate handler = delegate 
         { 
          Console.WriteLine("Button Clicked!"); 
          return null; 
         }; 

Button myButton = new Button(); 
// Connect the handler to the event 
EventBinder.BindToEvent("Click", myButton, handler); 

LinFuあなたは関係なく、デリゲート署名の、いずれかのイベントにあなたのハンドラをバインドすることができます。楽しい! http://www.codeproject.com/KB/cs/LinFuPart3.aspx

1

私は最近、ユニットテストイベントを記述したブログ投稿のシリーズを書いた、と私は議論する技術の一つは、動的なイベントサブスクリプションについて説明します。

あなたはここでそれを見つけることができます。私はリフレクションとMSIL(コード発光)を動的な側面に使用しましたが、これはすべてうまくまとめられています。 DynamicEventクラスを使用して、イベントを動的にそうようにサブスクライブすることができます。

EventPublisher publisher = new EventPublisher(); 

foreach (EventInfo eventInfo in publisher.GetType().GetEvents()) 
{ 
    DynamicEvent.Subscribe(eventInfo, publisher, (sender, e, eventName) => 
    { 
     Console.WriteLine("Event raised: " + eventName); 
    }); 
} 

私が実装されたパターンの特徴の一つは、あなたが持っているイベントを知っているので、それはイベントハンドラへの呼び出しにイベント名を注入していることでした育った。単体テストに非常に便利です。

ブログ記事は、イベントユニットのテスト手法を説明しているのでかなり長いですが、完全なソースコードとテストが提供されており、動的イベントサブスクリプションの実装方法の詳細は前回の記事で詳しく説明しています。

http://gojisoft.com/blog/2010/04/22/event-sequence-unit-testing-part-1/

+0

404にあなたのブログのリンクにそれを渡してください。 –

+0

@Robertコメントのために乾杯しました。昨日私たちのWebサーバーにパッチを当てた後、私たちはいくつかの停止をしました。 –