metodos - C#- funciones anónimas y manejadores de eventos
metodo lambda c# (4)
A continuación se presenta un enfoque acerca de cómo anular la suscripción del evento en un método anónimo:
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();
Tengo el siguiente código:
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 registro mi miembro del evento (FoundStep) en la función anónima local local.
Mi pregunta es: cuando finalice la función ''FindStepByType'', ¿se eliminará automáticamente la función anónima de la lista de delegados del evento o tengo que eliminarla manualmente antes de abandonar la función? (y como hago eso?)
Espero que mi pregunta sea clara.
Cuando se utiliza un delegado anónimo (o una expresión lambda) para suscribirse a un evento, no le permite darse de baja fácilmente de ese evento más tarde. Un controlador de eventos nunca se anula automáticamente.
Si observa su código, aunque declare y se suscriba al evento en una función, el evento al que se suscribe está en la clase, por lo que una vez suscrito, siempre se suscribirá incluso después de que la función finalice. La otra cosa importante a tener en cuenta es que cada vez que se llame 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 pretendes).
Para cancelar la suscripción 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. A continuación, debería poder eliminar al delegado del evento antes de que se cierre la función.
Por estos motivos, si tiene que darse de baja del evento en algún momento posterior, no se recomienda utilizar delegados anónimos. Vea Cómo: suscribirse y cancelar la suscripción de eventos (Guía de programación de C #) (específicamente la sección titulada "Para suscribirse a eventos utilizando un método anónimo").
No, no se eliminará automáticamente. En este sentido, no hay una diferencia entre un método anónimo y un método "normal". Si lo desea, debe darse de baja manualmente del evento.
En realidad, capturará otras variables (p. Ej., La res
en su ejemplo) y las mantendrá vivas (evita que el recolector de basura las recoja) también.
Su código tiene algunos problemas (algunos que usted y otros han identificado):
- El delegado anónimo no se puede eliminar del evento como está codificado.
- El delegado anónimo vivirá más tiempo que la vida del método que lo llama porque lo ha agregado a FoundStep, que es un miembro de este .
- Cada entrada en FindStepsByType agrega otro delegado anónimo a FoundStep .
- El delegado anónimo es un cierre y extiende efectivamente la vida útil de retval , por lo que incluso si deja de hacer referencia a retval en otra parte de su código, el delegado anónimo aún lo retiene.
Para solucionar este problema, y aún utilizar un delegado anónimo, asignarlo a una variable local y luego eliminar el controlador dentro de un bloque finally (necesario en caso de que el controlador lance 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+ 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;
}