2015-05-18 12 views
8

sto tentando di collegare un delegato a un elenco di chiamata di un diverso delegato. Con ciò sto raggiungendo una specie di Hook sugli eventi esistenti. Ho bisogno di collegare qualcosa che viene eseguito dopo ogni evento richiamato.Riflessione - Aggiungere un delegato a un altro elenco di chiamate del delegato

L'esempio seguente funziona a condizione che il delegato esposto dal tipo e l'azione in cui passaggio abbia la stessa identica firma. (gli eventi On1 e OnAll sono entrambi dichiarati con un delegato di azione in modo che funzioni).

Codice: Come collego un'azione a un delegato esistente esposto da un modificatore di evento.

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); 
     } 
    } 
} 

Il campione:

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() { } 
} 

Il problema: quando il delegato esposta con un modificatore evento nel Tester non ha la stessa firma ottengo un'eccezione ben voluto ed evidente in cui si afferma (in parole) che non è possibile aggiungere Action a un elenco di chiamate di Action<int>. ha senso.

Giusto per essere chiari che sto descrivendo il seguente:

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

Quello che sto cercando è un modo per creare un altro delegato dello stesso tipo come l'EventHandlerType. Per farlo ho bisogno di creare un metodo con la firma i di EventHandlerType che invochi internamente un'azione.

qualcosa di simile:

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

Volete passare un 'azione ' come 'Action' e aggiungere al loro interno' AddHandler'? –

+0

Voglio invocare un'azione (sì di tipo Action per argomento) ogni volta che viene invocato il delegato sottostante di qualsiasi evento @. Ive ha descritto quello che ho tentato, ma qualsiasi soluzione sarebbe la più gradita. –

risposta

10

Questo sembra funzionare ... Ci sono vari commenti dentro ... Io non sono sicuro se questo è il modo migliore per farlo. Sto costruendo un albero Expression per eseguire il richiamo del delegato.

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

Stavo lavorando su una risposta simile ma mi hai battuto per questo :) ottima implementazione. –

+0

sembra buono fammi provare. –

+0

Incredibile :) 10 volte assegnato. –

Problemi correlati