Mejoras en la manipulación de delegados

Inferencia de delegados

Mientras que en C# 1.X siempre era necesario indicar explícitamente el delegado del objeto o evento al que añadir cada método utilizando el operador new, como en:

 
miObjeto.miEvento += new MiDelegado(miCódigoRespuesta);

    En C# 2.0 el compilador es capaz de inferirlo automáticamente de la definición del delegado en que se desea almacenar. Así, para el ejemplo anterior bastaría con escribir:


miObjeto.miEvento += miCódigoRespuesta;

    Sin embargo, asignaciones como la siguiente en la que el método no es asociado a un delegado no permiten la deducción automática del mismo y fallarán al compilar:


object o = miCódigoRespuesta;

    Aunque explicitando la conversión a realizar tal y como sigue sí que compilará:


object o = (MiDelegado) miCódigoRespuesta;

    En general, la inferencia de delegados funciona en cualquier contexto en que se espere un objeto delegado. Esto puede observarse en el siguiente ejemplo:


 using System;
 class A
 {
  delegate void MiDelegado(string cadena);
  
  public static void Main()
  {
   // En C# 1.X: MiDelegado delegado = new MiDelegado(miMétodo);
   MiDelegado delegado = miMétodo;
   // En C# 1.X: delegado.EndInvoke(delegado.BeginInvoke("Hola",
   // new AsyncCallback(métodoFin), null));

   delegado.EndInvoke(delegado.BeginInvoke("Hola", métodoFin, null));
  }
     
  static void miMétodo(string cadena)
  {
   Console.WriteLine("MiMétodo(string {0})", cadena);
  }
  static void métodoFin(IAsyncResult datos)
  {
   //…
  }
 }

Métodos anónimos

    C# 2.0 permite asociar código a los objetos delegados directamente, sin que para ello el programador tenga que crear métodos únicamente destinados a acoger su cuerpo y no a, como sería lo apropiado, reutilizar o clarificar funcionalidades. Se les llama métodos anónimos, pues al especificarlos no se les da un nombre sino que se sigue la sintaxis:


delegate(<parámetros>) {<instrucciones>};

    Y será el compilador quien internamente se encargue de declarar métodos con dichos <parámetros> e <instrucciones> y crear un objeto delgado que los referencie. Estas <instrucciones> podrán ser cualesquiera excepto yield, y para evitar colisiones con los nombres de métodos creados por el programador el compilador dará a los métodos en que internamente las encapsulará nombres que contendrán la subcadena reservada __. Nótese que con esta sintaxis no se pueden añadir atributos a los métodos anónimos.

    Con métodos anónimos, las asignaciones de métodos a delegados podría compactarse aún más eliminándoles la declaración explícita del método de respuesta. Por ejemplo:


 miObjeto.miEvento += delegate(object parámetro1, int parámetro2)
    { Console.WriteLine ("Evento producido en miObjeto"); };

    Incluso si, como es el caso, en el código de un método anónimo no se van a utilizar los parámetros del delegado al que se asigna, puede omitirse especificarlos (al llamar a los métodos que almacena a través suya habrá que pasarles valores cualesquiera) Así, la asignación del ejemplo anterior podría compactarse aún más y dejarla en:


miObjeto.miEvento += delegate {
Console.WriteLine ("Evento producido en miObjeto"); };

    En ambos casos, a partir de estas instrucciones el compilador definirá dentro de la clase en que hayan sido incluidas un método privado similar al siguiente:


 public void __AnonymousMethod$00000000(object parámetro1, int parámetro2)
 {
  Console.WriteLine ("Evento producido en miObjeto");
 }

    Y tratará la asignación del método anónimo al evento como si fuese:


miObjeto.miEvento += new MiDelegado(this,__AnonymousMethod$00000000);

    No obstante, la sintaxis abreviada no se puede usar con delegados con parámetros out, puesto que al no poderlos referenciar dentro de su cuerpo será imposible asignarles en el mismo un valor tal y como la semántica de dicho modificador requiere.

    Fíjese que aunque a través de += es posible almacenar métodos anónimos en un objeto delegado,  al no tener nombre no será posible quitárselos con -= a no ser que antes se hayan almacenado en otro objeto delegado, como en por ejemplo:


 MiDelegado delegado = delegate(object párametro1, int parámetro2)
  { Console.WriteLine ("Evento producido en miObjeto"); };
 miObjeto.miEvento += delegado;
 miObjeto.miEvento -= delegado;

    Los métodos anónimos han de definirse en asignaciones a objetos delegados o eventos para que el compilador pueda determinar el delegado donde encapsularlo. Por tanto, no será válido almacenarlos en objects mediante instrucciones del tipo:


 object anónimo = delegate(object párametro1, int parámetro2)
   { Console.WriteLine ("Evento producido en miObjeto"); }; // Error

    Aunque sí si se especificarse el delegado mediante conversiones explícitas, como en:


 object anónimo = (MiDelegado) delegate(object parametro1, int parámetro2)
  { Console.WriteLine ("Evento producido en miObjeto"); };

    Los métodos anónimos también pueden ser pasados como parámetros de los métodos que esperen delegados, como en por ejemplo:


 class A
 {
  delegate void MiDelegado();
  public void MiMétodo()
  {
     LlamarADelegado(delegate() { Console.Write("Hola"); });
  }
  void LlamarADelegado(MiDelegado delegado)
  {
     delegado();
  }                                 
 }

    Nótese que si el método aceptase como parámetros objetos del tipo genérico Delegate, antes de pasarle el método anónimo habría que convertirlo a algún tipo concreto para que el compilador pudiese deducir la signatura del método a generar, y el delegado no podría tomar ningún parámetro ni tener valor de retorno. Es decir:


