Miguel Angel Morán

Machines take me by surprise with great frecuency...

Delegados, métodos anónimos y expresiones lambda

 

 

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

 

Posted: 08-08-2007 3:14 AM por Bichi | con 6 comment(s) |

Comentarios

judicator ha opinado:

Hola ;)

una pequeñna problema :

en cada codigo sale "Orden" en negro con fondo negro .......... tenemos de seleccionar el texto para ver todo el codigo ;)

Sino super, un poco de C# :-P

# August 23, 2007 3:23 PM

Bichi ha opinado:

Hola! Ya se le cambié el color.

jejeje, el siguiente post también será en C#, creo que estará muy bueno.

Gracias y saludos!!!!!!

# August 23, 2007 11:55 PM

Marcos ha opinado:

Que bueno el cambio del color, porque como lo tenias era mas que anonimo! Excelente articulo!!!

# August 26, 2007 9:43 PM

Bichi ha opinado:

Maldita sea Markitos. Bichi ha sido clonado!!!!!

Bueno, no importa, mientras no existan métodos invisibles y sólo sean anónimos...

Salu2!!!

# August 27, 2007 6:21 PM

accenturiano ha opinado:

buen articulo me sirvio de mucho

# November 21, 2007 4:51 PM

Polizont ha opinado:

Bien jugado marco, al fin pude entender a nuestros amigos delegados!!

# February 17, 2009 6:34 PM