D. MONITORING
IX. APPENDICES
<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 +oDelegate.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
Eventos
Concepto de evento
Un evento es una variante de las propiedades para los campos cuyos tipos sean delegados. Es decir, permiten controlar la forman en que se accede a los campos delegados y dan la posibilidad de asociar código a ejecutar cada vez que se añada o elimine un método de un campo delegado.
Sintaxis básica de definición de eventos
La sintaxis básica de definición de un evento consiste en definirlo como cualquier otro campo con la única peculiaridad de que se le ha de anteponer la palabra reservada event
al nombre de su tipo (que será un delegado) O sea, se sigue la sintaxis: <modificadores> event <tipoDelegado> <nombreEvento>;
Por ejemplo, para definir un evento de nombre Prueba y tipo delegado D se haría: public event D Prueba;
También pueden definirse múltiples eventos en una misma línea separando sus nombres mediante comas. Por ejemplo:
public event D Prueba1, Prueba2;
Desde código ubicado dentro del mismo tipo de dato donde se haya definido el evento se puede usar el evento tal y como si de un campo delegado normal se tratase. Sin embargo, desde código ubicado externamente se imponen una serie de restricciones que permiten controlar la forma en que se accede al mismo. Éstas son:
• No se le puede aplicar los métodos heredados de System.MulticastDelegate.
• Sólo se le puede aplicar dos operaciones: añadido de métodos con += y
eliminación de métodos con -=. De este modo se evita que se use sin querer =en vez de += ó -= y se sustituyan todos los métodos de la lista de métodos del
campo delegado por otro que en realidad se le quería añadir o quitar (si ese otro valiese null, ello incluso podría provocar una System.NullReferenceException) • No es posible llamar a los métodos almacenados en un campo delegado a través
del mismo. Esto permite controlar la forma en que se les llama, ya que obliga a que la llamada tenga que hacerse a través de algún método público definido en la definición del tipo de dato donde el evento fue definido.
La verdadera utilidad de un evento es que permite controlar la forma en que se asocian y quitan métodos de los objetos delegados con += y -=. Para ello se han de definir con la
siguiente sintaxis avanzada:
<modificadores> event <tipoDelegado> <nombreEvento>
{ add { <códigoAdd> } remove { <códigoRemove> } }
Con esta sintaxis no pueden definirse varios eventos en una misma línea como ocurría con la básica. Su significado es el siguiente: cuando se asocie un método con += al
evento se ejecutará el <códigoAdd>, y cuando se le quite alguno con –= se ejecutará el
<códigoRemove>. Esta sintaxis es similar a la de los bloques set/get de las propiedades pero con una importante diferencia: aunque pueden permutarse las secciones add y
remove, es obligatorio incluir siempre a ambas.
La sintaxis básica es en realidad una forma abreviada de usar la avanzada. Así, la definición public event D Prueba(int valor); la interpretaría el compilador como:
private D prueba public event D Prueba {
[MethodImpl(MethodImlOptions.Synchronized)] add
{
prueba = (D) Delegate.Combine(prueba, value); }
[MethodImpl(MethodImlOptions.Synchronized)] remove
{
prueba = (D) Delegate.Remove(prueba, value); }
}
Es decir, el compilador definirá un campo delegado privado y códigos para add y remove que hagan que el uso de += y -= sobre el evento tenga el efecto que normalmente
tendrían si se aplicasen directamente sobre el campo privado. Como se ve, dentro de estos métodos se puede usar value para hacer referencia al operando derecho de los
operadores += y -=. El atributo System.Runtime.InteropServices.MethodImpl que precede
a los bloques add y remove sólo se incluye para asegurar que un cambio de hilo no pueda interrumpir la ejecución de sus códigos asociados.
Las restricciones de uso de eventos desde códigos externos al tipo donde se han definido se deben a que en realidad éstos no son objetos delegados sino que el objeto delegado es el campo privado que internamente define el compilador. El compilador
traduce toda llamada al evento en una llamada al campo delegado. Como este es privado, por eso sólo pueda accederse a él desde código de su propio tipo de dato.
En realidad, el compilador internamente traduce las secciones add y remove de la definición de un evento en métodos de la forma:
void add_<nombreEvento>(<tipoDelegado> value) void remove_<nombreEvento>(<tipoDelegado> value)
Toda aplicación de += y -= a un evento no es convertida en una llamada al campo privado sino en una llamada al método add/remove apropiado, como se puede observar
analizando el MSIL de cualquier fuente donde se usen += y -= sobre eventos. Además,
como estos métodos devuelven void ése será el tipo del valor devuelto al aplicar += ó -=
(y no el objeto asignado), lo que evitará que código externo al tipo donde se haya definido el evento pueda acceder directamente al campo delegado privado.
Si en vez de la sintaxis básica usamos la completa no se definirá automáticamente un campo delegado por cada evento que se defina, por lo que tampoco será posible hacer referencia al mismo desde código ubicado en la misma clase donde se ha definido. Sin embargo ello permite que el programador pueda determinar, a través de secciones add y
remove, cómo se almacenarán los métodos. Por ejemplo, para ahorrar memoria se puede
optar por usar un diccionario donde almacenar los métodos asociados a varios eventos de un mismo objeto en lugar de usar un objeto delegado por cada uno.
Dado que las secciones add y remove se traducen como métodos, los eventos también
podrán participar en el mecanismo de herencia y redefiniciones típico de los métodos. Es decir, en <modificadores> aparte de modificadores de acceso y el modificador static, también se podrán incluir los modificadores relativos a herencia. En este sentido hay que precisar algo: un evento definido como abstract ha de definirse siempre con la
TEMA 13: Estructuras
Concepto de estructura
Una estructura es un tipo especial de clase pensada para representar objetos ligeros. Es decir, que ocupen poca memoria y deban ser manipulados con velocidad, como objetos que representen puntos, fechas, etc. Ejemplos de estructuras incluidas en la BCL son la mayoría de los tipos básicos (excepto string y object), y de hecho las estructuras junto con la redefinición de operadores son la forma ideal de definir nuevos tipos básicos a los que se apliquen las misma optimizaciones que a los predefinidos.