class A
 {
  public void MiMétodo()
  {
     LlamarADelegado((MiDelegado) delegate { Console.Write("Hola"); });
  }

  void LlamarADelegado(delegate delegado)
  {
     MiDelegado objeto = (MiDelegado) delegado;
     objeto("LlamarADelegado");
  }                                 
 }

Captura de variables externas

    En los métodos anónimos puede accederse a cualquier elemento visible desde el punto de su declaración, tanto a las variables locales de los métodos donde se declaren como a los miembros de sus clases. A dichas variables se les denomina variables externas, y se dice que los métodos anónimos las capturan ya que almacenarán una referencia a su valor que mantendrán entre llamadas. Esto puede observarse en el siguiente ejemplo:


 using System;
 delegate int D(ref VariablesExternas parámetro);
 class VariablesExternas
 {
  public int Valor = 100;
  static D F()
  {
   VariablesExternas o = new VariablesExternas();
   int x = 0;
   D delegado = delegate(ref VariablesExternas parámetro)
      {
       if (parámetro==null)
        parámetro = o;
       else
        parámetro.Valor++;
       return ++x;
      };
   x += 2;
   o.Valor+=2;
   return delegado;
  }
  static void Main()
  {
   D d = F();
   VariablesExternas objeto = null;
   int valor = d(ref objeto);
   Console.WriteLine("valor={0}, objeto.Valor={1}", valor, objeto.Valor);
   valor = d(ref objeto);
   Console.WriteLine("valor={0}, objeto.Valor={1}", valor, objeto.Valor);
  }
 }

     Cuya salida es:

      valor=3, objeto.Valor=102

      valor=4, objeto.Valor=103

    Fíjese que aunque las variables x y o son locales al método F(), se mantienen entra las llamadas que se le realizan a través del objeto delegado d ya que han sido capturadas por el método anónimo que éste almacena.

    A las variables capturadas no se les considera fijas en memoria, por lo que para poderlas manipular con seguridad en código inseguro habrá que encerrarlas en la sentencia fixed.

    Debe señalarse que la captura de variables externas no funciona con los campos de las estructuras, ya que dentro de un método anónimo no se puede referenciar al this de las mismas. Sin embargo, la estructura siempre puede copiarse desde fuera del método anónimo en una variable local para luego referenciarla en el mismo a través de dicha copia. Eso sí, debe señalarse que en un campo de la estructura no se podrá realizar esta copiar ya que ello causaría ciclos en la definición de ésta.

Covarianza y contravarianza de delegados

    Los delegados también son más flexibles en C# 2.0 porque sus objetos admiten tanto métodos que cumplan exactamente con sus definiciones, con valores de retorno y parámetros de exactamente los mismos tipos indicados en éstas, como métodos que los tomen de tipos padres de éstos. A esto se le conoce como covarianza para el caso de los valores de retorno y contravarianza para el de los parámetros. Por ejemplo:


 using System;
 public delegate Persona DelegadoCumpleaños(Persona persona, int nuevaEdad);
 public class Persona
 {
  public string Nombre;
  private int edad;
  
  public int Edad
  {
   get { return this.edad; }
  }
  public event DelegadoCumpleaños Cumpleaños;
  public Persona(String nombre, int edad)
  {
   this.Nombre = nombre;
   this.edad = edad;
  }

  public void CumplirAños()
  {
   Cumpleaños(this, this.edad+1);
   this.edad++;
  }
 }
 public class Empleado:Persona
 {
  public uint Sueldo;
  
  public Empleado(string nombre, int edad, uint sueldo):base(nombre, edad)
  {
   this.Sueldo = sueldo;    
  }
 }
 public class Covarianza
 {
  static void Main()
  {
   Empleado josan = new Empleado("Josan", 25, 500000);
   josan.Cumpleaños += mostrarAños;
   josan.CumplirAños();
  }
  static Persona mostrarAños(Persona josan, int nuevaEdad)
  {
   Console.WriteLine("{0} cumplió {1} años", josan.Nombre, nuevaEdad);
   return josan;
  }
 }

    Nótese que aunque en el ejemplo el delegado se ha definido para operar con objetos del tipo Persona, se le han pasado objetos de su subtipo Empleado y devuelve un objeto también de dicho tipo derivado. Esto es perfectamente seguro ya que en realidad los objetos del tipo Empleado siempre tendrán los miembros que los objetos del Persona y por lo tanto cualquier manipulación que se haga de los mismos en el código será sintácticamente válida. Si lo ejecutamos, la salida que mostrará el código es la siguiente:

 Josan cumplió 26 años

 

Mejoras en la manipulación de delegados
José Antonio González Seco

José Antonio es experto en tecnologias Microsoft. Imparte cursos y conferencias en congresos sobre C# y .NET en Universidades de toda España (Sevilla, Barcelona, San Sebastián, Valencia, Oviedo, etc.) en representación de grandes empresas como Microsoft.
Fecha de alta:02/01/2007
Última actualizacion:02/01/2007
Visitas totales:10642
Valorar el contenido:
Últimas consultas realizadas en los foros
Últimas preguntas sin contestar en los foros de devjoker.com