martes, 2 de agosto de 2011

Delegados en VB.NET (I)

Llevo tiempo programando en .NET y reconozco que un tema que me cuesta muhco son los delegados.

Siendo así, he intentado ponerme las pilas sobre este asunto y ahora os intentaré contar con mis propias palabras que son los delegados.

Lo primero es encontrar una definición clara y sencilla de que es un delegado.
Yo diría que un delegado es un tipo que guarda una referencia a un método.

Lo segundo es tener claro para qué sirve un delegado.
Según mi experiencia, un delegado permite pasar un método como un parámetro a otro método.

Vamos a ver un ejemplo sencillo y así seguro todos los entenderemos mejor.

En nuestro ejemplo tenemos una clase Calculadora con un método Sumar. El método Sumar realmente no sabe (o no quiere saber) cómo llevar a cabo esta operación, así que además de pedir 2 números también pide una operación. Realmente es aquí donde aparece el delegado, es decir ¿Qué significa que el método Sumar está pidiendo una operación? Lo que está haciendo el método Sumar es pedir un método para poder realizar la operación, es decir, estamos pasando un método como parámetro al método Sumar que se llamará cuando el propio Sumar tenga que realizar la operación de suma. En otros palabras “Sumar está delegando en un método para llevar a cabo su implementación”.

' Declaramos el delegado y determinamos que recibirá 2 números y devolverá otro número

Public Delegate Function TuOperacionDeSuma(ByVal numero1 As Integer, ByVal numero2 As Integer) As Integer

 

Public Class Calculadora

    ' Este método recibe una variable del tipo TuOperacionDeSuma (delegado)

    Public Function Sumar(ByVal numero1 As Integer, ByVal numero2 As Integer, ByVal operacion As TuOperacionDeSuma) As Integer

        ' Sumar realmente no sabe sumar, delega en el método que ha recibido como parámetro

        Return operacion(numero1, numero2)

    End Function

End Class


Viendo este código está claro que si queremos utilizar el método Sumar tenemos que ser nosotros mismos quien hagamos un método que sume y pasárselo al método Sumar (está claro que el ejemplo no nos sacará de pobres, pero…), ¿Cómo tiene que ser ese método? Pues es la propia declaración del delegado quién nos está diciendo que firma tiene que tener, es decir, el delegado guarda una referencia a un método con una determinada firma (no guarda una referencia a cualquier método sino a un método que cumple con la declaración expuesta). De este modo, haríamos lo siguiente:

    ' Este método encaja en la definición del delegado TuOperacionDeSuma

    Public Function MiOperacionDeSuma(ByVal numero1 As Integer, ByVal numero2 As Integer) As Integer

        Return numero1 + numero2 ' Super implementación dificil de la muerte ;-)

    End Function

 

    Sub Main()

        Dim calc As New Calculadora

        ' Declaramos una variable del tipo del delegado

        ' En el constructor le pasamos la referencia al método al que queremos referenciar

        Dim metodo As New TuOperacionDeSuma(AddressOf MiOperacionDeSuma)

        ' Llamamos a Sumar y le pasamos nuestra variable del tipo TuOperacionDeSuma
       
' Es decir, le pasamos la operación con la que podrá Sumar

        calc.Sumar(5, 5, metodo)

    End Sub

 

Respecto a este código cabe aclarar que en VB.NET la forma de obtener una referencia a un método es con el operador AddressOf.

En realidad, AddressOf es aún más versátil (o engaña-bobos según se mire) y podríamos haber escrito este otro código que sería equivalente:

    Sub Main()

        Dim calc As New Calculadora

        calc.Sumar(5, 5, AddressOf MiOperacionDeSuma)

    End Sub

 

Si nos fijamos, aquí no hemos declarado una variable del tipo TuOperacionDeSuma, sino que simplemente hemos utilizado AddressOf. Esto es porque AddressOf crea de forma implícita una instancia del delegado TuOperacionDeSuma y le pasa la referencia al método MiOperacionDeSuma.

¿Qué cuál es la forma idónea de utilizar AddressOf? Pues a mí me gusta más la primera porque es más explícita y por eso me entero más de lo que está ocurriendo, pero para gustos los colores.

Finalmente, podríamos decir que un delegado permite pasar como parámetro un método a otro método, y que este último ejecute el método suministrado, o dicho de otro modo, un delegado es un objeto que permite llamar a métodos de otros objetos.

También es importante saber cómo este ejemplo quedaría en C#. Digo esto porque últimamente estoy leyendo mucho código C# (no por gusto sino porque parece que abunda más) y a veces, el no entender la sintaxis de C# no me permite avanzar todo lo que quisiera.

Primero, la clase Calculadora:

public delegate int TuOperacionDeSuma(int numero1, int numero2);

 

public class Calculadora

{

    public int Sumar(int numero1, int numero2, TuOperacionDeSuma operacion)

    {

        return operacion(numero1, numero2);

    }

}


