Delegados en C#.

  Introducción.

  Uno de los temas que más suelen confundir a los nuevos programadores de la plataforma .NET, al menos a mí, son los delegados. Para los programadores de C++ el concepto es muy similar al de los punteros a funciones,   los programadores de JavaScript tienen una funcionalidad parecida a través de objetos Function (que rara vez se utilizan), pero para el resto es totalmente nuevo.

   Lo primero que vamos a hacer es intenter definir que son los delegados. Una buena definición seria: 

   Los delegados son un tipo que representa a una función con una determinada declaración.

   Vamos a analizar esta definición poco a poco, ya que dice mucho en muy pocas palabras ...

  • Los delegados son un TIPO.
  • Los delegados representan a una función.
  • La función debe tener una determinada declaración.

   Si un delegado es un tipo, entonces puedo crear variables de ese tipo. También hemos dicho que un delegado representa a una función, por lo que una variable creada de un tipo delegado es la representación de una determinada función. Por úlitmo hemos dicho que esta función debe tener una determinada declaración.

  Un ejemplo sencillo.

   Veamos un ejemplo que es como mejor se ven las cosas:


using
System;
namespace Devjoker.Ejemplos 
{
   delegate int myDelegate(int arg) ;
}

   En este caso hemos declarado un tipo delegado, al que hemos llamado myDelegate que representa a una función que devuelve un entero y que recibe como argumento también un entero. Como es un tipo no es necesario definirlo dentro de una clase. Como resultado vamos a poder declarar variables del tipo myDelegate.

   La siguiente pregunta seria,¿Como declaro variables de tipo myDelegate? ¿y como asigno "valores" a esa variable?. Los delegados se declaran como cualquier otro objeto en .NET, recordemos que todo en .NET son objetos y los delegados no son una excepción. Para asignar un delegado, no debemos asignar un "valor", sino un método, normalmente una funcion. El método al que asignemos el delegado no puede ser una función cualquiera, debe cumplir con la declaración del delegado. La forma de asignarlo es a través del constructor del delegado, pasando como argumento la función.

   A continuacion mostramos dos posibles funciones que cumplen con la declaración del delegado:


private
int delegateFunction1(int a)
{
      Console.WriteLine("Ejecutando ... delegateFuntion1");
      return ++a;
}
private int delegateFunction2(int a)
{
      Console.WriteLine("Ejecutando ... delegateFuntion2");
      return a;
}

    Como podemos ver tanto delegateFuntion1 como delegateFunction2 son functiones que devuelven un tipo int y reciben como parametro un int, que es la forma en la que hemos declarado el delegado.

   La declaración de una variable con el tipo delegado, es la siguiente, como cualquier otra variable en C#:


myDelegate
variableDelegado;

   Ahora vamos a ver como asignar un delegado. Hemos dicho que para asignar un delegado necesitamos utilizar el constructor del mismo. En el siguiente ejemplo vamos a ver un pequeño programa completo que declara y ejecuta un delegado:

using System;
namespace Devjoker.Ejemplos 
{
   delegate int myDelegate(int arg) ;
  
   class MainClass
   {
       public static void Main(string[] args)
      {
            MainClass instancia = new MainClass();           
            myDelegate variableDelegado;
            variableDelegado =
            new myDelegate(instancia.delegateFunction1);
            int a = variableDelegado(5);
            System.Console.WriteLine("El resultado obtenido es ... {0}",a);
      }
     
      private int delegateFunction1(int a)
      {
            Console.WriteLine("Ejecutando ... delegateFuntion1");
            return ++a;
      }

      private int delegateFunction2(int a)
      {
            Console.WriteLine("Ejecutando ... delegateFuntion2");
            return a;
      }
   }
}

   En este ejemplo declaramos una variable variableDelegado y la asignamos a la función delegateFunction1. Debido a que el método Main es estático, pero delegateFunction1 no lo es, necesitamos una instancia de la clase MainClass para referiros a la función de forma concreta. Por úlitmo ejecutamos nuestra variable (con un parámetro fijo 5) y mostramos el resultado en pantalla.  

   El programa genera una salida similar a la siguiente:


