2015-05-18 13 views
8

Próbuję dołączyć Delegata do listy inwokacji innego uczestnika. Dzięki temu uzyskuję rodzaj haka na istniejących wydarzeniach. Potrzebuję podłączyć coś, co działa po każdym wywołanym zdarzeniu.Odbicie - Dodaj uczestnika do innej listy wywołań Delegata

Następujący przykład: działa tak długo, jak delegowany wystawiony przez typ i akcja, którą przekazano, mają dokładnie tę samą sygnaturę. (Zdarzenia On1 i OnAll są zadeklarowane jako delegat akcji, więc to działa).

Kod: Sposób podpinania akcji za pomocą istniejącego uczestnika ujawnionego przez modyfikator zdarzenia.

public static class ReflectionExtensions 
{ 
    public static IEnumerable<EventInfo> GetEvents(this object obj) 
    { 
     var events = obj.GetType().GetEvents(); 
     return events; 
    } 

    public static void AddHandler(this object obj, Action action) 
    { 
     var events = obj.GetEvents(); 
     foreach (var @event in events) 
     {      
      @event.AddEventHandler(obj, action); 
     } 
    } 
} 

próbkę:

public class Tester 
{ 
    public event Action On1; 
    public event Action On2; 

    public void RaiseOn1() 
    { 
     On1(); 
    } 

    public void RaiseOn2() 
    { 
     On2(); 
    } 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     var t = new Tester(); 
     t.On1 += On1; 
     t.On2 += On2; 

     t.AddHandler(OnAll); 

     t.RaiseOn1(); 
     t.RaiseOn2(); 
    } 

    public void On1() { } 
    public void On2() { } 
    public void OnAll() { } 
} 

Problem: Kiedy Delegat wystawiony z modyfikatora zdarzeń tester nie ma ten sam podpis dostanę dobrze chciał i oczywisty wyjątek który członkowskich (w moje słowa) , że Action nie można dodać do listy wywołań z Action<int>. ma sens.

Wystarczy być jasne, jestem opisujące następujące:

public event Action<int> On1;  
    public void On1(int i){} 

Co szukam jest sposobem, aby utworzyć kolejną Delegata tego samego typu co EventHandlerType. W tym celu potrzebuję stworzyć metodę z sygnaturą i klasy EventHandlerType, która wewnętrznie wywoła akcję.

coś takiego:

public static void AddHandler(this object obj, Action action) 
{ 
     var events = obj.GetEvents(); 
     foreach (var @event in events) 
     { 
      // method with the signeture of EventHandlerType which does action(); 
      MethodInfo wrapperMethod = WrapAction(@event.EventHandlerType, action); 

      Delegate handler = Delegate.CreateDelegate(@event.EventHandlerType, action.Target, wrapperMethod); 
      @event.AddEventHandler(obj, handler); 
     } 
} 
+0

Chcesz przekazać 'Akcja ' jako 'Akcja' i dodać je do' AddHandler'? –

+0

Chcę wywołać akcję (tak typu Action dla argumentu) za każdym razem, gdy wywoływany jest każdy delegat bazowy @ zdarzenia. Ive opisał, co próbowałem, ale wszelkie rozwiązanie byłoby najbardziej mile widziane. –

Odpowiedz

10

To wydaje się działać ... Istnieją różne komentarze w środku ... Nie jestem pewien, czy jest to najlepszy sposób, aby to zrobić. Buduję drzewo Expression, aby wykonać wywołanie delegata.

public static void AddHandler(this object obj, Action action) 
{ 
    var events = obj.GetEvents(); 

    foreach (var @event in events) 
    { 
     // Simple case 
     if (@event.EventHandlerType == typeof(Action)) 
     { 
      @event.AddEventHandler(obj, action); 
     } 
     else 
     { 
      // From here: http://stackoverflow.com/a/429564/613130 
      // We retrieve the parameter types of the event handler 
      var parameters = @event.EventHandlerType.GetMethod("Invoke").GetParameters(); 

      // We convert it to ParameterExpression[] 
      ParameterExpression[] parameters2 = Array.ConvertAll(parameters, x => Expression.Parameter(x.ParameterType)); 

      MethodCallExpression call; 

      // Note that we are "opening" the delegate and using 
      // directly the Target and the Method! Inside the 
      // LambdaExpression we will build there won't be a 
      // delegate call, there will be a method call! 
      if (action.Target == null) 
      { 
       // static case 
       call = Expression.Call(action.Method); 
      } 
      else 
      { 
       // instance type 
       call = Expression.Call(Expression.Constant(action.Target), action.Method); 
      } 

      // If you are OK to create a delegate that calls another 
      // delegate, you can: 
      // call = Expression.Call(Expression.Constant(action), typeof(Action).GetMethod("Invoke")); 
      // instead of the big if/else 

      var lambda = Expression.Lambda(@event.EventHandlerType, call, parameters2); 
      @event.AddEventHandler(obj, lambda.Compile()); 
     } 
    } 
} 
+0

Pracowałem nad podobną odpowiedzią, ale biłeś mnie do tego :) Świetna implementacja. –

+0

wygląda dobrze, pozwól mi to wypróbować. –

+0

Niesamowite :) 10x za dużo. –