Después, usamos la clase Calculadora:

        static void Main(string[] args)

        {

            var calc = new Calculadora();

            var metodo = new TuOperacionDeSuma(MiOperacionDeSuma);

            calc.Sumar(5, 5, metodo);

        }

 

        public static int MiOperacionDeSuma(int numero1, int numero2)

        {

            return numero1 + numero2;

        }

 

Como podemos observar, en C# no existe AddressOf, así que tenemos que optar por la versión explícita de declaración y asignación del delegado. Además, el método MiOperacionDeSuma lo hemos tenido que declarar como estático (Shared en VB), pero esto es simplemente porque en VB estamos trabajando en un módulo, y como ya sabemos, en C# no existen los módulos sino clases con todos su métodos estáticos (pero ese es otro tema y además no es relevante para nuestro ejemplo).

A partir de aquí (y sabiendo ya que es un delegado… no me lo puedo creer!), hay otro tema que es necesario aclarar y es que un delegado no es lo mismo que un evento, pero los eventos se basan en delegados para su correcto funcionamiento. Digo esto porque siempre que se habla de delegados también aparecen los eventos en la conversación, pero hay tener claro que no son lo mismo.

Un ejemplo de una clase con un evento en VB:

Public Class Calculadora

    ' Declarar evento

    Public Event PrevioSumar(ByVal resultadoPrevio As Integer)

 

    Public Function Sumar(ByVal numero1 As Integer, ByVal numero2 As Integer) As Integer

        RaiseEvent PrevioSumar(numero1 + numero2) ' Lanzar evento

        Return numero1 + numero2

    End Function

End Class

 

Module Module1

    ' Método que encaja en la definición del evento

    Public Sub PrevioSumar(ByVal resultadoPrevio As Integer)

        ' TODO

    End Sub

 

    Sub Main()

        Dim calc As New Calculadora

        ' Agregar un manejador de evento

        AddHandler calc.PrevioSumar, AddressOf PrevioSumar

        calc.Sumar(5, 5)

    End Sub

End Module


En VB.NET los eventos no parecen ser delegados, es decir, yo no veo por ningún lado la palabra delegate ni nada parecido, pero sin embargo utilizo AddressOf (uhmm, sospechoso).

¿Alguna vez te has parado a pensar cómo funcionan los eventos?

Según la MSDN, “Los eventos proporcionan un medio de que una clase u objeto informe a otras clases u objetos cuando sucede algo relevante. La clase que envía (o produce) el evento recibe el nombre de editor y las clases que reciben (o controlan) el evento se denominan suscriptores.”. Si te fijas, los eventos sirven para “informar” de algo (no para invocar código de otros objetos como sucede con los delegados). Además, un editor (quien expone el evento) mantiene una lista de suscriptores, es decir, el evento Click de un botón puede tener uno o varios manejadores asociados, esto es de primero de Windows Forms ;-)

¿Y si un evento no es un delegado porque siempre aparece la palabra evento cuando hablamos de delegados? Pues porque los eventos y en concreto los editores, tienen que mantener una lista de métodos de sus suscriptores a los que llamar cuando se lance el evento y justamente esa lista de métodos a los que llamar, se mantiene con delegados, es decir, los eventos utilizan delegados para notificar el evento a sus suscriptores ¿Cómo sino podría un editor ejecutar métodos en sus suscriptores?

El problema está en que (como de costumbre), VB.NET nos abstrae de todo esto y no podemos ver que pasa realmente cuando utilizamos AddHandler, estamos simplemente utilizando un atajo para asignar un delegado.

clip_image001

AddHanlder nos pide un evento (en nuestro caso calc.PrevioSumar) y un delegado. El delegado lo conseguimos con AddressOf (que recuerda creaba de forma implícita un delegado del tipo adecuado), pero ¿Qué delegado? Yo sólo declaré un evento…

Si abrimos el explorador de objetos para buscar un delegado de tipo PrevioSumar o algo parecido, vemos que no aparece por ninguna parte pero, sin embargo, te prometo que está por ahí esperando que alguien lo descubra.

¿Cómo descubrirlo (que no quiero quedar por mentiroso)? Pues es sencillo si compilamos la clase Calculadora en un ensamblado (librería de clases) y ahora creamos un nuevo proyecto en C# de tipo consola y agregamos la referencia a nuestro ensamblado Calculadora. De este modo, C# nos iluminará el camino!

clip_image002

Lo cierto es que a veces VB.NET me saca un poco de mis casillas. Con el explorador de objetos en VB.NET no aparecería nada pero en C# aparece como por arte de magia el delegado PrevioSumarEventHandler. Lo de EventHandler es porque todos los eventos heredan de MulticastDelegate (para poder mantener una lista de delegados y no sólo uno) y además el delegado que se crea para el evento siempre tiene el nombre <Evento>EventHandler.

Ahora que ya en C# vemos el delegado, tenemos las siguientes posibilidades para suscribirnos al evento:

        static void Main(string[] args)

        {

            var calc = new ClassLibrary1.Calculadora();

            // Declaramos un delegado del tipo PrevioSumarEventHandler con

            // una referencia al método PrevioSumar

            var metodo = new ClassLibrary1.Calculadora.PrevioSumarEventHandler(PrevioSumar);

            // Nos suscribimos al evento PrevioSumar

            // Como es MultiCastDelegate, lo hacemos con +=

            calc.PrevioSumar += metodo;

            calc.Sumar(5, 5);

        }

 

        public static void PrevioSumar(int resultadoPrevio)

        {

            // TODO

        }

 

