Marco Besteiro y Miguel Rodríguez Delegates y Eventos Delegates y eventos Delegates Un delegate es muy similar a un puntero a función de C++, es una estructura de datos que referencia a un método estático o a un método de instancia de un objeto. Existen algunas diferencias: - Un puntero a función de C++ sólo puede referenciar funciones estáticas. - Un delegate no sólo referencia al punto de entrada del método, sino también a la instancia del objeto al que pertenece el método. El sentido de los delegates es permitir que un método pueda recibir como parámetro un puntero a otro método. Es más, no es necesario conocer en tiempo de compilación el método concreto que se pasa como parámetro: puede asignarse en tiempo de ejecución, lo cual ofrece una gran flexibilidad a la hora de programar. El tipo base de todos los delegates es la clase System.Delegate,que quiere decir que un delegate es una instancia de la clase System.Delegate (o una derivada). Los delegates no necesitan saber la clase de objeto al cual referencian. Lo que necesitan saber es la definición del método al que referencian. Declaración de Delegates. Una declaración de un delegate define un tipo de referencia que extiende la clase System.Delegate y que puede utilizarse para encapsular un método con una definición determinada.. La sintaxis de la declaración es: declaración-delegate: atributosopc modificadores-del-delegateopc delegate identificador(lista-parámetros-formalesopc ) ; modificadores-del-delegate: modificador-del-delegate modificadores-del-delegate modificador-del-delegate modificador-del-delegate: new public protected internal private Por ejemplo: delegate int MiDelegate(); Un ejemplo más detallado es el siguiente: delegate int MiDelegate(int x); 1/11 tipo-resultado Marco Besteiro y Miguel Rodríguez Delegates y Eventos class MiClase { MiDelegate d = new MiDelegate(Cuadrado); static float Cuadrado(float x) { return x * x; } static int Cuadrado(int x) { return x * x; } } Utilización de Delegates. Una instancia de tipo delegate encapsula un método, también llamado “entidad invocable”. En el caso de métodos estáticos, una entidad invocable consta únicamente del método. En el caso de métodos de instancia, una entidad invocable consta de una instancia de la clase del método y un método de tal instancia. Un ejemplo de utilización de instancias de tipo delegate es: using System; // declaración del delegate delegate int MiDelegate(); public class MiClase { public int MetodoInstancia () { Console.WriteLine("Hola desde el método de instancia."); return 0; } static public int MetodoEstatico () { Console.WriteLine("Hola desde el método estático."); return 0; } } public class Aplicacion { static void Main (string[] args) { MiClase p = new MiClase(); // Asociar el delegate al método de instancia: MiDelegate d = new MiDelegate(p.MetodoInstancia); // Invocación al método de instancia a través del delegate: d(); // Asociar el delegate al método estático: d = new MiDelegate(MiClase.MetodoEstatico); // Invocación al método estático a través del delegate: d(); } } 2/11 Marco Besteiro y Miguel Rodríguez Delegates y Eventos La salida de este programa es el de la figura 8.1: Figura 8.1 Delegates Multicast. Un delegate de tipo multicast puede asociarse a más de un método, de modo que invocando al delegate una sola vez, todos los métodos a los que está asociado sean invocados secuencialmente. Un delegate referencia realmente una lista de métodos a la que es posible añadir o quitar métodos utilizando los operadores +, +=, - y -=. Para ilustrar esto puede modificarse el método Main() del ejemplo anterior: static void Main (string[] args) { MiClase p = new MiClase(); // Asociar el delegate al método de instancia: MiDelegate d = new MiDelegate(p.MetodoInstancia); // Asociar el delegate al método estático, se utiliza //el operador + para que se añada el método estático //a la lista de métodos apuntados por el delegate d: d += new MiDelegate(MiClase.MetodoEstatico); // Invocación //delegate: de ambos d(); } Obsérvese que sólo se invoca una vez el delegate d(). El resultado se representa en la figura 8.2: 3/11 métodos a través del Marco Besteiro y Miguel Rodríguez Delegates y Eventos Figura 8.2 Existe una clase específica para este tipo de delegates multicast llamada System.MulticastDelegate, la cual deriva de System.Delegate y está en el assembly Mscorlib (Mscorlib.dll). La clase Delegate soporta una lista de métodos siempre que todos devuelvan el mismo tipo de datos, pero en una lista de varios métodos esto no tiene porqué ser así. Cuando el compilador detecta que un delegate devuelve void crea una instancia de MulticastDelegate en lugar de una de Delegate. 4/11 Marco Besteiro y Miguel Rodríguez Delegates y Eventos Eventos. En su sentido más concreto, un evento es un campo o propiedad de una clase o estructura. El tipo del evento es delegate, lo cual quiere decir que puede referenciar a una lista de métodos. Lo interesante de los eventos es que son utilizados por la clase a la que pertenecen para notificar que algo ha sucedido a otras clases. El modelo de eventos en el entorno .NET El modelo de eventos .NET se basa en los conceptos de productor o fuente de eventos y consumidor o manejador de eventos. Figura 8.3. Modelo de eventos en el entorno .NET El productor o fuente puede generar uno o varios eventos diferentes. El consumidor o manejador se suscribe a uno o varios eventos del productor para que se le notifique cuando un evento suceda. Para poder implementar el mecanismo de suscripción a eventos se utilizan los delegates. El productor tiene tantas instancias de tipo delegate como eventos pueda producir. Cuando un consumidor quiere suscribirse a un evento concreto ha de añadir a la lista de métodos del delegate correspondiente la referencia a un método (del consumidor) que será el que se invoque a través del delegate cuando se de el evento. Al método cuya referencia se pasa al delegate se llama método manejador del evento. El productor o fuente del evento. La clase que se comporta como fuente de eventos se caracteriza por tener un miembro de tipo event, el cual se construye a partir de un tipo delegate. Este miembro de tipo event es el que va a referenciar a la lista de métodos manejadores del evento. Un ejemplo de clase fuente o productor de eventos puede es: 5/11 Marco Besteiro y Miguel Rodríguez Delegates y Eventos //Delegate que será utilizado como tipo base del evento MiRefME public delegate void ReferenciaManejadoresEventos (object fuente, ArgumentosEvento eventArgs); //Clase productora o fuente del evento MiRefME public class MiFuenteEventos { //MiRefME es el evento, su tipo está determinado por el delegate //ReferenciaManejadoresEventos public event ReferenciaManejadoresEventos MiRefME; //Este método será llamado por los consumidores de eventos //para suscribirse al evento public void AñadirManejador (MiManejadorEventos manejador) { //Añade a la lista de métodos manejadores la referencia al //método ManejadorMiFuenteEventos, el cual ha de existir //en el objeto manejador y será el método del consumidor //al que se llame cuando se de el evento MiRefME this.MiRefME += new ReferenciaManejadoresEventos (manejador.ManejadorMiFuenteEventos); } //Este método será llamado cuando se desee lanzar el evento public void LanzarEvento () { //A través de MiRefME se invoca a todos los métodos //manejadores registrados en la lista referenciada por //MiRefME. this.MiRefME (this,new ArgumentosEvento ("MiFuenteEventos")); } } El elemento primordial de esta clase es public event ReferenciaManejadoresEventos MiRefME; al cual pueden suscribirse los consumidores o manejadores de eventos y a través del cual pueden ser invocados. El resto, es decir, los métodos de la clase, han sido creados para facilitar el manejo del evento. Es importante notar que la estructura de los métodos manejadores, cuyas referencias se añadirán a la lista de MiRefME, es determinada por el delegate: public delegate void ReferenciaManejadoresEventos (object fuente, ArgumentosEvento eventArgs); en este ejemplo como puede verse, al invocar a los métodos manejadores del evento se le pasan dos argumentos (que es el caso más común): • fuente representa la clase del objeto que ha generado el evento. • eventArgs representa los argumentos del evento. 6/11 Marco Besteiro y Miguel Rodríguez Delegates y Eventos Los argumentos del evento. Cuando se notifica un evento a un consumidor, se transmite al consumidor o manejador cierta información relativa al evento. Piense en un evento “pulsación de tecla” que se notifique a un formulario. Un dato interesante es el código de la tecla pulsada. A esta información relativa al evento se le llama argumentos del evento y lo normal es que sea un objeto de una clase derivada de la clase System.EventArgs. Siguiendo el ejemplo anterior: //Al notificar que ha sucedido un evento, lo más común //es que se desee dar alguna información más. //Esto puede hacerse utilizando una clase derivada de EventArgs //en la que se indiquen los argumentos del evento. public class ArgumentosEvento : EventArgs { public string fuenteEvento; public ArgumentosEvento (string fuenteEvento) { this.fuenteEvento = fuenteEvento; } } El consumidor del evento. El consumidor del evento es un objeto al que se le avisa o notifica que el evento ha ocurrido. Dicho objeto realiza una acción de respuesta a ese evento (por ejemplo, la clase Formulario puede querer ser avisada de un evento pulsación de tecla para mostrar el código de la tecla en una caja de texto). Para poder realizar la acción de respuesta, la clase consumidora del evento ha de poseer un método que será invocado cuando se de el evento y cuyo código provocará la respuesta. Siguiendo con el ejemplo anterior, la clase consumidora o manejadora puede ser: //Una clase manejadora del evento MiRefME ha de disponer del método //ManejadorMiFuenteEventos public class MiManejadorEventos { public void ManejadorMiFuenteEventos (object fuente, ArgumentosEvento eventArgs) { System.Console .WriteLine ("Se ha lanzado un evento"); System.Console.WriteLine ("El origen es: " + eventArgs.fuenteEvento); } } El método ManejadorMiFuenteEventos es el que deberá ser invocado cuando se dé el evento. Para ello ha de estar referenciado por la lista del evento MiRefME (obsérvese que los argumentos que espera son los que indica el delegate ReferenciaManejadoresEventos. 7/11 Marco Besteiro y Miguel Rodríguez Delegates y Eventos Suscripción al evento. Una vez se dispone de las clases productora o fuente y consumidora o manejadora es posible crear objetos de ambas y conectarlos entre sí. Al mecanismo de conexión entre manejador y fuente se le llama “suscripción” y consiste en añadir la referencia al método manejador del evento a la lista de referencias del evento (MiRefME). Siguiendo con el ejemplo anterior: public class Aplicacion { public static void Main(string [] args) { MiFuenteEventos MFE = new MiFuenteEventos (); MiManejadorEventos MME = new MiManejadorEventos (); //Suscripción al evento MFE.AñadirManejador (MME); ... ... Al pasarle la referencia al objeto MME (MiManejadorEventos) al método AñadirManejador de la fuente de eventos, se añade la referencia al método ManejadorMiFuenteEventos en la lista de manejadores del evento. A partir de ahora, cada vez que se de el evento MiRefME, se llamará al método ManejadorMiFuenteEventos del objeto MME. Es importante tener en cuenta que el modelo de eventos es un patrón de comportamiento que se ha diseñado para manejar la interacción del usuario con el sistema en las aplicaciones gráficas (aunque no es su única aplicación). De este modo puede comprenderse que un caso ejemplo es que un objeto Formulario desee suscribir un método OnClick() al evento click de un objeto de la clase Button. Así, cuando el usuario pulse el objeto de la clase Button se dará el evento click y se llamará al método OnClick() del objeto Formulario. Lanzamiento del evento. Cuando ocurre el evento ha de invocarse a todos los métodos manejadores suscritos al evento. En el caso de una aplicación gráfica esta situación puede corresponder a una pulsación con el ratón por parte del usuario sobre un botón. En este caso, el S.O. es el encargado de lanzar el evento, es decir, llamar al método OnClick() del objeto Formulario suscrito al evento click sobre el botón. En el ejemplo que se está siguiendo un modo de lanzar el evento es: MFE.LanzarEvento (); Con lo que la clase aplicación puede quedar como sigue: public class Aplicacion { public static void Main(string [] args) { MiFuenteEventos MFE = new MiFuenteEventos (); MiManejadorEventos MME = new MiManejadorEventos (); //Suscripción al evento 8/11 Marco Besteiro y Miguel Rodríguez Delegates y Eventos MFE.AñadirManejador (MME); System.Console.WriteLine ("Si desea lanzar el evento pulse Enter"); System.Console.ReadLine(); //Lanzamiento del evento MFE.LanzarEvento (); } } En este caso el evento es lanzado explícitamente cuando el usuario pulsa ENTER. El ejemplo completo. El resultado de unir el código comentado es: using System; //Al notificar que ha sucedido un evento, lo más común //es que se desee dar alguna información más. //Esto puede hacerse utilizando una clase derivada de EventArgs //en la que se indiquen los argumentos del evento. public class ArgumentosEvento : EventArgs { public string fuenteEvento; public ArgumentosEvento (string fuenteEvento) { this.fuenteEvento = fuenteEvento; } } //Delegate que será utilizado como tipo base del evento MiRefME public delegate void ReferenciaManejadoresEventos (object fuente, ArgumentosEvento eventArgs); //Clase productora o fuente del evento MiRefME public class MiFuenteEventos { //MiRefME es el evento, su tipo está determinado por el delegate //ReferenciaManejadoresEventos public event ReferenciaManejadoresEventos MiRefME; //Este método será llamado por los consumidores de eventos //para suscribirse al evento public void AñadirManejador (MiManejadorEventos manejador) { //Añade a la lista de métodos manejadores la referencia al //método ManejadorMiFuenteEventos, el cual ha de existir //en el objeto manejador y será el método del consumidor //al que se llame cuando se de el evento MiRefME this.MiRefME += new ReferenciaManejadoresEventos (manejador.ManejadorMiFuenteEventos); } //Este método será llamado cuando se desee lanzar el evento public void LanzarEvento () { 9/11 Marco Besteiro y Miguel Rodríguez Delegates y Eventos //A través de MiRefME se invoca a todos los métodos //manejadores registrados en la lista referenciada por //MiRefME. this.MiRefME (this,new ArgumentosEvento ("MiFuenteEventos")); } } //Una clase manejadora del evento MiRefME ha de disponer del método //ManejadorMiFuenteEventos public class MiManejadorEventos { public void ManejadorMiFuenteEventos (object fuente, ArgumentosEvento eventArgs) { System.Console .WriteLine ("Se ha lanzado un evento"); System.Console.WriteLine ("El origen es: " + eventArgs.fuenteEvento); } } public class Aplicacion { public static void Main(string [] args) { MiFuenteEventos MFE = new MiFuenteEventos (); MiManejadorEventos MME = new MiManejadorEventos (); //Suscripción al evento MFE.AñadirManejador (MME); System.Console.WriteLine ("Si desea lanzar el evento pulse Enter"); System.Console.ReadLine(); //Lanzamiento del evento MFE.LanzarEvento (); } } Al ejecutarlo se mostrará la siguiente pantalla: Figura 8.4 10/11 Marco Besteiro y Miguel Rodríguez Delegates y Eventos Si se pulsa la tecla ENTER se lanzará el evento: Figura 8.5 11/11