Preeliminares
Durante el
tiempo el pasado dando clases de .Net me resulta particularmente llamativo que
al momento de preguntarle algún participante qué es un delegado muchas veces me
encuentro con caras de pánico a pesar de que muchos de sus programadores que
asisten a clase son gente realmente experta a su trabajo y lleva mucho tiempo
programando, sin temor a equivocarme puedo decir que menos del 10% de todos los
participantes ha esbozado una explicación medianamente correcta.
Y creo que
esto se debe no tanto a los programadores, toda vez que muchas veces no es tan necesario entender
para qué sirven los delegados sobre todo cuando Visual Studio se encarga de
generar automáticamente mucho del código que soporta el manejo de eventos
dentro de una aplicación y trae una gran cantidad de delegados listos para
usarse (el más común el EventHandler delegate). Menos culpa aún tienen los programadores
en Visual Basic ya que el mismo lenguaje encapsula toda la tubería de los
delegados y los eventos con sus funciones y palabras reservadas: Handles,
Addhandler, Removehandler, AddressOf y Withevents, inclusive me atrevo a decir
que un programador en VB.NET realmente puede olvidarse en la mayoría de los
casos de los delegados y a pesar de eso seguir siendo un excelente programador
en VB.NET.
Sin embargo
esto está por cambiar. Con el .NET Framework 3.5, Visual Basic 9.0 y C# 3.0 hay
que comprender más a detelle para qué sirven los delegados y de esta manera poder
asimilar y utilizar adecuadamente las nuevas “lambda expressions”
Haremos un
poco de historia y hablaremos de algo que se llama el “mecanismo de publicación
suscripción”
El patrón de diseño observador
Suponga que
está usted en un restaurante y revisa la carta para decidir que platillo
ordenar; en este escenario pueden pasar dos cosas cuando usted se decida: 1) Que
espere hasta que el mesero se aparezca en su mesa y le pregunte que desee y
consumir o 2) Que usted levante la mano y explícitamente le diga al mesero
cuando está listo para que el tome la orden. Evidentemente la opción 2 es la
más adecuada debido a que el mesero no lo tiene que estar esperando sino que
usted le indica cuando pedir y además si el mesero no está usted puede
llamarlo. Pues bien, sin ir muy lejos a esto se les llama mecanismo de
publicación suscripción y es la base del patrón de diseño definido por la banda
de los cuatro llamado el observador. En este caso el publicador es el Comensal
y el suscriptor es el cliente. Esto es la base para la implementación del patrón
de diseño observador
Delegados
Lo mismo
sucede en la programación. Todo el mecanismo de eventos de la plataforma. Net
está basado en delegados, en el ejemplo anterior el delegado sería la Comanda
que traería el platillo que usted eligió, esta comanda viajará y notificará,
aunque también notificará al chef, y al personal de caja para saber qué
cobrarle, algo muy parecido hacen los delegados: “apuntan” a aquellos métodos
que deben ser ejecutados cuando algo sucede.
En otras
palabras un delegado es ”como” un puntero de función de tipo seguro. Ese “como”
no está ahí por casualidad, está porque en el CLR no están definidos los
punteros, así que tenemos que usar un “como” y no un “es”
La
maravilla de los delegados es que no es necesario implementar una interfaz a
una clase (como los Listeners en Java) para poder escuchar un evento
determinado, sino que no importando la clase, cualquier método que cumpla con
la firma (lista de parámetros) puede ser usado para implementar el delegado, de
ahí se desprende lo del tipo seguro.
Por su
parte los eventos notifican a una clase que tiene una relación de composición o
agregación con otra (volvemos a lo mismo la clase compositora seria el
suscriptor) que algo ha sucedido, entonces el delegado lleva los datos hacia la
función adecuada.
A
continuación veremos cómo C# ha ido evolucionando para resolver el problema de
la delegación con sintaxis cada vez más elegante y menos verbosa.
Como lo
acabamos de decir, evidentemente, si un delegado es como un puntero de función,
forzosamente requeriremos de una función. En el caso de C# 1.2 (.NET 1.x)
tenemos explícitamente que declarar en una clase el método. El ejemplo del
comensal es este:
|
using System;
namespace DelegadosEstandar
{
//Delegado que sirve como base para el evento
ComandaOrdenada
public delegate void Orden(string pstrPlatillo);
class Mesero //Clase suscriptora
{
private Comensal lobjComensal;
public Mesero()
{
//Se instancia la clase, es
decir estamos haciendo una relación
// de composición
lobjComensal = new Comensal();
//Se genera el mecanismo de
publicación suscripción
//aqui nos suscribimos y
empezamos a "escuchar" si el comensal pide
lobjComensal.ComandaOrdenada += new Orden(TomarOrden);
}
public void EsperarPedido()
{
lobjComensal.PedirPlatillo();
}
//Este método se ejecutara
cuando se dispare el evento ComandaOrdenada
//en el comensal
public static void TomarOrden(string pstrPlatillo)
{
Console.WriteLine("Tomé la orden de " + pstrPlatillo);
Console.ReadLine();
}
}
class Comensal //Clase publicadora
{
//Este es el evento al que se
suscribirá el mesero
public event Orden ComandaOrdenada;
public void PedirPlatillo()
{
Console.WriteLine("Qué desea comer: ");
string lstrPlatillo = Console.ReadLine();
//Se dispara el evento mientras
Mesero "escucha"
ComandaOrdenada(lstrPlatillo);
}
}
class Programa
{
public static void Main(string[] args)
{
Mesero lobjMesero = new Mesero();
lobjMesero.EsperarPedido();
}
}
}
|
Como
podemos apreciar en el ejemplo anterior, existen 3 clases
a) Mesero:
Es el suscriptor que escucha cuando el Comensal pide algo
b)
Comensal: Publica la orden y genera el evento ComandaOrdenada cuando se ha
decidido por algo, notese que el evento es del tipo del delegado
c)
Programa: Punto de entrada de la aplicación
En este
caso la clase programa instancia al Mesero, además de ejecutar su método
EsperarPedido del mismo, el método EsperarPedido llama a PedirPlatillo que a su vez lee
información del teclado. Cuando se teclea el platillo se dispara el evento
ComandaOrdenada.
Métodos Anónimos
Como
podemos ver esto resulta muy sencillo, sin embargo la sintaxis es algo verbosa.
¿Qué sucedería si yo quiero manejar un evento dentro de la misma clase?. Es
decir que el mismo Comensal maneje su orden sin necesidad de llamar siquiera a
un método. A este tipo de sintaxis se le denomina método anónimo y resultan
parecidas a las inner classes en Java.
El objetivo
principal del método anónimo es reducir la cantidad de código requerida para
establecer una funcionalidad con delegados, sobre todo cuando no estamos interesados
en involucrar relaciones entre las clases. Los métodos anónimos se introdujeron
en C# 2.0 (.NET 2.0)
El ejemplo
del Comensal utilizando un método anónimo la encontramos a continuación:
|
//////////////////Metodos
anonimos con eventos
using System;
namespace DelegadosconMetodosAnonimos
{
public delegate void Orden(string
pstrPlatillo);
class Mesero
{ //Esta
clase ya no tiene la implementación de la toma de pedido
//sino
que se encuentra dentro del mismo Comensal
private Comensal
lobjComensal;
public Mesero()
{
lobjComensal = new Comensal();
}
public void
EsperarPedido()
{
lobjComensal.PedirPlatillo();
}
}
class Comensal
{
//Este
es el evento al que se suscribe el mesero
public event Orden ComandaOrdenada;
public void
PedirPlatillo()
{
Console.WriteLine("Qué desea comer:
");
string lstrPlatillo = Console.ReadLine();
//Metodo
anonimo, ya no necesito el método en la
//clase
Mesero para hacer la suscripción
ComandaOrdenada += delegate(string pstrPlatillo)
{
Console.WriteLine("Tomé la orden de
" + pstrPlatillo);
Console.ReadLine();
};
ComandaOrdenada(lstrPlatillo);
}
}
class Programa
{
public static void Main(string[] args)
{
Mesero lobjMesero = new Mesero();
lobjMesero.EsperarPedido();
}
}
}
|
Expresiones Lambda
Como podemos
apreciar, la misma definición del comportamiento del manejador de evento está
dentro de la misma clase comensal, no
fue necesario implementar un método, sino que automáticamente definimos el
código necesario, simplemente se escribió la palabra clave delegate dentro del
cuerpo del método PedirPlatillo en el Comensal.
Cabe
señalar que inclusive si usted desea puede quitar la firma de la palabra clave
delegate dentro del método Main y de todos modos se hará la suscripción.
En el caso
de C# 3.0 (.NET 3.5) se introduce el concepto de expresiones lambda que pueden
ser utilizadas en cualquier contexto donde se pueda utilizar un método anónimo,
reduciendo aun más la cantidad de código requerido y evidentemente aumentando
la legibilidad.
Para generar
expresiones lambda en C# hay que usar el operador => que significa “apunta a” o “va a”. A continuación
el ejemplo del Comensal con una expresión lambda.
|
//Lambda con eventos
using System;
namespace ExpresionesLambda
{
public delegate void Orden(string pstrPlatillo);
class Mesero
{ //Esta clase ya no tiene la
implementación de la toma de pedido
private Comensal lobjComensal;
public Mesero()
{
lobjComensal = new Comensal();
}
public void EsperarPedido()
{
lobjComensal.PedirPlatillo();
}
}
class Comensal
{
//Este es el evento al que se
suscribe el mesero
public event Orden ComandaOrdenada;
public void PedirPlatillo()
{
Console.WriteLine("Qué desea comer: ");
string lstrPlatillo = Console.ReadLine();
//Expresión lambda, más limpia y
legible que el método anónimo
ComandaOrdenada += pstrPlatillo
=> Console.WriteLine("Tomé la orden de " + pstrPlatillo);
ComandaOrdenada(lstrPlatillo);
Console.ReadLine();
}
}
class Programa
{
public static void Main(string[] args)
{
Mesero lobjMesero = new Mesero();
lobjMesero.EsperarPedido();
}
}
}
|
¿Como ven? ¿Está
padre no? ¿Y Basic? ¿Y el hermoso Basic? ¿Se quedó atrás? Pues ¡No! En Visual
Basic las lambda expressions también son soportadas. Básicamente es lo mismo
sólo que hay que usar la palabra clave Function en lugar de => además de que
las expresiones lambda en VB siempre deben producir un valor o no se dejará
compilar… Visualbeisiqueros del mundo… ¡Deleitáos con expresiones lambda en VB!
Aquí os presento el mismo código anterior pero en VB
|
Public Delegate Sub Orden(ByVal lstrPlatillo As String)
Public Class Mesero
Private lobjComensal As New Comensal
Public Sub EsperarPedido()
lobjComensal.PedirPlatillo()
End Sub
End Class
Public Class Comensal
Public Event ComandaOrdenada As Orden
Public Sub PedirPlatillo()
Console.WriteLine("Qué desea comer: ")
Dim lstrPlatillo As String = Console.ReadLine()
'Expresion lambda en VB a
diferencia de C#
'aqui siempre de devolver un
valor
Console.WriteLine((Function(pstrPlatillo) "Tomé la orden de " + pstrPlatillo)(lstrPlatillo))
'Disparamos el evento
RaiseEvent ComandaOrdenada(lstrPlatillo)
Console.ReadKey()
End Sub
End Class
Module ExpresionesLambda
Sub
Main()
Dim lobjMesero As New Mesero()
lobjMesero.EsperarPedido()
End Sub
End Module
|
Pues así
esto de las lambda expressions. Ojalá les haya gustado.
Este
artículo fue inspirado y dedicado para los participantes de Intersoftware de 2º
semestre de MCTS. (Marco, Sergio, Julio, Juan, Lalo) que según no les entraban los
delegados. Después de varias clases, re-explicando, frustración, enojos y
antiácidos parece que el concepto ya quedó verdaaaad?????. Jaja no se crean.
Sin más que
decir Salu2 a todos, recuerden que cualquier duda o concepto que no haya
quedado claro lo pueden postear y vemos como lo resolvemos.
¡Feliz
Codificación!
Miguel A. Morán B.
Microsoft MVP Visual Developer