Implementación interna de los delegados

    Cuando hacemos una definición de delegado de la forma:


<modificadores> delegate <tipoRetorno> <nombre>(<parámetros>);

    El compilador internamente la transforma en una definición de clase de la forma:


 <modificadores> class <nombre>:System.MulticastDelegate
 {
  private object _target;
  private int _methodPtr;
  private MulticastDelegate _prev;
  
  public <nombre>(object objetivo, int punteroMétodo)
  {...}
  
  public virtual <tipoRetorno> Invoke(<parámetros>)
  {...}
  
  public virtual IAsyncResult BeginInvoke(<parámetros>,
AsyncCallback cb,  Object o)
  {...}
  
  public virtual <tipoRetorno> EndInvoke(<parámetrosRefOut>,
IASyncResult ar)
  {...}
 }

    Lo primero que llama la atención al leer la definición de esta clase es que su constructor no se parece en absoluto al que hemos estado usando hasta ahora para crear objetos delegado. Esto se debe a que en realidad, a partir de los datos especificados en la forma de usar el constructor que el programador utiliza, el compilador es capaz de determinar los valores apropiados para los parámetros del verdadero constructor, que son:

  • object objetivo contiene el objeto al cual pertenece el método especificado, y su valor se guarda en el campo _target. Si es un método estático almacena null.

  • int punteroMétodo contiene un entero que permite al compilador determinar cuál es el método del objeto al que se desea llamar, y su valor se guarda en el campo _methodPtr. Según donde se haya definido dicho método, el valor de este parámetro procederá de las tablas MethodDef o MethodRef de los metadatos.

    El campo privado _prev de un delegado almacena una referencia al delegado previo al mismo en la cadena de métodos. En realidad, en un objeto delegado con múltiples métodos lo que se tiene es una cadena de objetos delegados cada uno de los cuales contiene uno de los métodos y una referencia (en _prev) a otro objeto delegado que contendrá otro de los métodos de la cadena.

    Cuando se crea un objeto delegado con new se da el valor null a su campo _prev para así indicar que no pertenece a una cadena sino que sólo contiene un método. Cuando se combinen dos objetos delegados (con + o Delegate.Combine()) el campo _prev del nuevo objeto delegado creado enlazará a los dos originales; y cuando se eliminen métodos de la cadena (con o Delegate.Remove()) se actualizarán los campos _prev de la cadena para que salten a los objetos delegados que contenían los métodos eliminados.

    Cuando se solicita la ejecución de los métodos almacenados en un delegado de manera asíncrona lo que se hace es llamar al método Invoke() del mismo. Por ejemplo, una llamada como esta:


objDelegado(49);

    Es convertida por el compilador en:


objDelegado.Invoke(49);

    Aunque Invoke() es un método público, C# no permite que el programador lo llame explícitamente. Sin embargo, otros lenguajes gestionados sí que podrían permitirlo.

    El método Invoke() se sirve de la información almacenada en _target, _methodPtr y _prev, para determinar a cuál método se ha de llamar y en qué orden se le ha de llamar. Así, la implementación de Invoke() será de la forma:


public virtual <tipoRetorno> Invoke(<parámetros>)
{
 if (_prev!=null)
  _prev.Invoke(<parámetros>);                      
 return _target._methodPtr(<parámetros>);
}

 Obviamente la sintaxis _target._methodPtr no es válida en C#, ya que _methodPtr no es un método sino un campo. Sin embargo, se ha escrito así para poner de manifiesto que lo que el compilador hace es generar el código apropiado para llamar al método perteneciente al objeto indicado en _target e identificado con el valor de _methodPtr

    Nótese que la instrucción if incluida se usa para asegurar que las llamadas a los métodos de la cadena se hagan en orden: si el objeto delegado no es el último de la cadena. (_prev!=null) se llamará antes al método Invoke() de su predecesor.

    Por último, sólo señalar que, como es lógico, en caso de que los métodos que el objeto delegado pueda almacenar no tengan valor de retorno (éste sea void), el cuerpo de Invoke() sólo varía en que la palabra reservada return es eliminada del mismo.

Implementación interna de los 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:25/10/2006
Última actualizacion:25/10/2006
Visitas totales:13775
Valorar el contenido:
Últimas consultas realizadas en los foros
Últimas preguntas sin contestar en los foros de devjoker.com