Hay situaciones en programación que no son de resolución trivial.

Supongamos que tenemos instanciado un objeto servidor y necesitamos conocer algún cambio de estado en el mismo.

Podríamos solucionarlo con un Timer, pero como solución es excesivamente costosa e impráctica. Hay quien sencillamente programa una función en el objeto servidor que avisa al cliente, un horror, desde el punto de vista de la orientación a objetos, impide la reutilización de código, etc.

Para este tipo de situaciones existen salidas más elegantes y efectivas que es lo que vamos a ver en esta nota.

Los delegados son algo similar a los punteros a funciones en C++, pero como corren sobre el Framework de .NET se trata de código administrado (code managed) y de tipo seguro.

Se usan para implementar métodos de devolución de llamada, callbacks, que son fundamentales para lograr:programar procesamiento asíncrono o para insertar código de cliente, entre las instancias que produce el servidor, para no usar timers donde no sea necesario.

Los métodos de devolución de llamada se usan en procesamiento asíncrono porque se desconoce el tiempo que va a tardar en ejecutarse el método que se invoca y que potencialmente podría ser largo.

En este caso vamos a ver como se usan delegados para insertar código de cliente, entre las devoluciones de llamada que produce el servidor.

Para entender esto, vamos a pensar en una aplicación que toma mediciones que pueden tardar en completarse, para el ejemplo no nos importa la medición, por eso la vamos a implementar como una medición simulada en una clase de nombre Measures. Tendremos además otra clase, Manager, que administra las mediciones y una aplicación de consola de nombre App.

Veamos primero la clase Measure: 

