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. 🙂
10 comentarios
Comments feed for this article
jueves 18 \18-03:00 junio \18-03:00 2009 a 12:06:35
Sebastian Arg
muchas gracias por el articulo.
Estoy empezando con Vb.Net y la verdad no cazaba el asunto de delegates.
viernes 19 \19-03:00 junio \19-03:00 2009 a 16:06:12
Carlos Marcelo Santos
Estás empezando pero ya andás con Delegates… nada mal ¿no?
Me alegra haber sido útil.
viernes 12 \12-03:00 marzo \12-03:00 2010 a 10:03:58
Claudia
Hola Carlos,
Capaz me podes aconsejar con un tema que tengo a resolver y capaz que por el lado de delegates venga la solucion, pero como no conozco casi nada del tema de delegates me atrevi a preguntarte.
desarrollo en vbnet, basicamente tengo tres form, el principal, el de datos y el de reporte.. desde el principal debo llamar al de datos en forma modal, y el de datos debe llamar al de reporte tambien modal, El tema es que cuando en el de datos presiono el boton para que me abra el de reporte se me abre otro form de datos y despues el del reporte, osea se me estan duplicando-
Tenes idea de como resolver esto? Muchas Gracias
domingo 14 \14-03:00 marzo \14-03:00 2010 a 23:03:48
Carlos Marcelo Santos
Hola Claudia:
El comportamiento que me describís no parece ser un caso a resolver con delegados.
Te recomendaría revisar en tu código lo que produce esta situación, ya que llamar esa cadena de tres formularios es muy simple y a primera impresión no debiera haber nada que produzca ese resultado.
Saludos.
viernes 26 \26-03:00 marzo \26-03:00 2010 a 8:03:47
Alain Matute
Hola:
No termino de captar el concepto de delegados. Te explico el porque quiero/debo utilizarlo.
Tengo una aplicacion, ya desarrollada, pero para mejorarla estoy modificandola para que se ejecute en procesos multihilo. Resulta, que me comentaron de una excepcion muy común, InvalidOpertionException. La forma que hallado evitar dicha excepcion a sido CheckForIllegalCrossThreadCalls = False
Pero me han comentado que delegando no es necesario tal instuccion si delego.
El problema es que no se como implementarlo a mi proyecto.
Si pudieses mandarme una pequeña app haber si termino de enterderlo te lo agradeceria muchisimo.
Gracias.
domingo 28 \28-03:00 marzo \28-03:00 2010 a 15:03:44
Carlos Marcelo Santos
Hola Alain:
Para entender delegados es fundamental saber que estamos hablando de un puntero a una función. Este concepto es central.
No se si te interpreto correctamente, pero, creo que lo que necesitás es abrir Threads declarando delegados.
Te pido disculpas, pero no estoy con tiempo para escribir una aplicación como me pedís, pero puedo copiarte un ejemplo que Andrew Troelsen escribe en su libro Pro C# with .NET 3.0 en su capítulo 14 (ISBN-10: 1590598237, ISBN-13: 978-1590598238, http://www.amazon.com/Pro-C-NET-3-0-Special/dp/1590598237).
Me atrevo a incluir algunos comentarios mios.
El ejemplo trata sobre una operación asíncrona, esto no es lo importante y podés obviarla en una primera leida. Te lo aclaro porque quizá todo el manejo sobre la operación asíncrona sume confusión.
Lo que nos interesa es ver cómo abrir un Thread con delegados.
using System;
using System.Threading;
using System.Runtime.Remoting.Messaging;
namespace AsyncCalbackDelegate
{
//Declaro un delegado con la firma de Add
public delegate int BinaryOp(int x, int y);
class Program
{
//Al ejecutar vemos que Main corre en su Thread
static void Main(string[] args)
{
Console.WriteLine(«AsyncCallbackDelegate Example»);
Console.WriteLine(«Main() invoked on thread {0}.», Thread.CurrentThread.GetHashCode());
//Declaro un delegado b al que le paso la función Add
BinaryOp b = new BinaryOp(Add);
//IAsyncResult representa el estado de una operación asíncrona.
//Con BeginInvoque paso los parámetros de Add e indico que se ejecute
//AddComplete al finalizar al operaci+on asíncrona (Add).
//Al ejecutar podés ver que Main se ejecuta en un Thread, mientras que
//Add y AddComplete se ejecutan en otro.
//Esto es, estoy abriendo un Thread distinto al actual ejecutando el
//BeginInvoke del delegate.
IAsyncResult itfAR = b.BeginInvoke(10, 10, new AsyncCallback(AddComplete), null);
Console.ReadLine();
}
static void AddComplete(IAsyncResult itfAR)
{
Console.WriteLine(«AddComplete() Invoked on thread {0}.», Thread.CurrentThread.GetHashCode());
Console.WriteLine(«Your addition is complete.»);
AsyncResult ar = (AsyncResult) itfAR;
BinaryOp b = (BinaryOp) ar.AsyncDelegate;
Console.WriteLine(«10 + 10 is {0}.», b.EndInvoke(itfAR));
}
static int Add(int x, int y)
{
Console.WriteLine(«Add() invoked on thread {0}.», Thread.CurrentThread.GetHashCode());
Thread.Sleep(5000);
return x + y;
}
}
}
Podés copiarlo y pegarlo en una aplicación de consola. Yo lo ejecuté y funciona. La ejecución en si misma no aporta mucho, pero es más fácil leer el código en Visual Studio.
Espero que te sea útil.
Carlos.
viernes 26 \26-03:00 marzo \26-03:00 2010 a 8:03:25
Alain Matute
«Pero me han comentado que delegando no es necesario tal instuccion[ si delego.»]
miércoles 23 \23-03:00 junio \23-03:00 2010 a 17:06:37
GEoorge
Hola, queria consultarte por que estoy con una app en vb.net y necesitaría utilizar delegados para la funcionalidad de controladores fiscales. existe la posibilidad de hacerte una consulta un poco mas detallada?
Si te parece bien, puedo proveerte de la dicha funcionalidad en modo de pago.
miércoles 23 \23-03:00 junio \23-03:00 2010 a 17:06:39
Carlos Marcelo Santos
Si, siempre que esté a mi alcance.
domingo 12 \12-03:00 junio \12-03:00 2011 a 12:06:55
seotools
Amazing content!
Thank you for this share!
seo