Ejecutando ... delegateFuntion1
El resultado obtenido es ... 6
Presione una tecla para continuar . . .

   Seguro que alguno de vosotros está pensando en que esto mismo podría hacerse invocando a la funcion sin más, y no andar con tanto lio. Por supesto, pero este es un ejemplo muy sencillo y no pretende mostrar los casos complicados, recordemos que queremos aprender que son y para que sirven los delegados.

   Un caso más complejo podría ser el siguiente, Imaginemos un control creado dinamicamente por el programa, sobre el que queramos programar su click ... La forma de hacerlo es con delegados.

   La función a la que asignamos el delegado no debe pertenecer a una clase en concreto, ni siquiera a un assemblie en particular, solo debe cumplir la declaración del delegado.

   Vamos ahora a ver un ejemplo algo más practico.

  Un ejemplo practico.

   El siguiente ejemplo, sin pretender entrar en mucha complejidad, es un sencillo programa que realiza su log de proceso bien por pantalla o en un fichero.

   El programa tiene tres clases:

  • MainClass, que es la clase que contiene el método main.

  • ProgramClass, que es la clase que contiene el programa principal.

  • FormasLog, que es la clase que contiene las diferentes formas para realizar el log del proceso.

   El programa tiene además un delegado, que vamos a utilizar para realizar el log del proceso.Tienbe la siguiente forma:


delegate void logDelegate(string arg);

   Veamos cada una de estas clases. Empezaremos con la clase FormasLog, que tiene dos métodos (que coinciden con la declaración del delegado), ConsoleLog y FileLog. ConsoleLog muestra un mensaje en pantalla y FileLog, escribe el mensaje en un fichero (ubicado en la misma ruta que el assemblie).

   El codigo de la clase es el siguiente:

class FormasLog
      {
            private static StreamWriter fw;
            public static void ConsoleLog(string texto)
            {
                  System.Console.WriteLine(texto);
            }
            public static void FileLog(string texto)
            {
                  try
                  {
                        bool append = true;
                        if (fw == null)
                        {
                              append = false;
                        }
                        fw =
                        new StreamWriter( "ficherolog.log",append);
                        fw.WriteLine(texto);
                        fw.Close();
                  }
                  catch (IOException e)
                  {                      
                        FormasLog.ConsoleLog(e.Message);
                  }
            }
      }

 

   La clase ProgramClass tiene los siguientes métodos y miembros:

  • logFunction, que es un miembro de tipo logDelegate y es la función encargada de realizar el log
  • Run, que es el método principal de la clase, unicamente comprueba que logFunction está asignada e itera 100 veces llamando al método Log.
  • SetLogMethod, este método es el más importante de todos, ya que recibe un parámetro de tipo logDelegate y lo asigna a logFunction para que pueda hacer su trabajo de forma correcta.
  • Log, este método ejecuta logFunction.

   El código completo de la clase se muestra a continuacion:


class
ProgramClass
      {
            logDelegate logFunction;
            public void Run()
            {
                  if (logFunction == null)
                  {
                  logFunction =
                  new logDelegate(FormasLog.ConsoleLog);
                  }
                  int i = 0;
                  do
                  {
                  logFunction(
                  "Este es el log! en la iteracion " + i.ToString());
                  }
                  while (i++<100);
            }
            public void SetLogMethod(logDelegate metodo)
            {
                  logFunction = metodo;
            }
          
            private void Log(string texto)
            {
                  logFunction(texto);
            }
      }

 

   Por último tenemos la clase MainClass que es la clase que contiene el método Main en el que se inicia la ejecución del programa.

   El método Main crea una instancia de la clase ProgramClass a la que llamamos "programa", y solicita al usuario que especifique una forma para realizar el log. Dependiendo de la selección del usuario llama al método SetLogMethod  del objeto "programa" pasandole una nueva variable de tipo logDelegate construida con las funciones que proporciona la clase FormasLog.

   El código de la clase es:

