c# - tiempo - Lambdas para controladores de eventos?
eventos en c# (5)
Cada función lambda forma un cierre sobre su ámbito de contención. Esto podría significar que los objetos temporales creados anteriormente en el constructor permanecen vivos durante mucho más tiempo de lo necesario debido a los cierres que mantienen referencias a ellos. Ahora, con suerte, el compilador es lo suficientemente inteligente como para excluir objetos del cierre que la lambda no utiliza, pero no estoy seguro. ¿Alguien sabe?
Según lo que he leído, el compilador de C # genera un método anónimo, o una clase interna anónima, según si necesita cerrarse sobre el alcance contenedor o no.
En otras palabras, si no accede al ámbito que lo contiene desde su lambda, no generará el cierre.
Sin embargo, esto es un poco de "rumores", y me encantaría tener a alguien que esté más informado con el compilador de C #.
Dicho todo esto, la antigua sintaxis de delegado anónimo de C # 2.0 hizo lo mismo, y casi siempre uso delegados anónimos para manejadores de eventos breves.
Ha cubierto bastante bien los pros y los contras, si necesita desenganchar su controlador de eventos, no use un método anónimo, de lo contrario, estoy totalmente de acuerdo.
La sintaxis de Lambda en C # 3 hace que sea realmente conveniente crear métodos anónimos de una línea. Son una mejora definitiva sobre la sintaxis de delegado anónimo wordier que C # 2 nos dio. La conveniencia de lambdas, sin embargo, trae consigo la tentación de utilizarlos en lugares donde no necesariamente necesitamos la semántica de programación funcional que proporcionan.
Por ejemplo, con frecuencia encuentro que mis manejadores de eventos son (o al menos comienzan como ) líneas simples que establecen un valor de estado, o llaman a otra función, o establecen una propiedad en otro objeto, etc. Para estos, ¿debo desordenar? mi clase con otra función simple, ¿o debería simplemente incluir un lambda en el evento en mi constructor?
Hay algunas desventajas obvias para lambdas en este escenario:
- No puedo llamar a mi controlador de eventos directamente; solo puede ser activado por el evento. Por supuesto, en el caso de estos simples manejadores de eventos, casi no hay tiempo en que necesite llamarlos directamente .
- No puedo desenganchar mi controlador del evento. Por otro lado, rara vez necesito desenganchar controladores de eventos, así que esto no es un problema, de todos modos .
Estas dos cosas no me molestan demasiado, por las razones expuestas. Y podría resolver esos dos problemas, si realmente eran problemas, almacenando el lambda en un delegado miembro, pero eso sería una especie de derrota con el propósito de usar lambdas para su conveniencia y para mantener la clase limpia de desorden.
Sin embargo, hay otras dos cosas que creo que quizás no sean tan obvias, pero posiblemente sean más problemáticas.
Cada función lambda forma un cierre sobre su ámbito de contención. Esto podría significar que los objetos temporales creados anteriormente en el constructor permanecen vivos durante mucho más tiempo de lo necesario debido a los cierres que mantienen referencias a ellos. Ahora, con suerte, el compilador es lo suficientemente inteligente como para excluir objetos del cierre que la lambda no utiliza, pero no estoy seguro. ¿Alguien sabe?
Afortunadamente, esto no siempre es un problema, ya que a menudo no creo objetos temporales en mis constructores. Sin embargo, puedo imaginar un escenario en el que lo hice y en el que no podría ubicarlo fácilmente fuera de la lambda.
- Mantenibilidad puede sufrir. Gran tiempo. Si tengo algunos manejadores de eventos definidos como funciones, y algunos definidos como lambdas, me preocupa que pueda ser más difícil rastrear errores, o simplemente entender la clase. Y más adelante, si mis controladores de eventos terminan expandiéndose, tendré que moverlos a funciones de nivel de clase o enfrentar el hecho de que mi constructor ahora contiene una cantidad significativa del código que implementa la funcionalidad de mi clase .
Así que quiero recurrir a los consejos y la experiencia de otros, quizás aquellos con experiencia en otros idiomas con funciones de programación funcional. ¿Hay alguna mejor práctica establecida para este tipo de cosas? ¿Evitaría el uso de lambdas en los controladores de eventos o en otros casos en los que el lambda supera significativamente su ámbito de aplicación? Si no, ¿a qué umbral decidirías usar una función real en lugar de una lambda? ¿Alguna de las trampas anteriores ha mordido significativamente a alguien? ¿Hay algún problema en el que no haya pensado?
Basado en un pequeño experimento con el compilador, diría que el compilador es lo suficientemente inteligente como para crear un cierre. Lo que hice fue un simple constructor que tenía dos lambdas diferentes que se usaban para un predicado en List.Find ().
La primera lamdba usó un valor codificado duro, la segunda utilizó un parámetro en el constructor. La primera lambda se implementó como un método privado estático en la clase. La segunda lambda se implementó como una clase que realizó el cierre.
Entonces, su suposición de que el compilador es lo suficientemente inteligente es correcta.
La mayoría de las mismas características de lambdas pueden aplicarse igualmente bien en otros lugares donde puedes usarlas. Si los controladores de eventos no son un lugar para ellos, no puedo pensar en nada mejor. Es una unidad de lógica autónoma de punto único, ubicada en su punto único.
En muchos casos, el evento está diseñado para obtener un pequeño paquete de contexto que resulta ser el adecuado para el trabajo en cuestión.
Considero que este es uno de los "buenos olores" en un sentido de refactorización.
Por lo general, tengo una rutina dedicada al cableado de controladores de eventos. En esto, uso delegados anónimos o lambdas para los manejadores actuales, manteniéndolos lo más cortos posible. Estos manejadores tienen dos tareas:
- Descomprime los parámetros del evento.
- Llamar a un método nombrado con los parámetros adecuados.
Hecho esto, evité abarrotar el espacio de nombres de mi clase con métodos de manejo de eventos que no se pueden usar limpiamente para otros fines, y me obligué a pensar sobre las necesidades y propósitos de los métodos de acción que implemento, generalmente dando como resultado un código más limpio.
Con respecto a lambdas, esta pregunta que hice recientemente tiene algunos hechos relevantes sobre los efectos en la vida útil de los objetos en la respuesta aceptada.
Otra cosa interesante que aprendí recientemente es que el compilador C # comprende múltiples cierres en el mismo ámbito que un cierre único con respecto a las cosas que captura y mantiene vivo. Tristemente no puedo encontrar la fuente original para esto. Añadiré que si me tropiezo con él de nuevo.
Personalmente, no uso lambdas como controladores de eventos porque creo que la ventaja de la legibilidad realmente viene cuando la lógica fluye de una solicitud a un resultado. El controlador de eventos tiende a agregarse en un constructor o inicializador, pero rara vez se llamará en este momento en el ciclo de vida del objeto. Entonces, ¿por qué debería mi constructor leer como si estuviera haciendo cosas ahora que realmente están sucediendo mucho más tarde?
Por otro lado, utilizo un tipo de mecanismo de evento ligeramente diferente en general, que considero preferible a la característica de lenguaje C #: un NotificationCenter de estilo iOS reescrito en C #, con una tabla de envío codificada por Tipo (derivada de Notificación) y con Valores de Acción <Notificación>. Esto termina permitiendo definiciones de tipo "evento" de línea única, como las siguientes:
public class UserIsUnhappy : Notification { public int unhappiness; }