DELEGADOS EN C# .NET | PUNTEROS A METODOS

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:

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:

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.


EJEMPLO DEL VIDEO



No hay comentarios:

Publicar un comentario

DELEGADOS EN C# .NET | PUNTEROS A METODOS

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 def...