2009-09-07 26 views
17

tengo el siguiente código:C# - funciones anónimas y controladores de eventos

public List<IWFResourceInstance> FindStepsByType(IWFResource res) 
{ 
    List<IWFResourceInstance> retval = new List<IWFResourceInstance>(); 
    this.FoundStep += delegate(object sender, WalkerStepEventArgs e) 
         { 
         if (e.Step.ResourceType == res) retval.Add(e.Step); 
         }; 
    this.Start(); 
    return retval; 
} 

Observe cómo puedo registrar el miembro de evento (FoundStep) a función local en el lugar en el anonimato.

Mi pregunta es: cuando la función 'FindStepByType' termine, ¿se eliminará automáticamente la función anónima de la lista de delegados del evento o tendré que eliminarla manualmente antes de ejecutar la función? (¿y cómo lo hago?)

Espero que mi pregunta sea clara.

Respuesta

34

Su código tiene algunos problemas (algunos usted y otros han identificado):

  • El delegado anónimo no puede ser removido del evento como codificado.
  • El delegado anónimo vivirá más que la vida del método llamándolo porque lo ha agregado al FoundStep que es miembro de este.
  • Cada entrada en FindStepsByType agrega otro delegado anónimo al FoundStep.
  • El delegado anónimo es un cierre y efectivamente se extiende la vida útil de retval, por lo que incluso si deja de hacer referencia retval en otras partes de su código, sigue siendo sostenido por el delegado anónimo.

Para solucionar esto, y seguir utilizando un delegado anónimo, asignarlo a una variable local, y luego quitar el controlador dentro de un bloque finalmente (necesaria en caso de que el guía lanza una excepción):

public List<IWFResourceInstance> FindStepsByType(IWFResource res) 
    { 
    List<IWFResourceInstance> retval = new List<IWFResourceInstance>(); 
    EventHandler<WalkerStepEventArgs> handler = (sender, e) => 
    { 
     if (e.Step.ResourceType == res) retval.Add(e.Step); 
    }; 

    this.FoundStep += handler; 

    try 
    { 
     this.Start(); 
    } 
    finally 
    { 
     this.FoundStep -= handler; 
    } 

    return retval; 
    } 

con C# 7.0 o superior puede reemplazar el delegado anónimo con una función local, logrando el mismo efecto:

public List<IWFResourceInstance> FindStepsByType(IWFResource res) 
    { 
     var retval = new List<IWFResourceInstance>(); 

     void Handler(object sender, WalkerStepEventArgs e) 
     { 
      if (e.Step.ResourceType == res) retval.Add(e.Step); 
     } 

     FoundStep += Handler; 

     try 
     { 
      this.Start(); 
     } 
     finally 
     { 
      FoundStep -= Handler; 
     } 

     return retval; 
    } 
5

No, no se eliminará automáticamente. En este sentido, no hay diferencia entre un método anónimo y uno "normal". Si lo desea, debe darse de baja manualmente del evento.

En realidad, capturará otras variables (por ejemplo, res en su ejemplo) y las mantendrá vivas (evita que el recolector de basura las recopile) también.

+0

¿No es exactamente lo mismo que usar predicados? Cuando uso predicados, no libero al predicado predicado. –

+2

Los predicados no se guardan en ningún lado, pero aquí, se está suscribiendo a un evento. Mientras el objeto que contiene el evento esté activo, mantendrá una referencia a su delegado e indirectamente a sus variables. Cuando pase diga, '.Where (x => x.Hidden)' a algún método, el método hará el trabajo con él y lo descartará (es solo una variable local en lo que respecta al método 'Where'. Esto no es cierto para su caso. Además, si 'Where' lo almacenó en alguna parte, también debería haberse preocupado por esto. –

3

Al usar un delegado anónimo (o una expresión lambda) para suscribirse a un evento no le permite anular la suscripción a ese evento más adelante. Un controlador de eventos nunca se anula automáticamente.

Si miras tu código, aunque declares y suscribas el evento en una función, el evento al que te estás suscribiendo pertenece a la clase, por lo que una vez suscrito siempre estará suscrito incluso después de que la función finalice. La otra cosa importante es darse cuenta de que cada vez que se llama a esta función, se suscribirá nuevamente al evento. Esto es perfectamente legal ya que los eventos son esencialmente delegados de multidifusión y permiten múltiples suscriptores. (Esto puede o no ser lo que usted desea).

Para darse de baja del delegado antes de salir de la función, deberá almacenar el delegado anónimo en una variable de delegado y agregar el delegado al evento. Debería poder eliminar al delegado del evento antes de que la función finalice.

Por estas razones, si tendrá que darse de baja del evento en algún momento posterior, no se recomienda el uso de delegados anónimos. Consulte How to: Subscribe to and Unsubscribe from Events (C# Programming Guide) (específicamente la sección titulada "Suscribirse a eventos utilizando un método anónimo").

5

a continuación se enfoque sobre el evento cómo darse de baja en un método onymous:

DispatcherTimer _timer = new DispatcherTimer(); 
_timer.Interval = TimeSpan.FromMilliseconds(1000); 
EventHandler handler = null; 

int i = 0; 

_timer.Tick += handler = new EventHandler(delegate(object s, EventArgs ev) 
{ 
    i++; 
    if(i==10) 
     _timer.Tick -= handler; 
}); 

_timer.Start(); 
Cuestiones relacionadas