Documentos de Académico
Documentos de Profesional
Documentos de Cultura
fundamentos
dnm.incio.fundamentos
Delegados y eventos
Primera parte: ¿En quién delegas tú?
En este número vamos a tratar de los delegados, y también de los eventos (aunque de
estos últimos nos encargaremos con más detalle en el próximo artículo), ya que en
.NET están estrechamente relacionados;tanto es así que no podemos definir un even-
to sin la intervención de un delegado.
<< Delegados y eventos en Visual Basic y C# En este caso, primero se define un delegado y a
continuación hay que definir el evento que debe ser
Seguramente los programadores de Visual Basic del tipo de ese delegado. Complicado, ¿verdad? Ahora
estarán pensando que lo dicho en la entradilla no es mismo no entraremos en muchos detalles sobre esto,
totalmente cierto. Bueno, posiblemente no, porque antes veamos cómo se lanzaría ese evento tanto des-
si están leyendo dotNetManía sabrán lo que se escon- de Visual Basic como desde C#:
de tras los eventos. Pero no está de más aclararlo para
' En Visual Basic:
que no queden dudas. RaiseEvent Click(sender, e)
Lo cierto es que Visual Basic es un lenguaje muy
protector, y nos “libera” de ciertas tareas para facili- // En C#:
tarnos el trabajo real, de forma que nos podamos con- if( Click != null )
{
centrar en lo que de verdad importa y olvidarnos un Click(sender, e);
poco de ciertos “asuntillos” que en parte solo nos }
hacen teclear más.
Aclaremos un poco todo esto que acabo de comen-
tar. En Visual Basic, para definir un evento solo hay que Indudablemente en Visual Basic sigue siendo mucho
escribir la instrucción Event seguida de la definición que más fácil, más simple, menos complicado, no tenemos
daremos al método que recibirá los eventos. Por ejem- que comprobar nada... Y es cierto, a eso es a lo que me
plo, si nuestra clase es del tipo Button, podemos definir refería con lo de que Visual Basic es muy “protector” y
el evento Click de la siguiente forma: nos libera de ciertos detalles que en realidad no nece-
sitamos saber, o al menos, no es obligatorio que sepa-
Public Event Click( ByVal sender As Object,_
ByVal e As EventArgs )
mos. Por otra parte a los programadores de C#, segu-
ramente por aquello de que les gusta “escribir más”
código, pues... ¡que escriban más! Aunque, como vere-
Guillermo “Guille” Som Como podemos comprobar, esa es la definición mos en este artículo, eso ya está cambiando, y ahora
es Microsoft MVP de Visual Basic
desde 1997. Es redactor de
que usaremos en cualquier formulario que quiera podrán hacer también ciertas cosas sin necesidad de
dotNetManía, miembro de Ineta interceptar la pulsación en un botón. Simple, ¿ver- escribir tanto, ya que el propio compilador de C# se
Speakers Bureau Latin America, dad? Veamos ahora cómo tendría que definir ese mis- encargará de algunos aspectos, digamos, de trasfondo.
mentor de Solid Quality Learning mo evento un programador de C#: Pero al final, tanto C# como Visual Basic deben seguir
Iberoamérica y autor del libro
Manual Imprescindible de
las reglas de .NET, y aunque nosotros como usuarios
public delegate void ClickEventHandler(
Visual Basic .NET. object sender, EventArgs e);
no tengamos que preocuparnos, los compiladores sí que
http://www.elguille.info public event ClickEventHandler Click; lo harán.
<< dnm.inicio.fundamentos
Independientemente de las bromas, esto saben mucho los que han desarro- arbitrario, es decir, que no nos “cole-
tenemos que ser conscientes (sobre todo llado con C, incluso los que desarrollan mos” donde no debemos, ya que, como
los programadores de Visual Basic) de con C#. Aunque en todas las puertas fal- ya he dicho, a .NET no le gustan las
que algunas veces el que nos “mimen” sas de .NET siempre hay alguien que sorpresas, por eso impone reglas que
tanto no es bueno, ya que nos acostum- “está por allí” y revisa que en realidad debemos cumplir; si las cumplimos,
bran mal, y cuando creemos que todo no hagamos demasiadas trastadas. Esto pasamos, si no las cumplimos, no nos
va a ser sencillo, llega la versión 2005 y en otros lenguajes no es así, y por error deja seguir.
nos dicen que si queremos usar la nue- o porque así lo hayamos previsto, pode- Y esto es así por todo lo comentado
va instrucción Custom Event debemos mos crear grandes problemas, si no, ¿por anteriormente, ya que el CLR quiere
saber manejar los delegados, además de qué aparecen los fallos de protección seguridad y la única forma de tenerla es
que también debemos saber en qué general? (las típicas pantallas azules de creando normas de conducta y de utili-
medida están relacionados con los even- las versiones anteriores de Windows, zación, en este caso, de la memoria o del
tos. Esto a los programadores de C# no que ahora simplemente están remoza- acceso a esas partes de la memoria en la
les pillaría tan desprevenidos. Así, si aho- das y han cambiado de look por un cua- que están las definiciones de los méto-
ra les dicen que pueden crear métodos dro de diálogo más “mono”). dos o funciones.
anónimos, y que esos métodos anóni-
mos los podrán crear donde se pueda
usar un delegado, o que ya no es nece-
sario usar un constructor para crear un
tipo delegado o que por medio de la
Un delegado permite acceder a una función de
covarianza o la contravarianza podrán
usar de forma más óptima los delega-
forma casi anónima,ya que simplemente tiene la
dos, simplemente estarán preparados y
sabrán soportar el cambio... dirección de memoria de dicha función
Pero como siempre hay gente nue-
va, (tanto en C# como en Visual Basic),
no está de más que algunos puntos estén
totalmente claros, así que eso es lo que Este tipo de problema se debe a un Por tanto, si queremos acceder a un
vamos a intentar en este primer artícu- acceso indebido a la memoria, normal- método, tenemos que hacerlo por medio
lo dedicado a los delegados y a los even- mente causado por un acceso a una posi- de un puntero controlado, y la forma de
tos. Y en los que seguirán, terminare- ción de memoria que no estaba dentro del controlar ese acceso es definiendo un
mos por aclarar casi cualquier duda que rango que teníamos permitido. .NET es prototipo en el que indiquemos de qué
posiblemente se nos pueda presentar a más estricto y menos permisivo para estas tipo es ese método, si recibe paráme-
la hora de trabajar con los eventos y con cuestiones, por tanto, si queremos estar tros, y de hacerlo cuántos y de qué tipo
los delegados. bajo el abrigo de la seguridad de .NET son. Una vez que tenemos definidos
debemos seguir sus normas. todos estos requerimientos, es cuando
¿Qué son y para qué sirven los Como sabemos, .NET define una le decimos al runtime de .NET que nos
serie de tipos de datos, los cuales pode- permita acceder a esa función. De esta
delegados? mos usar indistintamente desde un len- forma, podrá controlar que estamos
Como sabemos, .NET Framework guaje u otro, ya que independientemen- accediendo al sitio correcto.
se caracteriza por ser un entorno de te del nombre que cada compilador le Esa definición del prototipo de fun-
código administrado (managed code) o lo dé, en realidad estamos trabajando con ción (o método) al que queremos acce-
que es lo mismo, a .NET no le gustan los tipos definidos en la librería de cla- der lo hacemos por medio de un delega-
las sorpresas. Si una función tiene que ses, o mejor dicho, en el sistema de tipos do. Podemos pensar que un delegado es
devolver un valor de tipo String, debe comunes (CTS). Y los delegados no son en cierto modo similar a las interfaces (ver
devolver un valor de tipo String; si un una excepción. dotNetManía nº 16), que como sabemos
método debe recibir dos parámetros de Pero... ¿qué es un delegado? Un definen un contrato que debemos respe-
tipo Object, eso es lo que recibirá. Y delegado es una referencia a una fun- tar. Sabiendo esto, si queremos acceder a
todo esto los lenguajes adscritos a .NET ción, lo que también se conoce como un una función que devuelve una cadena y
deben respetarlo, ya que de no ser así, puntero a una función, es decir, un dele- que recibe un parámetro de tipo Cliente,
.NET no permitirá la ejecución del gado permite acceder a una función de debemos definir un delegado con esas
código. Y esto es aplicable a todo lo que forma casi anónima, ya que simplemen- características, y cuando posteriormente
.NET controla, es decir, a todo lo que te tiene la dirección de memoria de queramos acceder a ese método, en lugar
está bajo su influencia. Lo que no quie- dicha función. Y sabiendo la dirección de hacerlo directamente o por medio de
<<dotNetManía
re decir que no podamos hacer cosas que de memoria, podemos acceder a ella. un puntero directo, usaremos un objeto
.NET no permita; pero si lo hacemos, Pero en .NET esto debe estar contro- del tipo definido por el delegado. Lo que
debemos hacerlo por la puerta falsa. De lado, de forma que ese acceso no sea nos lleva a una segunda definición de lo
43
<< dnm.inicio.fundamentos
Los delegados definen la “firma” que los métodos public static void usarMiFuncionDelegado(
MiFuncionDelegado mfd)
{
a los que queremos acceder deben tener }
string s = mfd( new Cliente() );
44
<< dnm.inicio.fundamentos
NOTA
En Visual Basic,la forma de acceder a una función siempre es por medio ldftn instance string Cliente::MiFuncion(class Cliente)
de la instrucción AddressOf y la podemos usar también de las dos newobj instance void MiFuncionDelegado::.ctor(object,native int)
formas que acabamos de ver, es decir por medio de un objeto del tipo stloc.0
ldloc.0
del delegado o de forma directa, en cuyo caso, (al igual que ocurre con
newobj instance void Cliente::.ctor()
C# 2.0), será el propio compilador el que determinará si esa función callvirt instance string MiFuncionDelegado::Invoke(class Cliente)
cumple o no los requisitos del parámetro que espera el método: stloc.1
ret
usarMiFuncionDelegado(AddressOf c.MiFuncion)
son válidas para ese lenguaje, aunque } que sería más fácil mostrar directamen-
(como es costumbre en esta sección), el Fuente 3. Las dos formas equivalentes de te el saludo, en lugar de dar tantas vuel-
código mostrado será prácticamente en asignar el puntero a una función. tas, pero lo importante es ver cómo se
45
<< dnm.inicio.fundamentos
pueden usar los métodos anónimos, los cuales tienen Sobre la covarianza, la documentación nos dice
mayor utilidad con los eventos o cuando queramos que: Cuando un método delegado tiene un tipo de valor
simplemente definir la función “apuntada” de forma devuelto que es más derivado que la firma de delegado, se
directa, ya que como hemos visto anteriormente, esa denomina covariante. En realidad, al leerlo sabiendo
variable que apunta al método la podemos pasar como qué es lo que significa no era tan rebuscada la defini-
argumento a otro método. ción. Pero para dejarlo en un lenguaje más llano, dire-
En cualquier caso, lo que no nos estará permitido mos que esto significa que podemos usar delegados
hacer es modificar la firma del delegado; por tanto, si se que devuelvan un tipo y el receptor de ese valor pue-
nos ocurre la brillante idea de añadir un nuevo paráme- de ser cualquier clase de ese mismo tipo o de cualquier
tro a la función, tal como vemos en el siguiente código otra clase derivada.
fuente, el compilador nos avisará (entre otras cosas), que Por ejemplo, en el siguiente código, tenemos la
MiFuncionDelegado no tiene dos argumentos. definición de una clase Persona y un delegado que
devuelve un valor de ese tipo:
mfd = delegate(Cliente c, string saludo){
return saludo + “ “ + c.Nombre; public delegate Persona PersonaCallback();
};
public class Persona
{
Es importante saber que aunque estemos decla- // Omitidas las definiciones
// de las propiedades Nombre y Apellidos
rando un “método” anónimo, en realidad no es un }
método, al menos en el sentido de los ámbitos o
cobertura de las variables, ya que desde ese méto-
do anónimo podemos acceder a cualquier variable Y definimos una clase derivada, a la que llamare-
que hayamos declarado anteriormente, y la varia- mos Colega, en la que definimos un método estático
ble definida en el parámetro también tendrá el mis- que devuelve un objeto de ese mismo tipo:
mo ámbito que el método o propiedad que contie-
public class Colega : Persona
ne esa definición anónima. Debemos pensar en que {
el cuerpo del método anónimo en realidad es como public static Colega NuevoColega()
cualquier otro bloque de código que podamos {
incluir dentro de un par de llaves. return new Colega();
}
En el código del fuente 6 podemos ver ese con- // Omitida la definición de la propiedad Correo
flicto entre la variable definida directamente y la defi-
}
nida como parámetro del método anónimo.
Podemos usar esa función como el método al que
apuntará el delegado PersonaCallback, que como hemos
Cliente c = new Cliente(); visto, devuelve un objeto del tipo Persona:
usarMiFuncionDelegado(c.MiFuncion);
PersonaCallback nColega;
MiFuncionDelegado mfd; nColega = Colega.NuevoColega;
mfd = delegate(Cliente c) {return “Hola “ + c.Nombre;};
string s = mfd(new Cliente(“Manolo”)); Colega unColega = (Colega)nColega();
Console.WriteLine(s); // Omitidas las asignaciones a las propiedades
tema, al menos si lo iba a tratar. Y con esto pasa como tiene uno o más parámetros de tipos que derivan de los tipos
con casi todo, hasta que no lo tienes entre las manos, de los parámetros de método, ese método se denomina con-
no sabes el tacto que tiene. travariante. Lo que viene a significar que los tipos de
46
<< dnm.inicio.fundamentos
datos que podemos usar como parámetros al llamar a pero siempre y cuando el método anónimo tenga la
un delegado pueden ser del mismo tipo que está defi- firma correcta, es decir, el parámetro en la definición
nido en el delegado (así era hasta la versión 2.0), o de del método anónimo debe ser del mismo tipo que el
cualquier tipo derivado. Si el compilador ve una rela- indicado en la definición del delegado, pero a la hora
ción de herencia entre el tipo usado y el definido en de usarlo podemos indicar cualquier objeto de un tipo
el delegado, lo permitirá. Cliente o derivado:
Siguiendo con el ejemplo del delegado que recibe
MiFuncionDelegado mfd3;
un parámetro de tipo Cliente, (ver el fuente 1), vamos a mfd3 = delegate(Cliente co2) {return “A sus pies “ +
rediseñar la clase y el método MiFuncion para que devuel- co2.Nombre;};
va un saludo al nombre indicado en la propiedad Nombre Console.WriteLine(mfd3(co));
de la clase. A continuación creamos la clase ClienteOro
que se deriva de Cliente. Tanto en una como en otra cla- Pero el uso más práctico de esta característica será
se hemos definido un constructor que recibe como pará- (como casi todo lo relacionado con los delegados),
metro el nombre a usar. En el fuente 7 vemos esas dos cuando lo apliquemos a los eventos.
definiciones de estas clases. Cuando utilizamos los métodos de eventos de los
controles de Windows Forms, el segundo parámetro
suele (o debería) ser una clase derivada de EventArgs,
public class Cliente
{
pero dependiendo del evento, ese parámetro será del
// Omitida la definición de la propiedad Nombre tipo adecuado para el evento en cuestión. Por ejem-
public Cliente() { } plo, el evento KeyPress recibe un parámetro del tipo
public Cliente(string nombre) KeyPressEventArgs, pero en C# podemos definir el
{ this.Nombre = nombre; }
//
método que intercepta ese evento de cualquiera de
public virtual string MiFuncion(Cliente c) { estas dos formas:
return “Que tal “ + c.Nombre;
} private void txtNombre_KeyPress( object sender,
} KeyPressEventArgs e)
{ ... }
class ClienteOro : Cliente
{ private void txtNombre_KeyPress( object sender,
public ClienteOro() { } EventArgs e)
public ClienteOro(string nombre) { ... }
{ this.Nombre = nombre; }
}
La segunda forma, a pesar de ser menos espe-
Fuente 7. Definición simplificada de las clases cífica, seguramente la usaremos en casos muy con-
Cliente y ClienteOro.
cretos y siempre que necesitemos esa “generalidad”,
pero no adelantemos acontecimientos, ya que cuan-
NOTA do tratemos el tema de los eventos veremos algu-
El hecho de definir un constructor con parámetro en las cla- nas aplicaciones prácticas de esta posibilidad que
ses del fuente 7 es para facilitar el uso de las mismas, de for- tiene C#, ya que en Visual Basic siempre tendre-
ma que en una sola instrucción podamos asignar el valor de mos que definir los parámetros de los eventos del
la propiedad Nombre, de esa forma nuestro código de ejem- tipo exacto.
plo podrá mostrar algo. Pero la razón de que lo hayamos teni-
do que hacer en las dos clases es porque los constructores
no se heredan; por tanto, si queremos esa funcionalidad en Conclusiones
las dos clases, debemos definirlos en ambas.
Aún no hemos terminado con algunas de las
cosas interesantes o importantes de los delegados,
Ahora podemos usar cualquier función que reci- pero será en el próximo artículo donde veremos
ba un delegado que apunte a MiFuncion y el compila- otra característica interesante de los delegados: la
dor (realmente el runtime) usará la clase que corres- multidifusión. Esa forma de usar los delegados la
ponda. comprenderemos mejor cuando sepamos más sobre
la estrecha relación de estas clases especiales con
ClienteOro co = new ClienteOro(“Paco”);
MiFuncionDelegado mfd2;
los eventos.
mfd2 = co.MiFuncion; Como es costumbre en esta sección, en el sitio
string sco = mfd2(co); Web de dotNetManía está disponible el código de
Console.WriteLine(sco); ejemplo para poder bajarlo, aunque en el caso de Visual
<<dotNetManía
47