lunes, 23 de julio de 2007

Delegados en C#

Krlo [blackhat4all@gmail.com]

Los delegados son una herramienta de gran utilidad en .NET. Aunque son usados sólo en casos muy específicos, conocerlos es una gran habilidad de nuestro lado que nos permitirá resolver muchos problemas en el futuro. Para los programadores de C++, el concepto es muy similar al de los punteros a funciones; los programadores de JavaScript tienen una funcionalidad parecida a través de objetos Function (que rara vez se utilizan). La tecnología .NET convirtió esta idea en una herramienta tan fácil de utilizar que es un pecado darle de largo.

Primero que todo vamos a definir qué son los delegados:

Los delegados son un tipo (una clase que, por tanto, ha de ser instanciada para utilizar) encargados de agrupar funciones con una signatura en común. Observe que dice "funciones", pues un delegado puede representar a varias funciones al mismo tiempo.

¿Cuál es el objetivo de un delegado?

El delegado puede ser llamado como una función con la idea de ejecutar, en el mismo orden en que entraron, todas sus funciones asignadas. Ésta es su principal funcionalidad, aunque también veo útil el que nos permita pasar funciones como parámetros a otras funciones. Podremos ver un ejemplo de esto más adelante.

Cómo declarar un delegado::

La siguiente instrucción declara un delegado:

delegate void Compara(int valor, int valor2) ;

El delegado anterior funciona como una clase de nombre "Compara". Esta clase puede contener cualquier función que acepte dos parámetros de tipo int y no retorne nada (void). Cualquier función con esta característica podrá ser agregada al delegado Compara.

Vamos ahora a instanciar delegados de este tipo y asignarle alguna función. Primero definimos dos funciones para utilizar con el delegado. La primera escribe por consola true si el primer valor es menor que el segundo, y la otra función hace exactamente lo contrario.

static void Metodo1(int valor, int valor2){
bool comp = valor < valor2 ;
Console.WriteLine(comp.ToString()) ;
}
static void Metodo2(int valor, int valor2){
bool comp = valor > valor2;
Console.WriteLine(comp.ToString());
}

Ahora instanciemos un objeto de tipo Compara y asignémosle estas funciones:

Compara c = new Compara(Metodo1) ;
c += Metodo2 ;
c(12, 4) ;

Queda bastante claro cómo el nuevo objeto c tiene asignada dos funciones: Método1 y Metodo2. Observar cómo se le agregó el Metodo2 usando el operador +=; esta estrategia es válida para agregarle más funciones al delegado, hasta cansarnos.

La salida por pantalla es:

False
True

Un ejemplo más fuerte::

Este ejemplo, aunque no es muy práctico, transmite la idea de cómo usar delegados para pasar funciones en los parámetros de otras funciones. Tenemos las siguientes funciones que pintan líneas de un punto a otro. Cada una lo hace de forma diferente.

void LineaPuntos(Point uno, Point dos){…} // pinta la línea con puntos equidistantes.
void LineaContinua(Point uno, Point dos){…}
// pinta la línea de forma continua.
void LineaAsterisco(Point uno, Point dos){…}
//pinta la línea con asteriscos equidistantes.

Ahora tenemos otros métodos que dibujan figuras. En el caso del cuadrado traza desde cada uno de sus puntos una línea al siguiente. El triángulo se dibuja de igual manera, al igual que un rombo, un rectángulo y cualquier otra figura que se nos ocurra. Un círculo lo podemos simular con líneas pequeñas, por ejemplo.

El problema llega cuando queremos tener la posibilidad de en tiempo de ejecución poder decidir qué figura dibujar y cómo queremos que dibuje sus líneas. Un método DibujaCuadrado para cada uno de los tipos de líneas no es eficiente. Pues si son n tipos de líneas y m tipos de figuras serían n*m métodos. Podríamos también tener un parámetro tipo_línea y, en dependencia de éste, el método DibujaCuadrado decide como dibujar la figura; pero está la situación de que alguien redescubra (nos llegue en una DLL) una forma de pintar líneas con ceros o con signos diferentes, y tendríamos nuevamente que modificar a DibujaCuadrado... definitivamente muy chapucero. Este es el código del método DibujaCuadrado usando un delegado del tipo Pintor.

delegate void Pintor(Point p, Point p2); //dibuja una línea de p a p2.
public void DibujaCuadrado(Point inicial, int lado, Pintor p){
Point A = inicial;
Point B = new Point(inicial.x, inicial.y+lado);
Point C = new Point(inicial.x + lado, inicial.y + lado);
Point D = new Point(inicial.x + lado, inicial.y) ;
p(A, B) ;
p(B, C) ;
p(C, D) ;
p(D, A) ;
}

Ahora podemos dibujar de una manera cómoda un cuadrado usando cualquier tipo de formato de líneas. Sólo tenemos que implementar los métodos LineaPuntos, LineaContinua y LineaAsterisco y asignárselo a nuestro gusto a un objeto de tipo Pintor; este objeto se lo pasamos a DibujaCuadrado y listo.

Conclusiones::

Ventajas de usar delegados:

  1. Los delegados son similares a los punteros a función de C++, pero poseen seguridad de tipos.
  2. Los delegados permiten pasar los métodos como parámetros.
  3. Los delegados pueden utilizarse para definir métodos de devolución de llamada.
  4. Los delegados pueden encadenarse; por ejemplo, se puede llamar a varios métodos en un solo evento.
  5. No es necesario que los métodos coincidan exactamente con la firma de delegado.
  6. La versión 2.0 de C# introduce el concepto de métodos anónimos, que permiten pasar bloques de código como parámetros en lugar de utilizar métodos definidos independientemente.

Los delegados se usan para implementar los eventos en .NET. Todos los botones tienen un delegado llamado Click de tipo EventHandler (este es el delegado como tal, instanciado dentro de la clase Button). Cuando queremos asignarle funconalidad a miBoton le asignamos un método a su delegado de la siguiente manera:

this.miBoton.Click += new System.EventHandler(this. miBoton_Click);

¿Nunca les ha pasado que borran un el método _Click y al compilar les da error en la línea anterior? Es que ese método estaba asignado a un delegado. Pueden borrar la línea con confianza.

Para saber más...



Artículos relacionados


No hay comentarios: