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.