Los eventos son un tipo definido dentro del CLR que nos permite notificar a
otros objetos algo que está sucediendo en el interior de nuestra clase. Para
que estos objetos puedan ser informados, antes tienen que suscribirse al
evento.
Desde la aparición de Visual
Basic 6, normalmente achacamos los eventos a controles tales como Buttons, Combobox, DataGrids,
etc., pero su aplicación y su enfoque va mucho más allá, y su uso en clases que
no forman parte de la GUI,
es tan normal, tan común y tan útil como en éstas.
El concepto de evento está completamente ligado al de delegado, ya que se nutre de estos
para guardar las acciones que se suscriben al mismo, y así añadir su
restricción de firma para estas acciones, de manera que solo se puedan
suscribir a estos eventos los métodos
que cumplan con la firma de su delegado.
Recuerda que aquí tienes el indice de todos los posts del Curso de LinQ.
Pongamos que tenemos una clase Persona, y que en ella exponemos la funcionalidad de informar
(a quien se quiera suscribir) que nuestros objetos instanciados han superado la
mayoría de edad. Esto lo realizaríamos con un evento.
Nuestra clase ofrecería un evento público que podría llamarse MayorDeEdad,
y que informaría de esto a todos los objetos que se hubieran suscrito.
Con el uso de eventos, hacemos que nuestras clases sean
mucho más reutilizables, ya que
dejemos en manos de nuestros consumidores la elección de la acción que quieren
realizar cuando pasa un determinado suceso. En el ejemplo anterior nosotros
podríamos haber forzado a que nuestra clase ejecutara un método en vez de un
evento cuando su edad superara ‘X’ años, una acción como escribir un texto en
consola, en un MessageBox o en un mail, pero esto sería encajonar
mucho la funcionalidad, y con el evento dejamos un abanico de posibilidades
abierto.
Definición en código de un Evento
Podemos definir un evento dentro del CLR de la siguiente forma:
Nuestro evento, informará que el objeto Persona ha cumplido
la mayoría de edad y además dentro de esa información nos indicará el momento
exacto en el que lo ha hecho, que puede ser que no sea exactamente el mismo en
el que se ha producido la comunicación.
La definición consta de 4 partes:
- public .- Ámbito de la declaración
- event .- Palabra reservada para declarar eventos.
- MiDelegado .- Delegado de referencia al que tendrán que ceñirse los métodos que se suscriban a nuestro evento.
- MayorDeEdad .- Nombre de nuestro evento.
Esta declaración de evento, sería completamente válida y
correcta para la ejecución de nuestro código, pero se saldría de las reglas y
recomendaciones que nos da Microsoft
para el uso de eventos. Estas recomendaciones se basan en la firma del delegado que tiene que acompañar a
un evento, y que tiene que cumplir las siguientes reglas:
- No tiene que devolver ningún tipo de dato, por lo que tiene que ser un método void obligatoriamente.
- Tiene que recibir 2 parámetros, el primero de tipo object, que indicará el objeto que ha lanzado el evento, nombrándose con el nombre de sender, y el segundo tiene que ser de una clase que derive de EventArgs, y en ella deben de incluirse todo la información adicional que quiere dar nuestro evento, este se nombrará como e.
El Framework ya contiene un delegado base para esto, que se
llama EventHandler y que
tiene la firma siguiente:
Con poco que hayamos consumido algún evento dentro del Framework, esta firma ya nos
resultará familiar.
La clase EventArgs,
es prácticamente una clase vacía, pero es la clase base de la que tendremos que
heredar cuando queramos ofrecer información dentro de nuestros eventos.
Por todo esto tendríamos que realizar una serie de cambios
en nuestro código para hacerlo compatible, los cambios serían los siguientes:
1.- Crearemos una clase que herede de EventArgs y que incluirá un campo para almacenar el DateTime con el momento exacto del
cumplimento de la mayoría de edad.
Por nomenclatura, estas clases finalizan con la definición
de EventArgs, para que sean más
compresibles a la lectura, en nuestro ejemplo MomentoEventArgs.
2.- En segundo lugar, modificaremos la definición de nuestro
delegado con los nuevos cambios:
Llegados a este punto, y pensando en un desarrollo amplio,
podríamos llevarnos las manos a la cabeza con la cantidad de delegados que
tendríamos que definirnos para poder cubrir los posibles casos de información
dentro de nuestras clases EventArgs.
Esto supondría una pérdida de tiempo y un aumento de nuestro código, pero Microsoft sacó una respuesta
inmediata añadiendo el delegado genérico EventHandler<TEvenArgs>,
que tiene la siguiente firma:
Nota: Me resulta bastante
curioso que dejaran un delegado genérico tan abierto, y no lo limitaran a una
clase que derivara de EventArgs, como marca la firma, pero supongo que sus
razones tendrían, pero la firma ideal hubiera sido la siguiente:
Si tenéis alguna duda sobre restricciones de Generics os remito a mis entradas anteriores
sobre el tema aquí: link
Con lo que nos podríamos librar de la definición del delegado, y nos bastaría solo con la
del evento:
Implementación interna de un Evento
En esta parte vamos a tratar la manera en que se realizan
las llamadas a un campo de tipo evento dentro de la misma clase, y como debemos
comprobar si tiene o no tiene algún suscriptor
vinculado.
Para ello vamos a comenzar a implementar la clase Persona:
Peculiaridades de la implementación:
- Tiene una propiedad de solo lectura (de forma externa) llamada edad. Esto es así para que nadie pueda manipular la edad desde el exterior a la clase, y para que solo se pueda hacer esto desde el interior y desde su método CumplirAños.
- Tiene un constructor que siempre inicializa la edad a 0 años, por lo que nuestras personas nacen cuando se crean sus instancias.
- Tiene un método CumplirAños, que aumenta en un año la edad actual de la persona.
La parte más importante sin en cambio es la llamada al
evento:
Como se puede apreciar, cada vez que cambia la edad,
comprobamos que supere los 18 años,
y en ese caso lanzamos el evento. Este código tiene un error muy normal que se
suele cometer cuando se empieza en el uso con eventos, y es que no está
comprobando que haya alguien suscrito al evento (this.MayorEdad ¡= null), y si nadie se hubiera suscrito este
código lanzaría un error por llamar a un evento nulo. Para arreglar esto, realizaríamos el siguiente cambio:
Añadiríamos la comprobación antes de la llamada de manera
que solo lanzaríamos el evento en caso de que tuviera algún suscriptor. Esto sigue siendo
completamente correcto, pero en nuestro ejemplo solo tenemos una llamada al evento,
si tuviéramos 100 o 1000, ¿Tendríamos que hacer la comprobación en todas?, y si
tuviéramos que modificar algo de la comprobación, ¿tendríamos que ir a cada una
de ellas a realizar el cambio?, para esto y para otras cosas más haremos uso
del Patrón On.
Nos crearemos un método Protected
Virtual void, con el nombre de nuestro evento, precedido por la
palabra On y recibiendo por
parámetro la clase EventArgs.
Esto es así, para que las clases que hereden
de la nuestra, tenga la capacidad de poder sobreescribir
este método y puedan cambiar la funcionalidad y la condición de lanzamiento del
mismo. A la vez encapsularemos dentro de este evento la comprobación de
suscripción.
Así quedaría en nuestro ejemplo:
Y así la llamada:
Suscribiéndose a eventos
La forma de suscribirnos a un evento, es prácticamente igual
que la de instanciar un delegado,
cuando nos suscribimos desde aplicaciones de GUI (Windows Forms, WPF, ASP, etc) nos parece transparente ya
que es el propio Visual Studio es el que lo realiza por nosotros en las cases o
métodos (.Designer, InitializeComponents, etc), pero vamos a ver unos ejemplos
de cómo podemos hacerlo:
Code:
Aparte de todas estas, habría otra que podríamos usarla por
herencia, dentro las clases que heredaran de Persona, por medio de la redefinición de métodos, así:
¿Por qué eventos existiendo delegados?
Hay gente que se pregunta el por qué de la existencia de eventos, cuando un delegado podría realizar su función
con las mismas garantías que un evento. La respuesta la tenemos en uno de los
pilares de la POO, y es la encapsulación. Si sustituyéramos nuestros
eventos por delegados, la ejecución de los mismos podría lanzarse al gusto por
cualquier objeto que consumiera la clase. Con los eventos esto no pasa y solo
se lanzan cuando la implementación interna de la clase lo requiere, y vienen a
ser un delegado público sin permisos de ejecución.
Pues ha quedado una entrada un poco más larga de lo normal, pero así lo tenemos todo en un mismo texto.
Continuamos con LinQ.
No hay comentarios :
Publicar un comentario