2008-09-05 17 views
30

¿Cómo se suscribiría dinámicamente a un evento C# para que una instancia de objeto y un nombre de cadena que contengan el nombre del evento se suscriban a ese evento y hagan algo (escribir en la consola por ejemplo) cuando ese evento ha sido despedido?C# Suscripción de evento dinámico

Parecería que al usar Reflection esto no es posible y me gustaría evitar tener que usar Reflection.Emit si es posible, ya que actualmente (para mí) parece ser la única forma de hacerlo.

/EDIT: no sé la firma del delegado necesaria para el evento, este es el núcleo del problema

/EDIT 2: Aunque contravarianza delegado parece un buen plan, no puede hacer la suposición necesaria para usar esta solución

Respuesta

28

Puede compilar árboles de expresiones para utilizar métodos vacíos sin ningún argumento como controladores de eventos para eventos de ningún tipo. Para acomodar otros tipos de controladores de eventos, debe asignar los parámetros del manejador de eventos a los eventos de alguna manera.

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

Hell, árboles de expresión son tan cool. Escribí un código similar una vez a través de Reflection.Emit. Que dolor. –

+0

Gran pieza de código. ¿Puedes mostrar cómo cambiarlo para apoyar argumentos? Lo cambié para obtener el método con los argumentos, pero obtengo "variable 'x' de tipo 'System.String' al que se hace referencia desde el alcance '', pero no está definido" cuando intento crear el delegado. Gracias –

+0

Hecho-He añadido otro ejemplo. –

2

Es posible suscribirse a un evento mediante la reflexión

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

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

Ahora aquí va a ser el problema que se va a tener que resolver . Los delegados necesarios para cada controlador de eventos tendrán diferentes firmas. Tendrá que buscar formas de crear dinámicamente estos métodos, lo que probablemente signifique Reflection.Emit, o tendrá que limitarse a un determinado delegado para que pueda manejarlo con un código compilado.

Espero que esto ayude.

-1

¿Quieres decir algo como:

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

Esto no hace la suscripción, pero le dará al método a llamar.

Editar:

mensaje se aclaró después de esta respuesta, mi ejemplo no ayudará si usted no sabe el tipo.

Sin embargo, todos los eventos en .Net deben seguir el patrón de evento predeterminado, por lo tanto, siempre que lo haya seguido, esto funcionará con el EventHandler básico.

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

No es una solución completamente general, pero si todos sus eventos son de la forma vacío (O objeto, args T) Foo, donde T se deriva de EventArgs, entonces usted puede utilizar contravarianza delegado de salirse con la eso. Como esta (donde la firma de KeyDown no es el mismo que el de clic):

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

Trate Linfu - tiene un controlador de eventos universal que permite enlazar a cualquier evento en tiempo de ejecución.Por ejemplo, aquí se puede enlazar es un controlador para el evento Click de un botón dinámico:

 
// 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 permite enlazar sus manipuladores para cualquier caso, con independencia de la firma del delegado. ¡Disfrutar!

Se puede encontrar aquí: http://www.codeproject.com/KB/cs/LinFuPart3.aspx

1

Hace poco escribí una serie de entradas de blog que describen eventos de pruebas unitarias, y una de las técnicas que discuto describe la suscripción de eventos dinámicos. Usé la reflexión y MSIL (emisión de código) para los aspectos dinámicos, pero todo está muy bien. Utilización de la clase DynamicEvent, los eventos pueden ser suscritas de forma dinámica este modo:

EventPublisher publisher = new EventPublisher(); 

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

Una de las características del patrón implementé fue que se inyecta el nombre del evento en la llamada al controlador de eventos para que sepa qué evento tiene sido criado Muy útil para pruebas unitarias.

El artículo de blog es bastante extenso ya que describe una técnica de prueba de unidad de evento, pero se proporcionan un código fuente completo y pruebas, y una descripción detallada de cómo se implementó la suscripción dinámica de evento se detalla en la última publicación.

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

+0

404 en su enlace de blog –

+0

@Robert Cheers por comentar, ayer tuvimos un corte de luz después de aplicar un parche a nuestro servidor web. –

Cuestiones relacionadas