[C#]

using System; class Measure{

     /// <summary>

     /// Variable protegida que encapsula el valor de la propiedad Name.

    /// </summary>

     /// <remarks></remarks>

     protected string Name;

     /// <summary>

     /// Constructor de la clase que implementa una Medición simulada.

     /// </summary>

     /// <param name=»name»>Nombre concreto de la Medición.</param>

     /// <remarks>Es una forma de diferenciar las distintas instacias

    /// que se produzcan de esta clase.</remarks>

     public Measure(string name)

    {

        this.name = name;

    }

    /// <summary>

    /// Propiedad Name.

    /// </summary>

    /// <value></value>

    /// <returns>Nombre (Name) de la instancia.</returns>

    /// <remarks>Las últimas dos lineas del get simulan una demora en la

    /// toma de la medición, pongo un beep para que sea más perceptible la

    /// demora entre callbacks. Esto para poder apreciar mejor que se está

    /// haciedo un callback por cada medición, de otra forma el tipo de output de

    /// consola no da idea de cómo se ejecuta el código.</remarks>

    public string name

    {

        get

        {

            for (int i = 0; i <= 100000000; i++) { }            System.Media.SystemSounds.Beep.Play();            return this.Name;        }

        set

        {

            this.Name = value;

        }

    }

} 

[VB]

Imports System 

Public Class Measure

    »’ <summary>

    »’ Variable protegida que encapsula el valor de la propiedad Name.

    »’ </summary>

    »’ <remarks></remarks>

    Protected strName As String

    »’ <summary>

    »’ Constructor de la clase que implementa una Medición simulada.

    »’ </summary>

    »’ <param name=»name»>Nombre concreto de la Medición.</param>

    »’ <remarks>Es una forma de diferenciar las distintas instacias

    »’ que se produzcan de esta clase.</remarks>

    Public Sub New(ByVal name As String)

        MyBase.New()

        Me.name = name

    End Sub

    »’ <summary>

    »’ Propiedad Name.

    »’ </summary>

    »’ <value></value>

    »’ <returns>Nombre (Name) de la instancia.</returns>

    »’ <remarks>Las últimas tres lineas del Get simulan una demora en la

    »’ toma de la medición, pongo un beep para que sea más perceptible la

    »’ demora entre callbacks. Esto para poder apreciar mejor que se está

    »’ haciedo un callback por cada medición, de otra forma el tipo de output de

    »’ consola no da idea de cómo se ejecuta el código.</remarks>

    Public Property name() As String

        Get

            Dim i As Int64            For i = 0 To 100000000 : Next            Beep()            Return strName

        End Get

        Set(ByVal value As String)

            strName = value

        End Set

    End Property

End Class 

 

Seguimos ahora con la clase Manager: 

[C#]

using System; 

class Manager{

    /// <summary>

    /// Es una constante donde se define la cantidad de

    /// mediciones simuladas que se harán.

    /// </summary>

    /// <remarks></remarks>

    int kMeasure = 4;

    /// <summary>

    /// Arreglo de Measures que soporta las mediciones.

    /// </summary>

    /// <remarks></remarks>

    static Measure[] Measures;

    /// <summary>

    /// Definición de la función Delegada

    /// </summary>

    /// <param name=»pMeasure»>Recibe un objeto Measure como entrada</param>

    /// <remarks>Esta es la función sobre la que se implementa el método

    /// de devolución de llamada (callback) al nombre de esta función se

    /// lo postfija con Callback por convención con la idea de facilitar

    /// la lectura.</remarks>

    public delegate void EnumMeasuresCallback(Measure measure);

    /// <summary>

    /// Agrega mediciones simuladas.

    /// </summary>

    /// <remarks>Se agregan tantas como hayan definido en kMeasure.</remarks>

    public void AddMeasures()

    {

        Measures = new Measure[kMeasure];

        for (System.Int64 i = 0; i < kMeasure; i++)

        {

            Measures[i] = new Measure(«Measure « + (i + 1));

        }

    }

    /// <summary>

    /// Enumera mediciones, es la función que se invocará desde la aplicación.

    /// </summary>

    /// <param name=»callback»>Función delegada.</param>

    /// <remarks>Aceptando una función delegada como parámetro de entrada

    /// es la forma en que en que queda establecido efectivamente el callback.</remarks>

    public static void EnumMeasures(EnumMeasuresCallback callback)

    {

        foreach (Measure measure in Measures)

        {

            callback(measure);

        }

    }

}  

[VB]

Imports System 

Public Class Manager

    »’ <summary>

    »’ Es una constante donde se define la cantidad de

    »’ mediciones simuladas que se harán.

    »’ </summary>

    »’ <remarks></remarks>

    Private Const kMeasure As Integer = 4

    »’ <summary>

    »’ Arreglo de Measures que soporta las mediciones.

    »’ </summary>

    »’ <remarks></remarks>

    Private Shared Measures(kMeasure) As Measure

    »’ <summary>

    »’ Definición de la función Delegada

    »’ </summary>

    »’ <param name=»pMeasure»>Recibe un objeto Measure como entrada</param>

    »’ <remarks>Esta es la función sobre la que se implementa el método

    »’ de devolución de llamada (callback) al nombre de esta función se

    »’ lo postfija con Callback por convención con la idea de facilitar

    »’ la lectura.</remarks>

    Public Delegate Sub EnumMeasuresCallback(ByVal pMeasure As Measure)

    »’ <summary>

    »’ Agrega mediciones simuladas.

    »’ </summary>

    »’ <remarks>Se agregan tantas como hayan definido en kMeasure.</remarks>

    Public Sub AddMeasures()

        Dim i As Integer

        For i = 0 To kMeasure

            Measures(i) = New Measure((«Measure « + CStr(i + 1)))

        Next i

    End Sub

    »’ <summary>

    »’ Enumera mediciones, es la función que se invocará desde la aplicación.

    »’ </summary>

    »’ <param name=»callback»>Función delegada.</param>

    »’ <remarks>Aceptando una función delegada como parámetro de entrada

    »’ es la forma en que en que queda establecido efectivamente el callback.</remarks>

    Public Shared Sub EnumMeasures(ByVal callback As EnumMeasuresCallback)

        For Each mMeasure As Measure In Measures

            callback(mMeasure)

        Next

    End Sub

End Class 

 

Las lineas más destacables de Manager son  

[C#]

public delegate void EnumMeasuresCallback(Measure measure);

[VB]

Public Delegate Sub EnumMeasuresCallback(ByVal pMeasure As Measure)

Esta es la definición de la función sobre la que se implementará el callback 

[C#]

    public static void EnumMeasures(EnumMeasuresCallback callback)

    {

        foreach (Measure measure in Measures)

        {

            callback(measure);

        }

    }

[VB]

    Public Shared Sub EnumMeasures(ByVal callback As EnumMeasuresCallback)

        For Each mMeasure As Measure In Measures

            callback(mMeasure)

        Next

    End Sub

Esta es la función que se invocará desde la aplicación (que todavía no vimos), su característica más saliente es que recibe una función delegada como parámetro de entrada. 

Por último la aplicación de consola: 

[C#]

using System;

/// <summary>

/// Esta es la aplicación desde dónde compruebo el funcionamiento de los callbacks.

/// </summary>

/// <remarks>Esta primera línea de código

/// Public Shared miCallback As Manager.EnumMeasuresCallback = New Manager.EnumMeasuresCallback(AddressOf MeasuresCallback)

/// En esta definición es dónde efectivamente relaciono la función delegada

/// con una función de la aplicación. En otras palabras, delego la función

/// EnumMeasuresCallback (definida en Manager) en MeasuresCallback (definida

/// localmente). En esta misma linea estoy definiendo la varibal miCallback,

/// de tipo Delegate, implícito en Manager.EnumMeasuresCallback.</remarks>

class App{

    public static Manager.EnumMeasuresCallback miCallback = new Manager.EnumMeasuresCallback(MeasuresCallback);

    /// <summary>

    /// Acá se inserta el código que se quiere ejecutar por cada callback.

    /// </summary>

    /// <param name=»pMeasure»></param>

    /// <remarks></remarks>

    public static void MeasuresCallback(Measure measure)

    {

        Console.WriteLine(«Callback « + measure.name);

    }

    /// <summary>

    /// Rutina principal de la aplicación

    /// </summary>

    /// <remarks>Con mgr.AddMeasures(), agrego las mediciones simuladas.

    /// En la invocación

    /// Manager.EnumMeasures(miCallback)

    /// está la llamada a la función que implementa el callback.

    /// En la clase Manager podrá verse que es la función

    /// que recibe un Delegate como parámetro de entrada.

    /// Es así que le paso el delegado que definí más arriba

    /// con nombre miCallback.

    /// </remarks>

    public static void Main()

    {

        Manager mgr = new Manager();

        Console.WriteLine(«Agrego mediciones»);

        mgr.AddMeasures();

        Console.WriteLine(«Entra»);

        Manager.EnumMeasures(miCallback);

        Console.WriteLine(«Sale»);

        Console.ReadLine();

    }

} 

[VB]

Imports System 

»’ <summary>

»’ Esta es la aplicación desde dónde compruebo el funcionamiento de los callbacks.

»’ </summary>

»’ <remarks>Esta primera línea de código

»’ Public Shared miCallback As Manager.EnumMeasuresCallback = New Manager.EnumMeasuresCallback(AddressOf MeasuresCallback)

»’ En esta definición es dónde efectivamente relaciono la función delegada

»’ con una función de la aplicación. En otras palabras, delego la función

»’ EnumMeasuresCallback (definida en Manager) en MeasuresCallback (definida

»’ localmente). En esta misma linea estoy definiendo la varibal miCallback,

»’ de tipo Delegate, implícito en Manager.EnumMeasuresCallback.</remarks>

Class App

    Public Shared miCallback As Manager.EnumMeasuresCallback = New Manager.EnumMeasuresCallback(AddressOf MeasuresCallback)

    »’ <summary>

    »’ Acá se inserta el código que se quiere ejecutar por cada callback.

    »’ </summary>

    »’ <param name=»pMeasure»></param>

    »’ <remarks></remarks>

    Public Shared Sub MeasuresCallback(ByVal pMeasure As Measure)

        Console.WriteLine((«Callback « + pMeasure.name))

    End Sub

    »’ <summary>

    »’ Rutina principal de la aplicación

    »’ </summary>

    »’ <remarks>Con mgr.AddMeasures(), agrego las mediciones simuladas.

    »’ En la invocación

    »’ Manager.EnumMeasures(miCallback)

    »’ está la llamada a la función que implementa el callback.

    »’ En la clase Manager podrá verse que es la función

    »’ que recibe un Delegate como parámetro de entrada.

    »’ Es así que le paso el delegado que definí más arriba

    »’ con nombre miCallback.

    »’ </remarks>

    Public Shared Sub Main()

        Dim mgr As Manager = New Manager

        Console.WriteLine(«Agrego Mediciones»)

        mgr.AddMeasures()

        Console.WriteLine(«Entra»)

        Manager.EnumMeasures(miCallback)

        Console.WriteLine(«Sale»)

        Console.ReadLine()

    End Sub

End Class 

 

Lo fundamental en la aplicación son las siguientes líneas de código: 

[C#]

public static Manager.EnumMeasuresCallback miCallback = new Manager.EnumMeasuresCallback(MeasuresCallback);

[VB]

Public Shared miCallback As Manager.EnumMeasuresCallback = New Manager.EnumMeasuresCallback(AddressOf MeasuresCallback)

Acá se declara el delegado (miCallback) que se pasará como parámetro de entrada a la función EnumMeasures que implementa el callback en Manager. Es importante notar que la función de devolución de llamada recibe como parámetro de entrada la dirección de la función delegada 

[C#]

Manager.EnumMeasures(miCallback);

[VB]

Manager.EnumMeasures(miCallback)

En estas líneas estamos invocando a la función que implementa el callback, para eso se le pasa cómo parámetro el delegado definico al comienzo.  

[C#]

    public static void MeasuresCallback(Measure measure)

    {

        Console.WriteLine(«Callback « + measure.name);

    }

[VB]

    Public Shared Sub MeasuresCallback(ByVal pMeasure As Measure)

        Console.WriteLine((«Callback « + pMeasure.name))

    End Sub

Esta es la función delegada. En otras palabras en ella se delega el Delegate, como se define en la declaración de miCallback. 

Este es sólo un ejemplo que usa Delegates, hay mucho más sobre este tema.

La solución con los proyectos de ejemplo se pueden descargar de Delegates.zip.

Para quien quiera profundizar es recomendable cualquier libro, en especial el libro de Tom Archer que conocí gracias a la desinteresada recomendación de Daniel Calvin. 

ISBN: 84-481-3246-7

Autor: Tom Archer (http://blogs.msdn.com/tomarcher/)

Título: C# a fondo (http://www.cuspide.com/isbn/8448132467)
Editorial: Mc Graw Hill. 

 

Les mando un saludo. 🙂