2016-09-22 17 views
1

おはよう! 私の目的は、オブジェクトの購読や購読をイベントから(やりとりする)ことができるクラスを実装することです。ここに私のクラスのコードです。リフレクションによるイベントハンドラの追加と削除c#

public static class EventSubscriber 
{ 
    public static void AddEventHandler(EventInfo eventInfo, object item, Action action) 
    { 
     var parameters = GetParameters(eventInfo); 
     var handler = GetHandler(eventInfo, action, parameters); 
     eventInfo.AddEventHandler(item, handler); 
    } 

    public static void RemoveEventHandler(EventInfo eventInfo, 
         object item, Action action) 
    { 
     var parameters = GetParameters(eventInfo); 
     var handler = GetHandler(eventInfo, action, parameters); 
     eventInfo.RemoveEventHandler(item, handler); 
    } 

    private static ParameterExpression[] GetParameters(EventInfo eventInfo) 
    { 
     return eventInfo.EventHandlerType 
      .GetMethod("Invoke") 
      .GetParameters() 
      .Select(parameter => Expression.Parameter(parameter.ParameterType)) 
      .ToArray(); 
    } 

    private static Delegate GetHandler(EventInfo eventInfo, 
       Action action, ParameterExpression[] parameters) 
    { 
     return Expression.Lambda(
      eventInfo.EventHandlerType, 
      Expression.Call(Expression.Constant(action), 
         "Invoke", Type.EmptyTypes), parameters) 
      .Compile(); 
    } 
} 

ご覧のとおり、オブジェクトを実際に購読したり購読を解除したりする2つのパブリックメソッドがあります。そして、ここで私は、サンプルの

class Program 
{ 
    static void Main() 
    { 
     Test test = new Test(); 
     test.SubscribeTimer(); 
     while (true) 
     { 
      if(test.a == 10) 
      { 
       break; 
      } 
     } 
     test.UnsubscribeTimer(); 
     while (true) 
     { 

     } 
    } 
} 

class Test 
{ 
    System.Timers.Timer timer; 
    public int a = 0; 

    public Test() 
    { 
     timer = new System.Timers.Timer(1000); 
     timer.Start(); 
    } 

    public void SubscribeTimer() 
    { 
     var eventInfo = typeof(System.Timers.Timer).GetEvent("Elapsed"); 
     EventSubscriber.AddEventHandler(eventInfo, timer, TimerElapsed); 
     EventSubscriber.RemoveEventHandler(eventInfo, timer, TimerNotElapsed); 
    } 

    public void UnsubscribeTimer() 
    { 
     var eventInfo = typeof(System.Timers.Timer).GetEvent("Elapsed"); 
     EventSubscriber.AddEventHandler(eventInfo, timer, TimerNotElapsed); 
     EventSubscriber.RemoveEventHandler(eventInfo, timer, TimerElapsed); 
    } 

    public void TimerElapsed() 
    { 
     Console.WriteLine("timer elapsed"); 
     a++; 
    } 

    public void TimerNotElapsed() 
    { 
     Console.WriteLine("timer not elapsed"); 
     a++; 
    } 
} 

予想される動作は、初めに、我々はメッセージ10番目秒後に我々は唯一の「しないタイマを参照する必要があり、毎秒「のタイマーが経過する」を参照してくださいということですそれをテストする方法サンプルです私たちはやっていますが、まだ "タイマー経過"を見ています。つまり、AddEventHandlerメソッドは機能しますが、RemoveEventHandlerメソッドは機能しません。

あなたが私を助けるならば、私はとても幸せです。前もって感謝します。

+0

、デリゲートは完全に一致する必要があります。同じメソッドとターゲットプロパティ。ターゲットは同じではありません。 btwを見るのが非常に難しく、ClosureオブジェクトはExpression.Lambda()によって非常に難読化されています。かわいいトリックですが、私は真剣にあなたがそれを動作させることができると疑う。また、スレッドの要件に違反していることにも注意してください。変数の更新を確認することはできません。 –

+0

返信いただきありがとうございます!だから、イベントハンドラのシグネチャをデリゲートタイプのイベントのシグネチャと同じにするなら、私がサンプルに書いたように私は退会することができますか? – Crispried

答えて

0

は、私はあなたが新しいハンドラを毎回作成しているからだと思う:(前のハンドラと一致していないので、呼び出しリストから削除することはできません)

public static void RemoveEventHandler(EventInfo eventInfo, 
        object item, Action action) 
{ 
    var parameters = GetParameters(eventInfo); 
    var handler = GetHandler(eventInfo, action, parameters); // <-- 
    eventInfo.RemoveEventHandler(item, handler); 
} 

はなぜあなたをしていますActionをラップしますか?パラメータを失う?パラメータのためにeventInfo.RemoveEventHandler(item, action);を追加/削除することはできません。新しく生成されたハンドラを削除する場合は、ハンドラを削除するときにそのハンドラを返す必要があります。削除するイベントハンドラのために

public static Delegate AddEventHandler(EventInfo eventInfo, object item, Action action) 
{ 
    var parameters = GetParameters(eventInfo); 
    var handler = GetHandler(eventInfo, action, parameters); 
    eventInfo.AddEventHandler(item, handler); 
    return handler; 
} 

public static void RemoveEventHandler(EventInfo eventInfo, 
        object item, Delegate handler) 
{ 
    eventInfo.RemoveEventHandler(item, handler); 
} 

var handler = EventSubscriber.AddEventHandler(eventInfo, timer, TimerElapsed); 

EventSubscriber.RemoveEventHandler(eventInfo, timer, handler); 
+0

ありがとう!今それは動作します。しかし、なぜあなたはアクションをラップしているのですか?何のパラメータを失うのですか?パラメータのためにeventInfo.RemoveEventHandler(アイテム、アクション)を追加/削除することはできません。 。あなたがそれを私に説明してくれれば非常に感謝しているかもしれません。 – Crispried

+0

要約: 'Timer.Elapsed'イベントタイプは' EventHandler'です。イベントを受け取る場合、イベントハンドラ/メソッドはイベントタイプと一致する必要があります。 'void MyHandler(Object sender、EventArgs e)'。あなたのアクションは定義と一致しません。したがって、 'Expression.Lambda'を使用すると、イベントの' Timer.Elapsed'定義と一致する新しいメソッドを作成し、パラメータなし 'Action'を呼び出します。それであなたの行動を包みます。つまり、 'Expression.Lambda'から作成されたデリゲートは、それがinvocationlistに追加されたため、自身のActionではなく、退会する必要があります。 –

+0

私たちのコード(私のイベントハンドラを意味する)が(少なくとも私のために)はっきりしているので、私たちのパラメータ(オブジェクト送信者とEventArgs e)を使用したくないなら、良いことです。しかし、イベントハンドラでこれらのパラメータを使用するには、必要なパラメタを使用してこのメ​​ソッドを作成し、EventSubscriber.AddEventHandler()でAction(Expression.Lambdaから作成したデリゲートではありません)を登録します。ハンドラの署名がデリゲートの署名と一致する場合は、私がサンプルに書いたように私は退会することができますか?私は正しい? – Crispried

関連する問題