Delegados
Tal como hemos comentado, los eventos y los delegados están
muy unidos en C#. De hecho no se pueden definir eventos si no definimos
previamente un delegado; ya que por medio de ese delegado podremos crear o
asignar el método que se encargará de interceptar el evento.
Por esa razón veremos primero que son los delegados para de esta forma
comprender mejor todo lo que tenemos que hacer para definir eventos en nuestras
clases y desde los objetos creados a partir de ellas asociarlos a un método.
Definición "formal" de delegado
Veamos algunas de las
definiciones de la
documentación de Visual Studio sobre los delegados:
- "Los delegados habilitan escenarios que en otros lenguajes se han resuelto con punteros a función. No obstante, a diferencia de los punteros a función, los delegados están orientados a objetos y proporcionan seguridad de tipos."
- "Un delegado es una clase que puede contener una referencia a un método. A diferencia de otras clases, los delegados tienen un prototipo (firma) y pueden guardar referencias únicamente a los métodos que coinciden con su prototipo."
De estas dos definiciones podemos sacar en claro que los delegados son clases
especiales que pueden tener referencias a un método, y que ese método debe
cumplir con el "prototipo" definido por el delegado. Y que esa referencia que
contienen es como los punteros de otros lenguajes, pero que están enfocados a
ser utilizados desde el punto de vista de .NET, es decir, a ser usados desde una
perspectiva orientada a objetos.
Por tanto, cuando definimos un delegado, estamos definiendo la firma que debe
tener una función (o método), con idea de que podamos crear un "puntero" a dicha
función, pero de una forma "controlada" por el CLR de .NET. Por tanto, solo se
admitirán "punteros" a funciones que concuerden con la definición que ha hecho
el delegado.
Si retomamos la definición del delegado System.EventHandler definido
por .NET para los eventos, veremos que ese delegado realmente está definiendo la
"forma" que debemos declarar el método que intercepte un evento basado en ese
delegado.
Analicemos con algo de detalle la definición tanto del delegado EventHandler
como del evento TextChanged.
El delegado está definido de la siguiente forma:
public delegate void EventHandler(object sender, EventArgs e);
Si desechamos public delegate, nos quedamos con una definición de un método
que bien podríamos incluir en una interfaz, ya que lo que hace es indicar que
ese método debe ser de tipo void (no devuelve ningún valor), y que tiene dos
parámetros, el primero de tipo object y el segundo de tipo EventArgs.
Ahora veamos la definición del evento TextChanged:
public event EventHandler TextChanged;
Si en esta ocasión también obviamos las dos primeras instrucciones, tendremos
una declaración "típica" de C#, en la que el tipo de datos es EventHandler (el
delegado, que al fin y al cabo es una clase de tipo especial), seguida de "la
variable" que define el evento. Lo que notamos aquí es que no aparece por ningún
lado los parámetros que hay que usar, y esa es una de las características de los
delegados, y posiblemente lo que complica más su entendimiento.
Veamos ahora cómo asignamos el método al evento del control:
this.textBox1.TextChanged += new System.EventHandler(this.textBox1_TextChanged);
El evento lo conectamos por medio del constructor del delegado, el cual
espera como parámetro un método, y lo que le pasamos es "un puntero al método",
es decir, le indicamos dónde está ese método. Como hemos comentado antes, ese
método debe cumplir con las especificaciones indicadas por el delegado, cosa que
podemos comprobar si vemos el código del método que se utilizará cuando el
evento se produzca:
private void textBox1_TextChanged(object sender, EventArgs e) {...}
Y tal como podemos ver, el método cumple a la perfección con la definición
del delegado.
Utilizar un delegado para acceder a un método
Ahora veamos brevemente cómo usar los delegados, en este caso sin necesidad
de que defina un evento.
Como hemos comentado, un delegado realmente es una clase que puede contener
una referencia a un método, además define el prototipo del método que podemos
usar como referencia. Sabiendo esto, podemos declarar una variable del tipo del
delegado y por medio de esa variable acceder al método que indiquemos, siempre
que ese método tenga la misma "firma" que el delegado. Parece complicado
¿verdad? Y no sólo lo parece, es que realmente lo es. Comprobemos esta
"complicación" por medio de un ejemplo. En este código, que iremos mostrando
poco a poco, vamos a definir un delegado, un método con la misma firma para que
podamos usarlo desde una variable definida con el mismo tipo del delegado.
Definimos un delegado de tipo void que recibe un valor de tipo cadena:
delegate void Saludo(string nombre);
Definimos un método con la misma firma del delegado, el cual devolverá
Hola, seguido del contenido del parámetro:
static private void mostrarSaludo(string elNombre) { Console.WriteLine("Hola, " + elNombre); }
Ahora vamos a declarar una variable para que acceda a ese método.
Para ello debemos declararla con el mismo tipo del delegado:
Para ello debemos declararla con el mismo tipo del delegado:
Saludo saludando;
La variable saludando es del mismo tipo que el delegado Saludo. La cuestión
es ¿cómo o que asignamos a esta variable?
Primer intento:
Como hemos comentado, los delegados realmente son clases, por tanto podemos usar new Saludo y, según parece, deberíamos pasarle un nombre como argumento. Algo así:
saludando = new Saludo("Pepe");
Pero esto no funciona, entre otras cosas, porque hemos comentado que un
delegado contiene (o puede contener) una referencia a un método, y "Pepe" no es
un método ni una referencia a un método.
Segundo intento:
Por lógica y, sobre todo, por sentido común, máxime cuando hemos declarado un método con la misma "firma" que el delegado, deberíamos pensar que lo que debemos pasar a esa variable es el método, ya que un delegado puede contener una referencia a un método, por tanto podemos pensar que si creamos un nuevo objeto del tipo Saludo pasándole el nombre del método y el argumento...
saludando = new Saludo(mostrarSaludo("Pepe"));
Tampoco funciona, porque nos dice que espera un método.
Probemos entonces a indicarle sólo el nombre del método:
Probemos entonces a indicarle sólo el nombre del método:
saludando = new Saludo(mostrarSaludo);
Ahora sí, al menos no da error de compilación, aunque, no muestra nada, cosa
que es lógica porque en ningún sitio le hemos indicado la cadena que tiene que
usar para el saludo.
¿Cómo podemos hacer que se muestre el saludo?
A ver, recapacitemos, tenemos una variable (saludando), que es del
tipo del delegado (Saludo), y que "apunta" a un método (mostrarSaludo),
que espera que le pasemos como argumento una cadena.
¿Podremos hacer esto?
saludando("Pepe");
¡Pues sí! Ahora muestra el saludo a "Pepe".
En realidad, cuando estamos usando la línea anterior, es como si estuviésemos
llamando al método de esta forma:
mostrarSaludo("Pepe");
Qué forma de complicarnos la vida, ¿verdad?
Pero debemos pensar, que en este contexto funciona la llamada directa al
método porque está definido en la misma clase que la variable declarada como el
delegado.