2011-12-06 24 views
35

Duplicar posible:
How to pass an event to a method?Pasar un evento como un parámetro a un método

¿Es posible pasar de un evento como un parámetro a un método?

Por ejemplo, el siguiente método se suscribe al evento, funciona, y cancela la suscripción del evento:

void SubscribeDoAndUnsubscribe<TElement, TEventArgs>(
     IEnumerable<TElement> elements, 
     ??? elementEvent) 
    where TEventArgs: EventArgs 
{ 
    EventHandler<TEventArgs> handler = (sender, e) => { /* Handle an event */ }; 

    foreach (var element in elements) 
    { 
     // Subscribe somehow 
     element.elementEvent += handler 
    } 

    // Do things 

    foreach (var element in elements) 
    { 
     // Unsubscribe somehow 
     element.elementEvent -= handler 
    } 
} 

El código de cliente:

var elements = new [] { new Button(), new Button() }; 
SubscribeDoAndUnsubscribe(elements, ??? /* e => e.Click */); 

Si no es posible, ¿Cómo logro la lógica similar en otras formas? ¿Debo pasar un par de delegados para los métodos de suscripción/desinscripción?

+0

Mira aquí: http://stackoverflow.com/questions/8022406/passing-an-event-and-a-delegate-event-handler-into-a-generic-helper-method/8022448#8022448 –

Respuesta

40

De hecho, ha descubierto que los eventos no son de "primera clase" en C#; no puede pasar un evento como datos. Puede pasar alrededor de un delegado a un método asociado con un receptor como un objeto de primera clase al hacer un delegado. Puede pasar alrededor de una referencia a cualquier variable como un objeto (en su mayoría) de primera clase. (Digo "principalmente" porque las referencias a variables no pueden almacenarse en campos, almacenarse en matrices, etc., son muy restringidas en comparación con otros tipos de datos). Puede pasar un tipo obteniendo su objeto Tipo y pasando eso.

Pero no hay forma de pasar directamente los datos como un evento, propiedad, indizador, constructor o destructor asociado a una instancia en particular. Lo mejor que puede hacer es crear un delegado (o un par de delegados) a partir de un lambda, como sugiere. O bien, obtenga el objeto de reflexión asociado con el evento y páselo, junto con la instancia.

+0

Hay una excepción a esa regla: PUEDE pasar un evento como parámetro a un método cuando el evento que está pasando se declara en la misma clase que realiza esta llamada. Como en la clase A {evento público SomeDelegate YourEvent; CallMe (SomeDelegate theEvent) {theEvent (this, args); }} –

+0

@ILIABROUDNO: No veo en qué parte de su pequeña muestra hay 'YourEvent' alguna vez pasado a nada. –

+0

Mi mal. Poner el vacío delante de CallMe y luego agregar otro método a la clase A: void OtherMethod() {CallMe (YourEvent);} Ahora estás pasando YourEvent como parámetro. –

25

No, desafortunadamente no.

Si mira Reactive Extensions, que sufre de un problema similar. Hay tres opciones que utilizan (IIRC - que ha sido un tiempo desde que he mirado):

  • pase en el correspondiente EventInfo y llamarlo con la reflexión
  • Pass en el nombre del evento (y el objetivo de ser necesario) y llamarlo con la reflexión
  • pase en delegados para la suscripción y desuscripción

la llamada en el último caso sería algo así como:

SubscribeAndDoUnsubscribe(elements, 
          handler => e.Click += handler, 
          handler => e.Click -= handler); 

y la declaración sería:

void SubscribeDoAndUnsubscribe<TElement, TEventArgs>(
     IEnumerable<TElement> elements, 
     Action<EventHandler<TEventArgs>> subscription, 
     Action<EventHandler<TEventArgs>> unsubscription) 
    where TEventArgs: EventArgs 
+0

Usted mezcló 'Click' y' Client' en su ejemplo. – CodesInChaos

3

Usted está tratando de moverse por la seguridad de tipos, y no puede hacerlo sin necesidad de utilizar la reflexión. Te mostraré un ejemplo aún más simple de lo que estás tratando de hacer.

void DoSomethingOnSomethingElse(T obj, Action method) 
{ 
    obj.method(); 
} 

C# no funciona de esta manera.¿Cómo sabe el compilador que todos los T tienen el método method? No lo hace, y no puede. Del mismo modo, no todos los TElement en su código tendrán un evento Click por ejemplo.

Parece que solo desea establecer un controlador de eventos de un solo uso en un conjunto de objetos. Usted puede hacer esto con bastante facilidad ...

EventHandler handler = null; 
handler = (s,e) => 
{ 
    DoSomething(e); 
    var b = (Button) s; 
    b.Click -= handler; 
} 

foreach (var button in buttons) 
{ 
    button.Click += handler; 
} 

Esto, obviamente, sólo funciona con botones, pero mientras escribo esto, veo Jon Skeet le ha mostrado una solución más general, así que voy a terminar aquí.

+1

Sí, tiene razón sobre la seguridad y la reflexión del tipo. – altso

Cuestiones relacionadas