2010-01-07 18 views
6

Nota: He editado esta pregunta para que sea más fácil para otras personas con el mismo problema obtener ayuda aquí. Para ver la pregunta original que se ajusta mejor con algunas de las respuestas, consulte el historial de edición.Problema con el "encadenamiento" de eventos

En un proyecto tengo una clase ExecutionManager que puede contener varias instancias de ExecutionSlot. La clase ExecutionSlot tiene varios campos de eventos públicos como este:

public event EventHandlers.ObjectEventHandler<IPlugin> ExecuteCompleted; 

Para cada uno de estos eventos hay un evento correspondiente en ExecutionManager. El comportamiento deseado es que cada vez que se lanza un evento en ExecutionSlot, el evento coincidente también se genera en el ExecutionManager que lo contiene.

La solución implmented era que cada vez que se añadió un ExecutionSlot a un ExecutionManager la ExectionManager sería añadir sus propios eventos a los de ExecutionSlot así:

executionSlot.ExecuteCompleted += ExecuteCompleted; 

No hay necesidad todavía de eliminar un ExecutionSlot, por lo que los eventos nunca se eliminan tampoco.

El problema es que el evento en ExecutionManager no se está iniciando. Después de confirmar que un evento estaba siendo reaised por un ExecutionSlot descubrí que la modificación de la línea anterior a la siguiente solucionado el problema:

executionSlot.ExecuteCompleted += (sender, eventArgs) => ExecuteCompleted(sender, eventArgs); 

Y yo no podía entender por qué, así que mi pregunta era, ¿cuál era la diferencia .

El motivo de esta diferencia es que el primero agrega los oyentes actuales del evento ExecutionManager al evento ExecutionSlot. Por lo tanto, los oyentes que se agreguen más adelante no se invocarán cuando se genere el evento. En contraste, la última solución usa una lambda para elevar el evento del ExecutionManager, lo que significa que se llamará a los oyentes en el momento del evento.

El motivo subyacente de la falla de la primera solución es que los delegados son inmutables. Entonces, cuando agrega un nuevo delegado y un evento, en realidad está creando un nuevo delegado que contiene los delegados existentes y el agregado. Por lo tanto, cualquier referencia a los delegados realizada anteriormente no contendrá el delegado recién agregado.

+1

tal vez invierta sus ediciones a su pregunta original, luego haga otra pregunta y respóndala, con referencias de ida y vuelta. – JoelFan

+0

Veo el punto de mantener el texto original ya que para eso son las respuestas. Pero dado que el texto original no era lo suficientemente claro, creo que el nuevo texto será una mejor ayuda para los demás, en comparación con las preguntas casi duplicadas. –

Respuesta

3

vistazo a this other post on stackoverflow

b += (s, e) => a(s, e); 

no es lo mismo que

b += a; 

Se anexa los contenido actual de A a B, por lo que si más adelante más manejadores se alistan con una, esta no hará que se los llame cuando b se dispara

+0

Ahí es que a y b son variables; en este caso, es el nombre de un método, por lo que no está capturando otra cosa que no sea la referencia "this" (que de todos modos se capturará implícitamente en la segunda forma). –

+1

Frig, estás absolutamente en lo cierto. Estúpidos delegados inmutables;) Eso lo explica perfectamente, los objetos que se conectan al último evento de la cadena más tarde no serán llamados. Gracias –

+1

Bien, había estado * asumiendo * que ExecuteCompleted era un método. Si en realidad es una variable (por ejemplo, a través de un evento tipo campo), entonces sí tiene sentido. Otro ejemplo donde tener un ejemplo razonablemente completo hubiera ayudado :) –

4

Una idea ... tal vez hay algún lugar de su código en el que está haciendo:

executionSlot.ExecuteCompleted -= ExecuteCompleted; 

que darse de baja del evento si se utiliza la sintaxis de suscripción original, pero no eliminaría una vez que lo hizo su cambio.

+0

Eso no puede hacerlo, no generará la misma instancia de objeto delegado. El objetivo es diferente. –

+0

¿Qué? ¡Creo que estamos diciendo lo mismo! ??? – JoelFan

+0

Gracias por la sugerencia, desafortunadamente no hay desuscripción. Pero una de las razones por las que preferiría que el último código funcione es que podría necesitarlo en una versión posterior del programa, y ​​realmente preferiría no tener que escribir la función del controlador, ya que hay más de unos pocos eventos. Lo intentaré pero un poco más de información en la pregunta. –

4

EDITAR: Esta respuesta suponía que ExecuteCompleted era un método. Como en realidad es un campo , eso cambia completamente las cosas. Dejaré esta respuesta aquí por el bien de la posteridad.

La primera versión agrega un controlador de eventos con un delegado creado a partir de un método autogenerado que a su vez simplemente llama al ExecuteCompleted. Es un poco como esto:

private void <>AutogeneratedMethodWithUnspeakableName(object sender, EventArgs e) 
{ 
    ExecuteCompleted(e); 
} 
... 
executionSlot.ExecuteCompleted += <>AutogeneratedMethodWithUnspeakableName; 

La segunda versión añade un controlador de eventos con un delegado creado directamente desde el método ExecuteCompleted.

Básicamente, la primera forma es un nivel extra de redirección. Esto normalmente no haría ninguna diferencia, a excepción de la cancelación de la suscripción como mencionó JoelFan. Me gustaría Supongo ese es el problema.

La clase que plantea el evento podría reflejar sobre los controladores adjuntos y observar los nombres de los métodos, reaccionando de manera diferente en este caso particular, pero es muy poco probable.

+0

Gracias por la sugerencia, desafortunadamente no hay desuscripción. Pero una de las razones por las que preferiría que el último código funcione es que podría necesitarlo en una versión posterior del programa, y ​​realmente preferiría no tener que escribir la función del controlador, ya que hay más de unos pocos eventos. Y tampoco estoy usando el reflejo en la clase que plantea el evento. Lo intentaré pero un poco más de información en la pregunta. –

+0

@Lillemanden: Si pudieras encontrar un ejemplo breve pero completo que demuestra el problema, estoy seguro de que podremos solucionarlo. –

+0

@Jon Skeet: Estoy bastante seguro de que Protron lo clavó. Como los delegados son inmutables, se crea un nuevo delegado de multidifusión cada vez que usa "+ =". Entonces, cuando algo se suscribe al último evento de la cadena, se crea una nueva instancia de delegado, pero esa instancia no se agrega al evento executionSlot.ExecuteCompleted. Al menos eso es AFAIK, espero que tenga sentido. –

0

Creo que lo que está sucediendo aquí es que algún tipo de objeto temporal se está creando en el primer ejemplo con un controlador de eventos vacío al que se llama, pero no hace nada.

El segundo ejemplo, que dices que funciona, es un controlador de eventos en tu objeto con el código real. No estoy del todo seguro de lo que está pasando allí, pero esa es mi mejor suposición.

Ciertamente, el primer ejemplo huele mal de todos modos, ya que utiliza expresiones lambda para ofuscar el significado sin un valor añadido real.

+0

Probablemente no esté creando un objeto, de hecho, espero que el compilador agregue un método de instancia a la clase actual, ya que no captura nada más que "esto". –

Cuestiones relacionadas