Esta es la opción más explícita, donde creamos un delegado del tipo PrevioSumarEventHandler y después nos suscribimos al evento. Sin embargo, se suele utilizar esta otra forma, que es más directa y seguro la hemos visto millones de veces cuando leemos código de terceros:

        static void Main(string[] args)

        {

            var calc = new ClassLibrary1.Calculadora();

            calc.PrevioSumar += PrevioSumar;

            calc.Sumar(5, 5);

        }

 

Espero que después de todo lo visto aquí tengamos un poco más claro que son los delegados, que no son los delegados (eventos) y para qué sirven los delegados, y además todo ello en VB.NET, que a veces me vuelvo loco para encontrar código en mi lenguaje preferido.

Por cierto, este post se lo dedico a Dani!

Un saludo!

15 comentarios:

  1. muchas gracias..!! mejor explicado no podria ser.. hubo un momento en la lectura que casi me rindo, jeje pero llegue hasta el final y me lo digerí completito! lo voy a marcar para repasarlo y tenerlo siempre presente..!
    De nuevo Gracias! saludos de Torreon, Coah. Mex Luigui

    ResponderEliminar
  2. Hola Sergio, recién estoy estudiando esto de los delegados, pero aún no entiendo para que sirven, es decir pasarle un método a otro método cuando ya ese método debería tener la lógica para hacer la operación, no entiendo o tengo duro el cerebro :D. Gracias. Saludos.

    ResponderEliminar
  3. Desde Argentina, de todas las explicaciones que vi en la web me parecio una de las mas claras. Ahora estoy tratando de comparar los Delegados con expresiones Lambda. Exitos !!!

    ResponderEliminar
  4. Gracias por vuestros comentarios!
    Ikanus, no te preocupes que los delegados son un tema duro... para todos. De hecho, todavía hoy tengo algunas dudas al respecto ;-)
    Aunque no vayas a utilizar directamente los delegados en tu código, si utilizas Linq o ASP.NET MVC, tienes que entender cómo funcionan porque estas tecnologías hace un uso "intensivo" de los delegados... mejor dicho de las expressiones lambda.
    Un saludo.

    ResponderEliminar
  5. Increible... por fin logre comprender "algo" de delegados, creo que llegue hasta "deleg", leere el post unas 2 veces mas a ver si lo termino del todo! je. Igualmente, me sumo fuertemente a lo que dijo Ikanus, estaria bueno que puedas poner ejemplos de donde podriamos usar estas cosas...

    ResponderEliminar
  6. No estoy de acuerdo con tu explicación, no se trata de explicar como implementar o utlizar los delagdos en Net; la idea es
    -PARA QUE SIRVE
    -ESCENARIOS
    -QUE VALOR NOS RETORNA

    ResponderEliminar
    Respuestas
    1. Eso es facil por ejemplo: Si tienes una funcion general (300 lineas de codigo) que llamas desde varias paginas web, pero en cada pagina hay algo que agregar. Digamos en la pagina 1: la funcion debe validar los parametros enviados, en la pagina 2 : los parametros se insertaran a una base de datos, en la pagina 3:los parametros se formatean. Asi evitas tener 2 o mas funciones casi iguales.

      el valor que retorno es el que defines en el Func(OF "tipo parametro"), "tipo de retorno")

      Eliminar
  7. Hola Anónimo:
    Lo cierto es que me gustaría tener ejemplos concretos que escribir para su uso con delegados, pero en todo el tiempo que llevo programando, cierto es que no he tenido necesidad de crear nuevos delegados. Sin embargo usarlos... a diario. Si trabajas con LINQ o con ASP.NET MVC, trabajarás mucho con ellos, sobre todo con los delegados genéricos (Action y Func).
    El propósito de este post era mas "entenderlos"... que es un primer paso para neófitos en el tema como yo.
    En cualquier caso, si encuentras información que pueda ayudarnos a todos, estaría encantado de estudiarla contigo y publicar casos concretos de uso de delegados.
    Un saludo y gracias por comentar!

    ResponderEliminar
  8. En realidad no sabes cuanto me has ayudado, muchas gracias!!

    ResponderEliminar
  9. Muy buena info. Gracias.

    ResponderEliminar
  10. Gracias Sergio, al menos tengo claro cual es una de las funciones de los delegados, buenísimo.

    ResponderEliminar
  11. Gracias, me alegro de que te haya servido, un saludo!

    ResponderEliminar
  12. Excelente, muchas gracias. Estoy cursando vb.Net en la facu y no estaba logrando entender el funcionamiento de los delegados. Gracias a tu explicación me quedó mucho más claro. Nuevamente GRACIAS!

    ResponderEliminar
  13. Buena información amigo. Gracias por compartir.

    ResponderEliminar