class MainClass
      {
            public static void Main(string[] args)
            {                
                  ProgramClass programa = new ProgramClass ();
                  string valor = "";
                  do
                  {
                  Console.WriteLine("?Que forma de log quiere?");
                  Console.WriteLine("1->Consola 2->Fichero");
                  valor = System.Console.ReadLine();
                  }
                  while (valor != "1" && valor!= "2" );                                 
                 
                  if (valor == "1")
                  {
                        programa.SetLogMethod(
                        new logDelegate(FormasLog.ConsoleLog));
                  }
                  else if (valor =="2")
                  {
                        programa.SetLogMethod(
                        new logDelegate(FormasLog.FileLog ));
                  }
                  programa.Run ( );
            }                        
      }

   La gran ventaja de esta forma de trabajar es que podemos cambiar la forma en la que el programa realiza el log desde el exterior del programa sin ningún tipo de esfuerzo. Imaginemos que las clase FormasLog  y ProgramClass constituyen un componente de software compilado en un assemblie miComponente.dll. Por otro lado, la clase MainClass pertenece a otro assemblie completamente diferente, otroAssemblie.dll. En este escenario es posible que las formas de log que hemos predeterminado no sean suficientes como para cubrir las necesidades del programa, por ejemplo el log debe guardarse en una base de datos. Podríamos escribir una nueva funcion y decirle al componente que la utilice, solo tendríamos que llamar al metodo SetLogMethod.

   El código completo del programa se muestra a continuación:


namespace Devjoker.Ejemplos
{
      delegate void logDelegate(string arg) ;
     
      class MainClass
      {
            public static void Main(string[] args)
            {                
                  ProgramClass programa = new ProgramClass ();
                  string valor = "";
                  do
                  {
                  Console.WriteLine("¿Que forma de log quiere?");
                  Console.WriteLine("1->Consola 2->Fichero");
                  valor = System.Console.ReadLine();
                  }
                  while (valor != "1" && valor!= "2" );                                 
                  if (valor == "1")
                  {
                        programa.SetLogMethod(
                        new logDelegate(FormasLog.ConsoleLog));
                  }
                  else if (valor =="2")
                  {
                        programa.SetLogMethod(
                        new logDelegate(FormasLog.FileLog ));
                  }
                  programa.Run ( );
            }
      }
      class ProgramClass
      {
            logDelegate logFunction;
            public void Run()
            {
                  if (logFunction == null)
                  {
                  logFunction =
                  new logDelegate(FormasLog.ConsoleLog);
                  }
                 
                  int i = 0;
                  do
                  {
                  logFunction(
                  "Este es el log! en la iteracion " + i.ToString());
                  }
                  while (i++<100);
            }
            public void SetLogMethod(logDelegate metodo)
            {
                  logFunction = metodo;
            }
            private void Log(string texto)
            {
                  logFunction(texto);
            }                       
      }
      class FormasLog
      {
            private static StreamWriter fw;
            public static void ConsoleLog(string texto)
            {
                  System.Console.WriteLine(texto);
            }
            public static void FileLog(string texto)
            {
                  try
                  {
                        bool append = true;
                        if (fw == null)
                        {
                              append = false;
                        }
                        fw = new StreamWriter( "ficherolog.log",append);
                        fw.WriteLine(texto);
                        fw.Close();
                  }
                  catch (IOException e)
                  {                      
                        FormasLog.ConsoleLog(e.Message);
                  }
            }
      }
 }

   Como hemos podido ver los delegados son una potentisima herramienta que ofrece, no solo C# sino la plataforma .NET.

   Espero que este articulo os haya servido de ayuda para entender un poco mejor los delegados, y si teneis alguna duda al respecto no dudeis en exponerla en los foros.

   Saludos y hasta la proxima, Devjoker

 

Pedro  Herrarte  Sánchez
Delegados en C#
Pedro Herrarte Sánchez

Pedro Herrarte, es consultor independiente, ofreciendo servicios de consultoría, análisis, desarrollo y formación. Posee mas de diez años de experiencia trabajando para las principales empresas de España. Es especialista en tecnologías .NET, entornos Web (ASP.NET, ASP.NET MVC,jQuery, HTML5), bases de datos (SQL Server y ORACLE) e integración de sistemas. Es experto en desarrollo (C#, VB.Net, T-SQL, PL/SQL, , ASP, CGI , C, Pro*C, Java, Essbase, Vignette, PowerBuilder y Visual Basic ...) y bases de datos (SQL Server y ORACLE). Pedro es MCP y MAP 2012, es fundador, diseñador y programador de www.devjoker.com..
Fecha de alta:16/11/2005
Última actualizacion:16/11/2005
Visitas totales:42269
Valorar el contenido:
Últimas consultas realizadas en los foros
Últimas preguntas sin contestar en los foros de devjoker.com