You are on page 1of 126

l ll l

l ll l
` `` `
` `` `
T TT T
T TT T
U UU U
U UU U
l ll l
l ll l
T TT T
T TT T
l ll l
l ll l
\ \\ \
\ \\ \
l ll l
l ll l


# ## #
# ## #

Introduccin a la Plataforma .NL1
Ian Marteens
2008
Copyright 2004-200S, Ian Marteens
iii
NDCE DE MATERA8

PROLOGO VII
OUROBUROS VII
ESTADO ACTUAL DEL LIBRO VII
CONVENIOS VIII
I. COMMON LANGUAGE RUNTIME 1
QU ES LA PLATAFORMA .NET? 1
POR QU .NET Y NO JAVA? 2
COMMON LANGUAGE RUNTIME 3
ENSAMBLADOS Y MDULOS 4
CLASES Y ESPACIOS DE NOMBRES 5
PROCESOS Y DOMINIOS DE APLICACIN 7
EL SISTEMA DE TIPOS COMUNES 8
TIPOS BSICOS 8
VECTORES 10
II. PROGRAMACION ORIENTADA A OBJETOS 12
ABSTRACCIN ORIENTADA A OBJETOS 12
ENCAPSULAMIENTO: DATOS 13
UNIDADES, PAQUETES Y OTROS TRIUNFOS PARCIALES 14
LA MULTIPLICACIN DE LOS PANES Y LOS PECES 15
ENCAPSULAMIENTO: COMPORTAMIENTO 15
UN PEQUEO CAMBIO EN LA SINTAXIS 17
HERENCIA 19
POLIMORFISMO 20
LA REGLA DE ASIGNACIN POLIMRFICA 22
MTODOS VIRTUALES 22
LA TABLA DE MTODOS VIRTUALES 22
LOS LMITES DE LA TECNOLOGA 22
LA SINGULARIDAD 23
III. CLASES Y OBJETOS 25
IDENTIDAD 25
QU HAY DENTRO DE UNA CLASE 25
VISIBILIDAD 27
CONSTRUCTORES 28
INICIALIZACIN DE CAMPOS 29
EL CONSTRUCTOR ESTTICO 31
IV. TIPOS DE INTERFAZ 34
HERENCIA E INTERFACES 34
IMPLEMENTACIN DE TIPOS DE INTERFAZ 35
INTERFACES Y POLIMORFISMO 37
MTODOS DE EXTENSIN 38
LA CARA OSCURA DE LOS MTODOS DE EXTENSIN 39
V. ESTRUCTURAS 43
GUERRA AL DESPILFARRO DE MEMORIA 43
CREACIN EFICIENTE, DESTRUCCIN INDOLORA 43
Intuitive C
#

Copyright 2004-200S, Ian Marteens
i
iv
LIMITACIONES: CONSTRUCTORES 46
LIMITACIONES: HERENCIA Y VISIBILIDAD 47
MTODOS VIRTUALES Y ESTRUCTURAS 48
INTERFACES Y ESTRUCTURAS 49
EL SAGRADO MISTERIO DEL BOXING 50
VI. TIPOS DELEGADOS 53
DELEGADOS 53
CADENAS DE DELEGADOS 56
LLAMADAS ASNCRONAS 57
VII. COMPONENTES 60
COMPONENTES VERSUS CLASES 60
PROPIEDADES 61
INDEXADORES 62
PROPIEDADES CON IMPLEMENTACIN AUTOMTICA 64
EVENTOS 65
CLASES PARCIALES 66
MTODOS PARCIALES 69
VIII. ADMINISTRACION DE MEMORIA 71
RECOGIENDO LA BASURA 71
FINALIZACIN Y DESTRUCCIN DETERMINISTA 72
PRECAUCIONES EN UN MUNDO FELIZ 74
IX. GENERICIDAD 76
TIPOS GENRICOS 76
CLIENTES Y PROVEEDORES DE TIPOS GENRICOS 77
GENERICIDAD Y RESTRICCIONES 78
MTODOS GENRICOS 80
CMO FUNCIONA LA INFERENCIA DE TIPOS 80
NUDISMO Y COVARIANZA 81
GENERICIDAD Y TIPOS ANIDADOS 83
TIPOS ANULABLES 85
OPERACIONES CON TIPOS ANULABLES 87
PROMOCIN DE OPERADORES 89
X. ITERACION 91
EL PATRN DE ENUMERACIN 91
ITERADORES 93
COMPOSICIN DE ITERADORES 95
MTODOS ANNIMOS 96
EXPRESIONES LAMBDA 98
EQUIVALENCIA ESTRUCTURAL? 100
RBOLES DE EXPRESIONES 100
XI. REFLEXION 102
SISTEMAS HIPOCONDRACOS 102
INFORMACIN SOBRE TIPOS 103
REFLEXIN Y ENSAMBLADOS 104
MANEJO DINMICO DE INSTANCIAS 106
ATRIBUTOS 108
DISEO DE ATRIBUTOS 110
REFLEXIN SOBRE ATRIBUTOS 112
Indice de Materias
Copyright 2004-200S, Ian Marteens

v
INDICE ALFABETICO 115

Copyright 2004-200S, Ian Marteens
vii
PROLOGO

A dream is an answer to a question
We haven't learned how to ask.
Dana Scully
CUN1AS CONS1LLACIONLS PULDL usted identiicar en el cielo nocturno Si intenta aprender-
las una a una, le costara mucho. Lo mejor es hilar una historia, e ir saltando de constelacin en
constelacin. Por ejemplo, puede empezar por Orin, que es la mas acil de todas. A partir de
Orin, localice la estrella Sirio, con lo que habra dado con el Can Mayor, en la direccin opuesta
estara esperandole el 1oro, con las Plyades en su rente.
0uroburos
Aprender un lenguaje de programacin no es tan dierente, pues la
diicultad es similar: los temas estan inculados por multitud de
enlaces circulares. Lsto no es muy comn. Resulta acil, por
ejemplo, escribir un libro o un curso sobre programacin
para bases de datos con ADO.NL1. Ls acil localizar la
hebra y tirar de ella, organizando la materia en una lnea
recta. Pero tratandose de aprender el propio lenguaje, te
encuentras con que no puedes ensenar qu es un mtodo
irtual antes de mostrar qu es la herencia. y iceersa.
Creo que la solucin consiste en renunciar a ensenarlo todo
de golpe, y en hilar historias` que recorran ranjas del material
como si se tratase de la ranja del zodiaco, la lnea ecuatorial o la
mismsima Va Lactea. Lso, al menos, es lo que he intentado hacer en este olleto.
Lstas notas se han desprendido de una introduccin a la plataorma .NL1 que inicialmente or-
maba parte de un curso sobre ADO.NL1. 1ratandose de un trabajo en marcha, que tendr que
ampliar y corregir unas cuantas eces en el uturo cercano, no he intentado disimular el origen de la
mayora de las partes.
Estado actua| de| ||bro
Lste no es un libro terminado y reisado segn dictan las normas. Ls un experimento, un libro cuya
escritura tiene lugar a la ista de todos. Ls ineitable que, mientras no termine su redaccin, aparez-
can saltos, inconsistencias e incluso algn que otro error en el contenido.
La presente ersin ,14,dic,2006, diide su contenido en captulos por primera ez. Antes, el libro
estaba organizado como un captulo gigantesco. Lsta es una lista de problemas y expectatias:
1 labra un captulo entre el primero y el segundo, titulado Programacin orientada a objetos.
2 Ll segundo captulo de ahora, titulado Clases y objetos, cedera la mayor parte de su contenido a
un nueo captulo titulado Tipos de interfaz.
3 1engo dudas sobre mantener captulos separados para Programacin orientada a objetos, que
contendra una explicacin de las tcnicas relacionadas con el clasico tro encapsula-
miento,herencia,polimorismo, y otro captulo, Clases y objetos, para detalles concretos
relacionados con C
4
. \a eremos.
4 Ll material del captulo Estructuras es nueo casi por completo. 1engo que decidir dnde debe
ir la seccin sobre clases parciales ,probablemente, mucho antes,. Donde ayan a parar las cla-
ses parciales sera donde se mencionen las clases estaticas de C
4
2.0.
5 Ll material del captulo Reflexin es tambin nueo. Le hace alta una buena limpieza.
6 Ll captulo Iteracin es un poco extrano. Me he arriesgado a tratar ah los mtodos annimos, y
a mencionar de paso las expresiones lambda. No es una eleccin arbitraria: el otro sitio donde


Intuitive C
#

Copyright 2004-200S, Ian Marteens
iii
viii
podran acomodarse los mtodos annimos sera junto a los delegados, pero me pareci prema-
tura. 1eniendo en cuenta que los mtodos annimos pueden erse como una extensin de las
estructuras de control del lenguaje, al acercar el cdigo al sitio desde dnde se utiliza, no creo
que sea tan grae mi eleccin. Pero ya eremos.
7 Sobre todo, alta mucho contenido, respecto a mis planes. Le pido un poco de paciencia.
Creo que no necesito decir lo siguiente: por aor, cualquier error que detecte, dgamelo! Ln esta
etapa no es tan importante localizar las altas de ortograa y redaccin, pero aseme sobre las que
encuentre. Si no, se corre el riesgo de que queden ocultas en cuanto crezca el libro.
6onven|os
A pesar de que cada ano nuestros ordenadores son mas rapidos y baratos, la impresin de libros
sigue siendo cara: es raro er un libro tcnico impreso en colores. Nos hemos acostumbrado a leer
en blanco, negro y gris. y puede que ahora le extrane el que haya usado colores sin complejos a lo
ancho y largo de este manual. Me siento justiicado: no lo he hecho por el dudoso eecto esttico,
sino para mejorar la legibilidad dentro de lo posible.
Lste pequeno ragmento de cdigo muestra algunas de las conenciones que he utilizado:
public class Triangular : Chip, IOscilador, IConfigurable
{
public double Voltaje(double tiempo)
{
// Implementacin de la onda triangular
}
}
le resaltado en cian los identiicadores que corresponden a nombres de tipos, sin importar si se
trata de una clase, una estructura, una interaz, un tipo enumeratio o delegado. Ls un indicio til
para lector porque no puede deducirse siempre de la sintaxis. Por el contrario, he usado negritas
para las palabras reseradas, en ez de usar texto de color azul como en Visual Studio. Ln mi opi-
nin, al resaltar de esta manera las palabras claes se acilita la identiicacin de la estructura sintac-
tica del texto. Por ltimo, he usado italicas en color erde para los comentarios.
Quiero agradecer, sobre todo, la e y la paciencia de todas las personas que han adquirido las distin-
tas series del curso de ADO.NL1 por adelantado. Slo espero no deraudarlas, y que estas notas les
sean de algn proecho.
Ian Marteens
www.marteens.com
www.commanet.net
Madrid, diciembre de 2006


Copyright 2004-200S, Ian Marteens
1
J JJ J
COMMON LANGUAGE RUNTME

NA PARBOLA BUDIS1A CULN1A la historia de tres ciegos que queran saber qu era un ele-
ante. Acudieron al palacio real y suplicaron al monarca que les permitiese palpar su eleante
blanco. Ll rey, budista deoto y compasio, accedi, y un criado lle a los ciegos al estanque donde
se banaba el paquidermo. A una oz del siriente, los ciegos se abalanzaron sobre el animal,
agarrandolo por distintos puntos de su inmensa anatoma:
- Por la gloria de mi madre! - exclam el primero - Un eleante es como una columna, pequena
pero muy ancha! - dijo, pues se haba aerrado a una de las patas.
- Mientes, bellaco! - se enad el segundo - Ls una serpiente peluda y se retuerce como un
demonio! - explic mientras intentaba que el eleante no se lo sacudiese de la trompa.
- lm! - se oy la oz apagada del tercero - que alguien me saque de esta caerna malo-
liente.!
0u es |a p|ataforma .NET?
Lstoy conencido de haber contado esta historia en algn otro sitio, aunque creo que el inal era
dierente. De todos modos, es una historia apropiada, porque la plataorma .NL1 puede presentar
tantas apariencias como el eleante del cuento, incluyendo la caerna maloliente.
Por ejemplo, para un oroo de la programacin de sistemas, .NL1 es la ersin 3.0 de COM. \ no
le alta razn, al menos desde el punto de ista histrico. Al parecer, las primeras ideas sobre .NL1
surgieron como parte del diseno de un sucesor para COM-. ,Qu es COM- Una serie de
protocolos que permiten la colaboracin entre objetos desarrollados con dierentes lenguajes y
herramientas, y que pueden residir en dierentes procesos o incluso maquinas. ,Qu es .NL1, si lo
obseramos a tras de esta estrecha rendija la adiinado: es lo mismo, pero mejor. Ls por eso
una irona que incluso la ersin 2.0 de la plataorma no haya logrado sustituir completamente la
uncionalidad de COM-, y que deba colaborar con COM- para poder aproechar los llamados
servicios corporativos, que estudiaremos en la ltima serie de este curso.
Pero yo no soy oroo de la programacin de sistemas, al menos, no de la tribu de esta especie que
pretende pasar a la ama copiando el uncionamiento de subsistemas del iejo UNIX. Me interesa
.NL1 mas por su aceta de herramienta de desarrollo que por cualquier otra cosa.
1 lay toda una amilia de lenguajes que pueden utilizarse para desarrollar aplicaciones o compo-
nentes. Ln realidad, tras esa amilia concreta de lenguajes, hay un conjunto muy bien deinido
de reglas del juego que deben ser satisechas por cualquier aspirante a ingresar en tal selecto
club.
2 lay un sistema de soporte para la ejecucin suicientemente complejo para que lo diidamos
en subsistemas. 1enemos, para empezar, un sistema que carga las aplicaciones y las prepara para
su ejecucin. 1radicionalmente, esta ha sido una responsabilidad del sistema operatio y,
eectiamente, \indows ha necesitado algunos retoques para satisacer las exigencias de carga
de las aplicaciones .NL1.
3 Las aplicaciones .NL1 no slo necesitan una tutela especial durante la carga, sino incluso du-
rante la ejecucin. Lstas aplicaciones utilizan, por ejemplo, un recolector de basura para la ges-
tin de memoria, y en el caso tpico, es necesario traducir el cdigo intermedio que generan los
O
Intuitive C
#

Copyright 2004-200S, Ian Marteens
2
2
compiladores a cdigo natio ejecutable, a medida que la ejecucin aya actiando una u otra
ruta dentro de la aplicacin. lay tipos de datos cuya implementacin se sintetiza en tiempo de
ejecucin: los tipos delegados, que se utilizan en la implementacin de eentos y de unciones
de respuesta, y los tipos genricos en la ersin 2.0 de la plataorma.
4 Por ltimo, estan las numerosas bibliotecas de clases necesarias para ejecutar hasta la mas sim-
ple tarea.
Ahora busque un libro sobre sistemas y ea cmo el autor deine qu es un sistema operatio. Se
parece mucho a la lista anterior, ,erdad Lsto ha lleado a que algunos sostengan lo siguiente:
.NET es el nuevo sistema operativo de Microsoft
Se trata de una exageracin, por descontado. Por mencionar slo un argumento: hay muchas areas
de las ersiones actuales de \indows que no se adecuan naturalmente al estado actual de la plata-
orma. Muchos pensaran inmediatamente en los controladores de dispositios, pero puede agregar
el subsistema COM- a esta lista. No obstante, esta idea de .NL1 como sistema operatio irtual no
es desatinada, y Microsot ya ha dado pasos importantes hacia esta meta, de momento lejana.
Por qu .NET y no Java?
la llegado el momento de romper corazones y herir almas sensibles. Si conoce algo sobre Jaa,
aunque sea de odas, habra notado las similitudes entre Jaa y .NL1. lay un lenguaje intermedio,
hay un traductor a cdigo natio, hay un intento de portabilidad. ,Por qu un indiiduo como yo,
que reniega de Jaa como aicin personal, se muestra tan entusiasmado con algo que parece una
imitacin Puedo resumir mis razones:
.NET es Java, pero bien diseado y mejor implementado
.NL1 ha tenido la posibilidad de aprender de los errores en el diseno de Jaa, y doy e de que se ha
aproechado esta oportunidad. Para demostrarlo, habra que eectuar una comparacin area por
area y muy bien detallada, pero puedo adelantarle algunos puntos:
Lntorno de ejecucin: el Common Language Runtime de .NL1 ha sido, desde sus primeras
ersiones, mas eiciente y uncionalmente completo que la Java Virtual Machine. Lsta resca an
la enconada discusin sobre las bondades de anadir un mecanismo de tipos delegados a este
tipo de maquinas irtuales. Ln la JVM no hay soporte para tcnicas tan tiles como los tipos
enumerados o el traspaso de parametros por reerencia. Lsto se paga en elocidad de ejecucin
y en menor productiidad, al tener el programador que tratar con un lenguaje menos expresio.
Otra maniestacin de lo mismo: para no desirtuar la uniersalidad de los metadatos en
tiempo de ejecucin, el equipo de Microsot que introdujo los tipos genricos, liderado por
Don Syme y Andrew Kennedy, ide una implementacin que respeta toda la inormacin sobre
tipos genricos, parametros e instancias en tiempo de ejecucin. La alternatia propuesta para
Jaa esta basada en trucos de compilacin. ,Resultado 1anto la JVM de Jaa como el CLR de
.NL1 deben orecer aplicaciones eriicables. Los tipos genricos de .NL1 son eriicables y
eicientes. Para que las instancias de tipos genricos en Jaa sigan siendo eriicables, el
compilador debe anadir eriicaciones de tipo en tiempo de ejecucin que tienen un alto coste
asociado.
Ll propio concepto de cdigo intermedio es muy dierente. Ll bytecode de Jaa muchas eces
termina por ser interpretado. Ll cdigo IL de .NL1 nunca se interpreta, sino que siempre se
transorma en eiciente cdigo natio. Jaa presenta una paradoja: su popularidad inicial se de-
bi precisamente a la presunta portabilidad de su bytecode y las posibilidades que se abran res-
pecto a la creacin de applets para Internet. Sin embargo, los problemas con las interaces grai-
cas terminaron por arrinconar el uso uniersal de applets, y Jaa ha encontrado su nicho ecol-
gico en la programacin para seridores. justo donde es mas crtica la elocidad de ejecucin
y respuesta.
Common Language Runtime
Copyright 2004-200S, Ian Marteens
3
3
Interaces graicas: ,quin no se ha isto inolucrado alguna ez en las tragedias jaanesas
relacionadas con A\1, Swing y el resto de los engendros isuales Ls cierto que \indows
lorms en .NL1 1.1 no es gran cosa, pero ha bastado una ersin mas para que la mejora sea
mas que notable. Ademas, podamos criticar el primer \indows lorms por su ealdad o por la
alta de alguna uncionalidad. pero jamas por su ineiciencia o por estar repleta de bugs de
todo tipo.
Interaz de acceso a datos: las interaces oiciales de acceso a datos en Jaa son de muy bajo
niel, en comparacin con .NL1. Lntre los programadores que usan Jaa es comn una extrana
adoracin por las implementaciones manuales de sistemas de persistencia, especialmente aque-
llos que se desarrollan en solitario y partiendo siempre de cero absoluto. DataSet se traduce
como abominacin` y la sola idea del data binding prooca espasmos epilpticos incluso a los
jaicos mas curtidos e insensibles.
La lista podra alargarse indeinidamente, pero oy a mencionar slo otro punto de comparacin:
los llamados servicios corporativos. Lstos seran tratados en la serie L de este curso, y siren como
soporte para aplicaciones diididas en capas que tienen que atender un nmero suicientemente
grande de peticiones concurrentes. Cualquier ersin de \indows, a partir de \indows 2000, iene
de serie con soporte integrado para estos sericios, como parte de la uncionalidad de COM-. Ll
equialente en Jaa se conoce como J2LL. y es uno de los motios por los que un seridor Linux
puede terminar costando mas que un seridor \indows, porque las implementaciones de estos
sericios para Jaa suelen pertenecer a grandes empresas de sotware y costar un ojo de la cara.
Por ltimo, es innegable que .NL1 cuenta con una entaja abrumadora sobre Jaa:
En .NET, el sistema operativo y el entorno de ejecucin colaboran estrechamente
,Ls injusto Me parece que no. pero en cualquier caso, la Inormatica es mi proesin, no mi
aicin para el tiempo libre, y mi inters es poder llear a cabo mi trabajo con la mayor calidad en el
menor tiempo posible. Me importa un pimiento quin me suministre esas herramientas. Puedo
equiocarme o puedo acertar, pero si me equioco, las consecuencias las oy a tener que pagar de
mi bolsillo. Por lo tanto, quiero ser yo, y no un maldito comisario poltico de la Unin Luropea o un
burcrata italicio ederal, quien decida qu herramientas estoy obligado a usar. Reconozco que he
escrito este parrao con mucha rabia y mala sangre, pero es que detesto el interencionismo estatal
y por experiencia s que nunca llea a nada bueno. Le pido disculpas por el tono amargo, pero no
he podido eitarlo.
6ommon Language Runt|me
\ ahora, al grano: eamos el mnimo de conceptos necesarios para empezar a trabajar con la plata-
orma. \a sabemos, y si no, se lo cuento ahora, que los compiladores .NL1 traducen el cdigo
uente a un cdigo intermedio llamado. bueno, cdigo intermedio, o IL. No hay una pasada
posterior de enlace, como eremos en la siguiente ersin, sino que el resultado de esta compilacin
ya constituye directamente una aplicacin ejecutable o una biblioteca de clases, o parte de alguna de
estas dos ariantes.
le dicho, sin embargo, que este cdigo intermedio jamas se interpreta. ,Quin es, por consiguiente,
el responsable de la traduccin a cdigo natio, y cuando entra en escena lay dos alternatias:
Compilacin sobre la marcha
,Sobre la marcha Bueno, just in time no signiica eso exactamente, pero me da pereza lean-
tarme e ir a por un diccionario. Cuando usted lea esto habran pasado ya arios meses desde que
lo escrib, pero si obsera bien, todaa sigue diciendo lo mismo, con lo cual puede hacer una
idea de cuanta pereza me da.
Lspero, de todos modos que la idea quede clara: Como parte del proceso de carga de la aplica-
cin traducida a IL, uno de los componentes del sistema de carga realiza esta traduccin sobre
la marcha, segn aya siendo necesario. Inicialmente se traduce un pequeno ragmento, diga-
mos, por concretar, que se traduce el mtodo Main por donde comienza la ejecucin del pro-
Intuitive C
#

Copyright 2004-200S, Ian Marteens
4
4
grama. Si este mtodo contiene una llamada a otro mtodo MeDaLoMismo, la instruccin que
ejecuta ese mtodo se truca para que salte en realidad a una rutina artiicial. ,Cual es el cdigo
de esa rutina artiicial Muy sencillo: se traduce el cdigo intermedio de esa rutina y se arreglan
las cosas para que una posible segunda llamada a MeDaLoMismo salte de cabeza al cdigo na-
tio real y deinitio.
Compilacin por adelantado
Alternatiamente, se puede compilar la aplicacin durante su instalacin. Antes de la instalacin
no es recomendable hacerlo, porque el resultado de esta operacin depende en gran medida de
la ersin exacta del sistema operatio, del tipo de CPU, e incluso puede que de la memoria
disponible. Lste proceso se actia desde una aplicacin de lnea de comandos incluida en la
plataorma y llamada ngen. Los detalles, tanto sintacticos como aran ligeramente con la er-
sin de la plataorma. Ln la ersin 2.0, por ejemplo, se puede dierir la traduccin para que sea
ejecutada en algn momento apropiado desde un sericio del ordenador.
Ademas de estos dos algoritmos basicos de traduccin, hay que tener en cuenta las particularidades
del equipo donde se ejecutara la aplicacin. Por ejemplo, el traductor para \indows CL tiene en
cuenta la poca memoria dinamica que es caracterstica de los dispositios donde debe ejecutarse. Ln
estas circunstancias, el sistema operatio puede eliminar de la memoria dinamica la traduccin de
determinadas rutinas ,siempre las menos usadas, y es posible que haya que traducir a cdigo natio
una misma rutina mas de una ez.
Imagino que, al llegar aqu, aquellos que todaa no sientan mucho aecto por .NL1, sonreiran pen-
sando que han encontrado la panacea. Una dosis de ngen a todo lo que compilen, y todas las pesadi-
llas asociadas al cdigo intermedio se desaneceran. Cuidado! Puede que pasar todo a cdigo na-
tio no sea siempre una buena idea:
Si la aplicacin unciona como seridor, mas temprano que tarde todo el cdigo intermedia
habra sido traducido a cdigo natio, y el actor traduccin deja de importar. Ls una paradoja
que sea preerible ejecutar ngen sobre una aplicacin GUI, para las que se supone que la eloci-
dad de procesamiento no es tan crtica, que ejecutarlo sobre un seridor, que suele ser el cuello
de botella en muchos casos. Pero hay un candidato incluso mejor: las aplicaciones de lnea de
comando, como el compilador que estoy desarrollando para el lenguaje lreya
1
. Ls comprensi-
ble: cada ez que se ejecuta el compilador, inmediatamente despus se descarga de la memoria,
y en la prxima ejecucin es necesario rehacer todo el trabajo de traduccin. Por este motio, el
compilador de lreya se traduce a cdigo natio al ser instalado.
Sorprendentemente, el compilador JI1 en tiempo de ejecucin puede realizar mejor trabajo que
el compilador ngen. lay arios motios para ello, y no todos son eidentes. Ln tiempo de
ejecucin, por ejemplo, el traductor se puede hacer una idea mejor de las condiciones en las que
se ejecuta la aplicacin. Incluso puede jugar con la relocalizacin de bloques de cdigo para
minimizar la paginacin. Un actor menos eidente es que en tiempo de ejecucin ya se sabe
cuales ensamblados necesita realmente la aplicacin. Lsto permite realizar operaciones mas
agresias de inlining, o expansin de cdigo en lnea.
Ensamb|ados y mdu|os
.NL1 no orece nada similar al enlace esttico de cdigo de otras plataormas de desarrollo, inclu-
yendo \indows en modo natio`. 1omemos como ejemplo una clase: digamos que se trata de
Complex, para el manejo de nmeros complejos. Ln lenguaje tradicional, como C--, el programa-
dor que crea esta clase la compila para obtener una biblioteca de clases. Ln \indows natio, esta
biblioteca sera un ichero con extensin lib. Si a continuacin, usted escribe dos programas que
hace uso de esta clase, el cdigo natio correspondiente a la misma se copia dentro de sus dos
aplicaciones. Aunque esto no es malo, los erdaderos problemas comienzan cuando usted disena y

1
freyalang.blogspot.com
Common Language Runtime
Copyright 2004-200S, Ian Marteens
5
5
compila dos bibliotecas dinamicas, como las DLL de \indows natio, que hacen uso de Complex.
Cada biblioteca cargara con su propia copia de la clase, y si una aplicacin necesita usar ambas
bibliotecas dinamicas, en el espacio del proceso ejecutable tendramos tambin dos copias indepen-
dientes de la clase de nmeros complejos. Por supuesto, la solucin eidente consiste en alojar la
clase Complex, desde el primer momento, en una tercera biblioteca dinamica.
Los disenadores de la plataorma .NL1 cortaron por lo sano: cada proyecto que usted compile
generara, o un ichero ejecutable, o una DLL. ,Quiere compartir cdigo entre proyectos Cree una
biblioteca de clases, que siempre sera de tipo dinamico. Con esta decisin no perdemos nada
signiicatio. Ls cierto que el sistema operatio tendra un poco mas de trabajo durante la carga de
las aplicaciones, pero las entajas que obtenemos como compensacin son mas que suicientes.
Lxiste, sin embargo, una maxima en Inormatica que, medio en broma, medio en serio, suele ser
cierta:
Todo avance en Informtica consiste en aadir otra capa de indireccin.
Ln este caso, la capa de indireccin consiste en que el programador no trabaja directamente con los
icheros sicos, sino que utiliza identiicadores simblicos para reerirse a las bibliotecas de clases.
Lsto permite que arios icheros sicos puedan combinarse de orma lgica en una misma biblio-
teca. A estos icheros sicos se les conoce como mdulos, y la unin de uno o mas de estos mdu-
los se llama ensamblado:

Un mdulo slo puede pertenecer a un ensamblado. Debo tambin precisar que los icheros
ejecutables tambin se consideran como un tipo especial de ensamblado. Ln realidad, esta es una
explicacin muy simpliicada. Lstoy dejando deliberadamente uera el sistema de ersiones, la irma
de ensamblados para su autentiicacin, etc. Lo que ahora nos importa es anadir y gestionar reeren-
cias en un proyecto de Visual Studio.
Ademas de permitir la ragmentacin de un ensamblado grande en trozos sicamente manejables,
la principal utilidad de los mdulos, al menos de los mdulos compilados por usted y yo, es permitir
que un ensamblado se cree a partir de proyectos desarrollados en lenguajes dierentes. Sin embargo,
no es acil trabajar con mdulos, si nos atenemos a las plantillas predeinidas por Visual Studio. Para
crear un mdulo hay que pasar un parametro especial al compilado de lnea de comandos: como
Visual Studio no trae plantillas para mdulos, habra que indicar ese parametro directamente en la
coniguracin del proyecto. Para unir arios mdulos en un ensamblado puede usarse tambin el
correspondiente compilador de lnea de comandos.
N
O
T
A

He mencionado la existencia de mdulos para explicar por qu hay ensamblados del sistema frag-
mentados entre varios ficheros fisicos, y para no dejar espacios en blanco en la explicacin de los
ensamblados. No obstante, no volveremos a tratar con mdulos en este curso, al menos de forma
directa.

6|ases y espac|os de nombres
,Qu contiene cada ensamblado en su interior Lo principal: una lista de tipos de datos y sus imple-
mentaciones, cuando procede. lay tambin reerencias a otros ensamblados que necesita para su
uncionamiento, y metadatos de todo tipo y niel.
Cada tipo de datos tiene un nombre que consiste por lo general en una lista de identiicadores
separados por puntos. Ll punto, en realidad, es un caracter mas dentro del nombre del tipo. De esta
manera, tenemos tipos con nombres como:
Ensamblado Ensamblado
Ndulo Ndulo Ndulo
Intuitive C
#

Copyright 2004-200S, Ian Marteens
6
6
System.String
System.Data.SqlClient.SqlDataAdapter
Microsoft.CSharp.CSharpCodeProvider
IntSight.Proteus.Stargate.Entity
Ahora bien, la mayora de los lenguajes que soportan la plataorma interpretan este ormato de
nombres segn un conenio recomendado por la plataorma, en realidad, no conozco lenguaje
alguno que no siga el conenio mencionado. Lste consiste en identiicar el ltimo identiicador de la
cadena como el nombre abreiado del tipo de datos. Los identiicadores restantes se tratan como si
uesen nombres de espacios de nombres, o namespaces. Un espacio de nombre es simplemente un
contenedor lgico que puede albergar otros espacios de nombre anidados, o tipos de datos. Por
ejemplo, obsere el siguiente nombre de clase:
System.Data.SqlClient.SqlDataAdapter
Ll nombre abreiado del tipo es SqlDataAdapter. Ll tipo se encuentra deinido dentro de un espacio
de nombres llamado SqlClient, deinido dentro de Data, que inalmente se deine dentro del espacio
de nombres de primer niel llamado System.
Ll problema con este conenio es que no existe una entidad sica` que represente al espacio de
nombres. Lsto, por regla general, es bueno: podemos deinir clases dentro` del espacio de nom-
bres System en cualquier ensamblado. Lso mismo es, a la ez, un inconeniente. La nica orma de
saber qu espacios de nombres se utilizan dentro de un ensamblado es buscar todas las clases
deinidas dentro del ensamblado, eliminar los nombres abreiados de tipos y eliminar luego los
duplicados. Lste mismo problema es el que encontramos al deinir las reerencias de un proyecto:
es acil conundir un nombre de ensamblado con un espacio de nombres. Ln deinitia, ambos son
listas de identiicadores separados por puntos.
,Cmo podemos saber qu contiene un ensamblado dado Oicialmente, la plataorma orece una
utilidad llamada ildasm, una aplicacin de interaz graica. Mi consejo, sin embargo, es que utilice
otra herramienta para estos propsitos: el excelente y sin par Reflector, de Lutz Roeder, una aplica-
cin gratuita que puede descargarse desde la siguiente pagina:
www.aisto.com/roeder/dotnet
Ln el momento en que escribo estas lneas, la ersin mas reciente de Reflector es la 4.2.48.0, y un-
ciona tanto con la ersin 2.0 de la plataorma como con las ersiones anteriores. Aqu tiene una
imagen de esta aplicacin:

Ll nodo superior del arbol corresponde al ensamblado System. Inmediatamente debajo aparece el
mdulo correspondiente al ichero system.dll. Ll mdulo hace reerencia a otros ensamblados, que
no se muestran en la imagen, pero deine una serie de clases que la aplicacin reparte entre los
Common Language Runtime
Copyright 2004-200S, Ian Marteens

7
correspondientes espacios de nombres. Por ejemplo, emos un espacio Microsoft.CSharp que con-
tiene arios tipos priados, en color gris, y el tipo CSharpCodeProvider, que al parecer es pblico y
podemos usarlo en nuestras aplicaciones.
Procesos y dom|n|os de ap||cac|n
Ll modelo de ejecucin tradicional de \indows consiste en procesos mutuamente aislados, con un
conjunto de uno o mas hilos ,threads, por proceso. Cada hilo pertenece a un nico proceso desde
que se crea hasta que se destruye. Obsere que he aclarado que se trata del modelo de \indows:
otros sistemas operatios tienen modelos dierentes. Ll de \indows es bastante bueno y eiciente:
la memoria se asocia al proceso, no al hilo, y es acil compartir inormacin entre hilos de un mismo
proceso, pues todos tienen acceso a la memoria del proceso. Por supuesto, tendremos que
preocuparnos por la sincronizacin del acceso a dicha memoria, pero la sincronizacin es ineitable
mas tarde o temprano, con independencia del sistema operatio.
No obstante, cuando ejecutamos .NL1 sobre \indows, manejamos un nueo concepto: el de
dominio de aplicacin, o application domain ,AppDomain,:

Como sugiere el diagrama, cada proceso puede contener uno o mas dominios de aplicacin. ,Se ha
dado cuenta de que no he incluido hilos en el diagrama Por una parte, procesos y dominios de
aplicacin tienen algo importante en comn: identiican una zona de memoria. La memoria de cada
proceso se reparte entre sus dominios de aplicacin, y cada zona es inaccesible para las demas.
No existe, sin embargo, una relacin tan clara entre hilos y dominios de aplicacin. Los hilos de
cada proceso pueden ejecutarse dentro de cualquiera de los dominios, aunque en cada momento, un
hilo slo puede residir dentro de un nico dominio. ,Ll motio Pues que los dominios puedan
compartir hilos de una cach comn. Cada proceso en .NL1 dispone de una cach propia de hilos,
que es utilizada por arios sistemas del CLR, como la ejecucin asncrona mediante delegados.
Los dominios de aplicacin son, a la ez, una oportunidad y una necesidad. Lo de oportunidad`
tiene que er con la curiosa orma en que se implementan:
Las aplicaciones y bibliotecas en .NL1 deben aprobar una verificacin antes de poder ser carga-
dos y ejecutados. Lsta eriicacin garantiza que el cdigo que la aprueba nunca inadira
memoria ajena. Por lo tanto, para que dos dominios de aplicacin mantengan su independencia
hay que hacer. nada, o casi nada.
.NL1 tiene buenas razones para echar mano de este recurso:
La principal razn se llama ASP.NL1. Un seridor \eb necesita montones de procesos`
uncionando en paralelo para atender a las distintas paginas y aplicaciones. Si hablamos de
procesos` erdaderos, el coste en recursos sera enorme, pero si se trata de dominios de
aplicacin, todo encaja.
Un motio relacionado: ASP.NL1 necesita descargar mdulos y oler a cargarlos si detecta
cambios en el cdigo uente asociado. Una ez que se carga una DLL en un proceso clasico, es
muy dicil descargarla, por las reerencias implcitas al cdigo que pueden estar ocultas por aqu
y por alla. Sin embargo, si la DLL se ha cargado dentro de un dominio de aplicacin, es muy a-
cil descargarla. descargando todo el dominio de aplicacin. Lste es el mecanismo oicial en
.NL1 para la descarga dinamica de bibliotecas de unciones.
Incluso si no a a usar directamente ASP.NL1, le interesa conocer el uso de este recurso. Las aplica-
ciones de gestin, por ejemplo, necesitan un grado de dinamismo que no puede, o no debe lograrse,
mediante el clasico ciclo de desarrollo, compilacin e implantacin. Si un comercio decide usar una
Proceso Proceso
AppDomain AppDomain AppDomain
Intuitive C
#

Copyright 2004-200S, Ian Marteens
8
S
nuea orma de pago, un sistema de transporte con tarias especiales o una promocin con
descuentos a la medida, la solucin no debe ser oler a sentarse rente al ordenador y reprogramar
los mdulos aectados. Ln muchos casos, se utiliza algn tipo de intrprete o de generador de c-
digo para dar respuesta a este tipo de necesidades. Con .NL1, es muy sencillo y eiciente generar
cdigo intermedio sobre la marcha. Normalmente, no es necesario hacer nada especial para descar-
gar el cdigo compilado, pero en determinados casos s sera un requisito. Ln tales casos, lo mas
indicado es cargar el cdigo compilado en dominios de aplicacin dentro del mismo proceso, para
poder descargar luego el cdigo de manera indolora.
N
O
T
A

No obstante, como los dominios no pueden comunicarse directamente entre si, hay que usar algun
tipo de comunicacin remota para poder manejar a distancia los objetos creados en otro dominio.

E| s|stema de t|pos comunes
Para que todos los lenguajes soportados por .NL1 puedan comunicarse entre s, deben compartir
un mismo sistema de tipos y un mismo modelo de objetos. Ll siguiente diagrama muestra los distin-
tos tipos de objetos deinidos por el Common Type System ,C1S,, que es el nombre del sistema de
tipos de .NL1:

Lste es un sistema de tipos mas rico y expresios que el soportado por Jaa. Lste ltimo lenguaje
slo incluira, de los tipos anteriores, esta lista reducida:
1 Clases
2 1ipos de interaz
3 1ipos basicos o atmicos
Jaa intenta utilizar el concepto de clase para modelar absolutamente todo, lo cual es correcto desde
el punto de ista conceptual. y un erdadero incordio desde el punto de ista practico. Ln .NL1
tambin se considera que la clase es el concepto undamental, pero se toleran ciertas mutaciones del
concepto para acilitar algunas tcnicas de programacin de uso recuente.
T|pos bs|cos
Antes de complicarnos demasiado la ida, eamos qu es un tipo basico` y cuales de ellos nos
orece la plataorma. No es una deinicin sencilla, entre otras cosas porque todos los lenguajes
intentan disimular al maximo las posibles caractersticas especiales de estos tipos. 1omemos como
ejemplo los enteros. Resulta que C
4
nos permite escribir expresiones como la siguiente:
999.ToString()
Common Type
System
Tipos
abstractos
Tipos
concretos
1nfeface 1nfeface 1nfeface 1nfeface
Tipos de
referencia
Tipos de valor
Tipos atmicos Tipos
compuestos
enum enum enum enum
Tipos basicos
sfucf sfucf sfucf sfucf
c1ass c1ass c1ass c1ass de1egafe de1egafe de1egafe de1egafe
vectores
Common Language Runtime
Copyright 2004-200S, Ian Marteens
9
9
Claro, es una tontera escribir una expresin como la anterior. aunque no tanto, si la uncin To-
String incluye una cadena de ormato:
999.ToString("C")
,Lsta seguro de que se trata de una expresin constante Ln Lspana imprime una cantidad en eu-
ros, claro. Lo que importa es que, a medida que los lenguajes orientados a objetos progresan, es
cada ez mas dicil distinguir entre tipos basicos y objetos. Lsto es bueno para el programador,
porque as debe recordar menos excepciones a las reglas, pero en el ondo se trata de un disraz.
Los objetos, ya sean instancias de clases o de estructuras, son tipos compuestos que, obiando la
recursiidad, deben deinirse con la ayuda de tipos mas simples.
A pesar de todo lo dicho, hay una orma inequoca para diagnosticar si un tipo determinado se
puede considerar basico`: basta con mirar la documentacin del Common Language Runtime, y
aeriguar si el tipo dado es reconocido como tal por la maquina irtual de ejecucin. Por ejemplo, la
maquina reconoce sin mas los tipos enteros de 32 bits. Aunque no se puede trabajar directamente
con enteros de 16 bits en la pila, existen operaciones para la conersin en las direcciones con los
enteros de 32 bits. lay otro indicio necesario, aunque no suiciente: todos los tipos basicos son
tipos de valor, como eremos mas adelante.
C
4
asigna una palabra clae para cada uno de los tipos basicos. Ademas, estos tipos tienen un nom-
bre completo, como cualquier tipo de datos normal del Common Type System. Lstos son los tipos
basicos y sus nombres, tanto desde el punto de ista de C
4
como del CTS:
C
#
CLR Significado
bool System.Boolean verdadero y falso, cno es asi?
char System.Char Caracteres Unicode (I16 bits cada uno!)
sbyte System.SByte Enteros de 8 bits con signo.
byte System.Byte Enteros de 8 bits sin signo.
short System.Int16 Enteros de 16 bits con signo.
ushort System.UInt16 Enteros de 16 bits sin signo.
int System.Int32 Enteros de 32 bits con signo.
uint System.UInt32 Enteros de 32 bits sin signo.
long System.Int64 Enteros de 6+ bits con signo.
ulong System.UInt64 Enteros de 6+ bits sin signo.
float System.Single valores reales representados en + bytes.
double System.Double valores reales representados en 8 bytes.

lay autores, y entre ellos hay alguno amoso, que aconsejan no usar los nombre de tipos abreiados
de C
4
. Aducen que as se acilita la conersin de cdigo entre lenguajes soportados por la plata-
orma. De modo que, de hacer caso al consejo, para declarar una ariable local de tipo entero
tendramos que escribir lo siguiente:
//
System.Int32 i = 0;
//
,Se imagina un bucle for
//
for (System.Int32 i = 0; i < 10; i++)
{

}
//
Me parece horrible. Ln primer lugar, porque no tengo ni la mas mnima intencin de programar
con Visual Basic.NL1. al menos, mientras pueda eitarlo. Ls cierto que, en teora, C
4
y este len-
guaje son uncionalmente equialentes, en la practica, C
4
a siempre por delante. Pero nadie me
paga para demostrar que soy un polglota de la programacin. Ln segundo lugar, el que yo escriba
int en mi cdigo uente no aecta al modo en que un hipottico programador en Visual Basic era
las declaraciones de mi ensamblado, ya que Visual Studio siempre se ocupara de estos detalles su-
cios.
Intuitive C
#

Copyright 2004-200S, Ian Marteens
10
10
lemos isto los tipos basicos, pero ,no hay un tipo de datos para las cadenas de caracteres laylo,
pero no es un tipo basico`, sino una clase especial. \ lo mismo sucede con el tipo que se suele
usar para representar dinero, pasta, parn, plata, guita
2
. o como quiera llamarlo. Lo notable res-
pecto a estos dos senores es que tienen nombres especiales en C
4
:
C
#
CLR Significado
object System.Object ILa fuente de la vida! Al menos, tericamente.
string System.String Cadenas de caracteres
decimal System.Decimal valores decimales de alta precisin, con 128 bits de capacidad.

le anadido el tipo object a la tabla, porque se trata tambin de un tipo de clase con una palabra
reserada a su disposicin. La existencia de esta palabra clae se debe al mayor protagonismo de
este tipo en las primeras ersiones de la plataorma, en las que las operaciones de boxing y unboxing
eran mucho mas importantes que ahora. A partir de la ersin 2.0, los tipos genricos hacen innece-
sarias la mayora de las aplicaciones del boxing.
linalmente, coniene presentar arios tipos relacionados con el acceso a datos:
CLR Significado
System.DateTime Fecha y hora.
System.Guid Corresponde al tipo uniqueidentifier de SQL Server.
System.DBNull Usado para representar valores nulos provenientes de una base de datos.

Lstos tipos no tienen nombre propio en C
4
, y slo los menciono porque los eremos con recuen-
cia en el curso. Sobre todo me interesa presentar la clase DBNull: cuando recuperamos el alor de
una columna que contiene un alor nulo, recibimos un alor de tipo DBNull. Para ser exactos,
recibimos el nico alor que puede existir para esta clase.
Vectores
Un lenguaje, como cualquier teora, es mas elegante mientras menos casos especiales presente. Lsta
es una uerza centrpeta, que tiende a reducir el tamano y complejidad del lenguaje. Se equilibra con
la correspondiente uerza centruga: la simpliicacin no debe aectar negatiamente ni a la acili-
dad de uso, ni a la eiciencia de la implementacin.
Ll caso de los vectores ,arrays, es un ejemplo clasico del conlicto entre ambas tendencias. Nos
gustara que un ector uese una clase mas. y hasta cierto punto se logra. Pero una caracterstica
de ellos nos amarga la golosina: no existen ectores a secas. Mas bien, existen ectores de enteros,
ectores de cadenas, ectores de personas y ectores de abogados. 1cnicamente hablando, tene-
mos una operacin de construccin de tipos, que a partir de un tipo T, nos orece un nueo tipo T',
o si utilizamos la notacin de C
4
, el nueo tipo T[].
N
O
T
A

En C
#
1.0, este "constructor de tipos" es unico en su gnero. Sin embargo, con la llegada de los ti-
pos genricos en la versin 2.0, los vectores podrian considerarse como un caso especial de tipo
genrico. Lamentablemente, ni C
#
ni la propia plataforma los tratan de esta manera: los vectores
siguen siendo tipos especiales. En Freya, en contraste, los vectores si se consideran un caso mas de
tipo genrico, aunque la implementacin luego no los trate asi.

Si comparamos C
4
con C,C--, encontraremos una dierencia importante en la orma en que se
declara un ector. 1ome nota:
// Esto sera en C++
int vector[];
// Esto es C#
int[] vector;
Los autores de C deendan una extrana teora sobre las declaraciones de ariables: la declaracin
deba preigurar la orma en que luego se usara la ariable. Como resultado, cuando se declara una

2
Slo las cosas vv, importantes tienen tantos nombres.
Common Language Runtime
Copyright 2004-200S, Ian Marteens
11
11
ariable de ector en C,C--, el tipo base del ector queda a la izquierda de la ariable y los corche-
tes a la izquierda: no se trata al ector como a un tipo de datos mas. Ln C
4
, y antes en Jaa, las
declaraciones de ectores son mas sensatas, y los corchetes se asocian al tipo, no al identiicador que
se esta declarando. Ahondando en esta dierencia, la siguiente declaracin en C-- no sera correcta
en C
4
,excepto en el modo de programacin no seguro, en C
4
2.0,:
// Esto es C++. En C# no est permitido.
int vector[7];
Para declarar e inicializar un ector en C
4
alen estas alternatias:
int[] vector1 = new int[7] { 6, 5, 4, 3, 2, 1, 0 };
int[] vector2 = new int[] { 11, 22, 33, 44, 55, 66, 77 };
int[] vector3 = { 111, 222, 333, 444, 555, 666, 777 };
A pesar de la sintaxis especial, las instancias de ectores son objetos, a todos los eectos. Aparte de
obtener sus elementos indiiduales con la ayuda de corchetes y una expresin numrica, podemos
aplicar mtodos y propiedades sobre la instancia. Obsere cmo se utiliza la propiedad Length en el
siguiente ejemplo:
string[] dias = { "Lunes", "Mircoles", "Viernes" };
for (int i = 0; i < dias.Length; i++)
dias[i] = dias[i].ToLower();
Ln el bucle anterior, recorremos y modiicamos el ector, pero si slo necesitamos recorrer sus
elementos, podemos usar la instruccin foreach:
foreach (string s in dias)
Console.WriteLine(s);
Internamente, el Common Language Runtime considera que una instancia de ector pertenece a una
clase especial deriada de la clase System.Array. Ls importante saberlo porque podemos usar los
miembros deinidos para la clase Array con ectores del tipo que sean. Muchos de los mtodos que
orece Array, sin embargo, son mtodos estaticos. Ll mtodo estatico Resize, por ejemplo, nos per-
mite cambiar la cantidad de elementos en una instancia de ector:
string[] dias = { "Lunes", "Mircoles", "Viernes" };
Array.Resize(ref dias, dias.Length + 1);
dias[dias.Length 1] = "Sbado";
La palabra clae ref que aparece en el primer parametro de la llamada a Resize, indica que se trata de
un parametro pasado por reerencia, y que el mtodo puede cambiar el contenido de la ariable.
Copyright 2004-200S, Ian Marteens
12
? ?? ?
PROGRAMACON ORENTADA A OBJETO8

OS LLNGUAJLS DL PROGRAMACIN ORILN1ADOS a objetos llean ya mucho tiempo con noso-
tros. Cuesta imaginar que exista todaa algn programador que no tenga unas nociones mni-
mas sobre qu signiica la amosa orientacin a objetos`. Pero qu tenga plena consciencia sobre
qu es lo que hace buena esta metodologa es otro asunto. Lsto hace posible que, de cuando en
cuando, algn iluminado se disrace de proeta y airme haber superado la tontera de los objetos.
e incluso la necesidad de la eriicacin estatica de tipos.
La Programacin Orientada a Objetos tiene sus limitaciones y problemas, por supuesto. Lxisten
propuestas que la complementan, o que corrigen algunos de sus problemas. Ls tambin posible que
un da descubramos que existe una orma mejor de programar, y que la metodologa se dierenciaba
tanto de la P.O.O. que era casi imposible dar con ella mediante pequenas desiaciones de lo que
ahora se considera correcto. Pero hay que ser prudentes. 1ambin llegara un da, quizas no muy
lejano, en el que la 1eora de la Relatiidad de Linstein sea sustituida por una teora mejor. La nuea
teora, sin embargo, tendra que dar respuesta a todo lo que la Relatiidad ya explica. \ en todo caso,
es poco probable que sea el cajero del supermercado quien d con ella. Aunque es posible.
N
O
T
A

Este capitulo es opcional. Si lleva usted tiempo programando, no slo le advierto que puede saltar-
selo; ademas, Ise lo aconsejo!, al menos, en una primera lectura, pues todo lo que narro aqui ya lo
habra leido en mas de una ocasin. Si decide seguir, tengo para usted una segunda advertencia: lo
que explico aqui no es exactamente una historia de las ideas, sino una posible explicacin de las
relaciones entre las mismas. porque lo que intento es hacer mas asequibles y comprensibles esas
ideas. Ne ha interesado mas, por ejemplo, contar cmo una pequena mutacin separa las unidades
de Delphi y los paquetes de Ada que relatar la historia de Jean !chbiah y el diseno de Ada.

Abstracc|n 0r|entada a 0bjetos
Antes de la aparicin de los lenguajes orientados a objetos, los lenguajes imperatios tradicionales se
enrentaban a una eleccin dicil por culpa de los sistemas de tipos de aquel entonces. Un lenguaje
poda implementar la eriicacin estatica de tipos con todas sus consecuencias. Lo malo es que,
entre esas consecuencias, estaba la prdida de expresiidad y lexibilidad. Por el contrario, poda
relajar las eriicaciones aqu y alla, y conertir al lenguaje en un coladero de errores de programa-
cin, o incluso renunciar a la eriicacin estatica, sustituyndola por eriicaciones en tiempo de
ejecucin. ,Resultado Lentitud y alta de seguridad en la programacin.
Ll otro problema importante anterior a la Programacin Orientada a Objetos era la diicultad para
reutilizar cdigo ya existente. Los mdulos reutilizables de por entonces eran simples bibliotecas de
unciones. ,Cmo se puede montar un sistema a partir de un conjunto de unciones Si stas son lo
suicientemente lexibles, podemos agrupar arias de ellas. siempre que escribamos el cdigo a la
medida que acte como pegamento.
Digamos, por concretar, que queremos programar un ray tracer. Si no existiese la Programacin
Orientada a Objetos, nuestro punto de partida, con la suerte a nuestro lado, sera una biblioteca de
unciones disenada para trabajar con ectores, matrices y colores. \ poco mas. Ls cierto que existen
descripciones muy precisas de los algoritmos utilizados por un ray tracer. Pero el tipo de abstrac-
cin necesaria para materializar estos algoritmos en cdigo reutilizable se expresaba con diicultad
en aquellos lejanos tiempos.
L
Programacin Orientada a Objetos
Copyright 2004-200S, Ian Marteens
13
13
Para explicarlo, usar un ejemplo mas sencillo: imagine que necesitamos una rutina que ordene un
ector con elementos de tipo arbitrario. Si elegimos el algoritmo quicksort, la rutina debe compor-
tarse de esta manera:
1 Debemos elegir un elemento arbitrario del ector.
2 Moemos los elementos del ector de manera que todos los elementos iguales o menores que el
elemento antes elegido queden a la izquierda de los elementos mayores. Para ello:
Usamos un par de punteros, inicialmente al principio y al inal del ector a ordenar.
Comparamos los dos elementos reeridos por los punteros mencionados y.
. un momento, ,qu quiere decir comparar Puede signiicar dierentes algoritmos, por lo que
deberamos buscar un modo de pasar la ormar de comparacin como un parametro mas de la
rutina. ,Se puede pasar un algoritmo como parametro en la programacin estructurada Claro que
se puede! Lso es lo que se ha conseguido desde siempre con los punteros a unciones. ,Por qu he
dicho entonces lo de se expresaba con dificultad La respuesta es sencilla: si nos ponemos
quisquillosos, no hay algoritmo que no pueda programarse en lenguaje ensamblador!
Ll que tal programa sea comprensible y acil de mantener es un asunto muy dierente.
Encapsu|am|ento: datos
Ln programacin, hay que tener mucho cuidado con la rase esto no vale. lay muchos criterios
para alorar la calidad del cdigo, y no existe hasta el momento un procedimiento mecanico al que
le suministremos cdigo uente y responda esto es una porquera o usted es un genio, amigo.
Quizas sea bueno que as sea. ,Imagina la cantidad de personas que podan quedarse sin empleo
,Por qu es dicil de mantener un programa que pasa punteros a unciones a diestra y siniestra La
culpa no es, en su mayor parte, de que los punteros a unciones sean tipos muy especiales, aunque
tambin inluya esta singularidad. La culpa, en realidad, es la alta de abstracciones adecuadas en el
estilo de programacin antes esbozado. La rutina de ordenacin es muy simple como ejemplo, por
lo que utilizaremos algo mas complicado.
Una de mis primeras aplicaciones grandes`, cuando estudiante, ue un editor de texto. 1ena dos
compiladores a mi disposicin: el 1urbo Pascal 3.0 y el compilador de C de Microsot. Como e,
nada de programacin orientada a objetos. Como todaa no se haban puesto de moda los sistemas
de entanas, mi editor era una aplicacin que editaba un nico ichero por ez. Las ariables que
controlaban el estado del editor eran, precisamente, variables: un buen punado de ariables globales
al alcance de cada una de las rutinas. No consero el cdigo uente de aquel desastre, pero pode-
mos hacernos una idea:
var
DesplHorizontal, DesplVertical: Integer;
CursorX, CursorY: Integer;
NombreFichero: String;
GuardarCopia: Boolean;
Me a a perdonar que omita las correspondientes rutinas que utilizaban todas estas ariables, pero
creo que este diagrama le permitira hacerse una idea:

va1ab1es
kuf1nas
Intuitive C
#

Copyright 2004-200S, Ian Marteens
14
14
le intentado dibujar el equialente de una orga dionisaca entre rutinas y ariables. Ls imposible
saber de quin es cada mano o cada pie. y es muy probable que alguna rutina termine tocando
algo que no le interesaba tocar. Si hay M ariables y N rutinas, el total de dependencias entre rutinas
y ariables es, naturalmente, MxN enlaces. \ la realidad suele ser an peor, pues no he hablado de las
dependencias entre las propias rutinas. Como resultado, cada ez que haya que hacer un cambio en
este sistema, habra que reisar todas las rutinas para aeriguar cuales ariables y rutinas se eran
aectadas.
Pero esta es una pelcula que han pasado muchas eces por teleisin. A no ser que est usted
empezando en este negocio, ya tendra usted la respuesta: para eitar esta marana de dependencias,
se utiliza la descomposicin modular. Se trata de diidir el sistema en partes mas pequenas, de ma-
nera que entre estas partes exista la menor comunicacin posible. Ls decir: debe tratarse de partes
lo mas independientes que sea posible. Incluso si slo logramos diidir el sistema en dos, ya habre-
mos conseguido una importante entaja:

Como cada parte tiene ahora la mitad de elementos que antes, el nmero total de lechas se reduce
a la mitad ,comprubelo!,. Recuerde, no obstante, que la reduccin de complejidad se consigue
cuando no existe practicamente comunicacin entre los mdulos en los que hayamos diidido el
sistema. \ para ello hacen alta recursos en el lenguaje que no tenan los primitios Pascal y C. Por
ejemplo, una manera de organizar un poco el cdigo de mi editor consista en agrupar las ariables
globales dentro de records, en Pascal, o structs, en C.
type
Posicion = record
X, Y: Integer;
end;
var
Desplazamiento: Posicion;
Cursor: Posicion;
Lsto slo nos sire para hacernos una idea sobre cuales grupos de ariables pueden modiicarse en
equipo, pero no nos orece mas garantas. Sigue siendo una orga jamonera, pero ahora hay marcas
de pintura en el paimento, como en un aparcamiento.
Un|dades, paquetes y otros tr|unfos parc|a|es
Naturalmente, el mundo no contuo el aliento hasta el momento en que Bjarne Stroustrup desente-
rr el mapa del tesoro que unos noruegos haban escondido a inales de los 60s en un iordo
escandinao: nos hubisemos asixiado de intentarlo. Lstas ideas sobre descomposicin modular
son casi axiomas del sentido comn, y estaban dando ueltas en el imaginario colectio desde los
mismsimos orgenes de esta disciplina. Ah estan, como demostracin, antiguas modas como las
unidades de Pascal, los mdulos de Modula y los paquetes de Ada.
De estos tres sistemas, el mas sencillo y acil de explicar son las unidades de Pascal, que aparecieron
por primera ez en UCSD Pascal, y que inspiraron las unidades que Borland anadira un poco mas
tarde a su 1urbo Pascal. La idea es extremadamente sencilla: cada ichero compilable se diide en
dos zonas. La primera, encabezada por la palabra clae interface, contiene declaraciones pblicas.
Programacin Orientada a Objetos
Copyright 2004-200S, Ian Marteens
15
15
1odas las declaraciones de la segunda seccin, encabezada por implementation, son internas al
ichero o unidad.
Para ser honestos, casi todos los lenguajes que permitan compilacin por separado, es decir, todos
los lenguajes decentes de aquella poca, orecan un mecanismo similar. aunque por cortesa del
enlazador, casi siempre. Ln C, por ejemplo, a no ser que una rutina o una ariable se declarase
explcitamente con el modiicador extern, se consideraba un recurso priado, inisible uera del
ichero compilado. ,Por qu no he mencionado el sistema de C para ocultar inormacin La res-
puesta es que C y su sistema de compilacin por separado es una de las tres grandes plagas que ha
surido la Inormatica
3
. Aunque con disciplina se puede domar la bestia, es muy acil hacer un uso
equiocado, e incluso peligroso, de la tcnica utilizada por C.
La mu|t|p||cac|n de |os panes y |os peces
Mi editor tena otro problema, un poco mas sutil, pero que al inal sera su condena: slo poda
editar un ichero por ez. 1ratandose de la poca en que el practicamente el nico sistema operatio
disponible para un PC era MSDOS, se trataba de una limitacin importante.
Obsere que hay dos causas relacionadas, pero dierentes, que proocan este problema:
1 Si el estado de un editor se mantiene en un conjunto grande de ariables globales, para mante-
ner dos editores con sus correspondientes estados, es necesario duplicar este conjunto de aria-
bles.
2 Pero incluso si hubiese la suiciente oluntad para declarar dos juegos de ariables, o tres, o un
nmero ariable de ellas, nos quedara el problema de que la mayora de las rutinas utilizaban
directamente las ariables globales.
Ln este sentido, los paquetes ,packages, de Ada ayudaban mas que las unidades de Pascal. Lstos
paquetes se inspiraban en la teora de los tipos de datos abstractos, o ADTs. Un paquete, en s, no era
muy dierente de una unidad Pascal, pero allanaba el camino hacia la implementacin de tipos de
datos opacos. ,Cual es la dierencia Ln Pascal podas agrupar en un registro las ariables que
controlaban el estado del editor, y programar, por separado, una lista de operaciones que actuasen
sobre este tipo de registros. Pero si el registro se declaraba, como era necesario, en la seccin de
interaz de la unidad, todo el contenido del registro segua estando al alcance de cualquiera! No
haba orma de eitar que alguien puentease las operaciones programadas y metiese las zarpas
directamente en los campos del registro.
Con un tipo opaco, por el contrario, se prohiba el uso directo, uera del paquete, de los campos del
registro. Se limitaba as el eecto de un cambio en estos campos al interior del paquete: si la seman-
tica, es decir, la orma de usar las operaciones aprobadas, no se modiicaba, quien quisiese usar el
editor no tena que estar pendiente de los cambios en su implementacin.
Obsere que las entajas que acabo de enumerar beneiciaban al programador que quera usar mi
editor. Pero tambin haba entajas para mi editor: poda descomponer su implementacin en blo-
ques, e implementar estos con la misma tecnologa de tipos opacos.
N
O
T
A

En el caso particular de un editor de texto, para tener dos instancias del mismo ejecutandose en
pantalla no basta con declarar dos variables de tipo Editor. La explicacin mas rapida es que todas
las instancias tendrian que compartir un unico recurso: la pantalla.

Encapsu|am|ento: comportam|ento
Ada es un lenguaje expresio, potente y seguro, y pudo haber dominado durante mucho mas tiempo
la ida intelectual en las acultades de Inormatica. De hecho, una de las caractersticas mas notables

3
Las otras dos plagas ueron el sistema MS-DOS, por su alta de soporte para la concurrencia, y el pererso
ormato OBJ de Intel.
Intuitive C
#

Copyright 2004-200S, Ian Marteens
16
16
de Ada, la genericidad, ha tardado bastante en migrar de manera adecuada a otros lenguajes. S,
existe algo parecido en C--. pero nueamente, su diseno e implementacin han surido la
inluencia negatia de su inculacin al enlazador del correspondiente sistema operatio. Pero, en
todo caso, ue la ola de la Programacin Orientada a Objetos la que barri con Ada.
Si aqu se tratase del reconocimiento a los inestigadores, habra que retroceder hasta el ano 69 y el
lenguaje Simula. Pero como se trata mas bien de explicar las ideas principales, oy a mostrarle cmo
hubiese podido surgir la idea si el ano 69 hubiese sido borrado de la lnea del tiempo. \ como mi
primer lenguaje de programacin ue el Pascal, el ejemplo lo tomar de 1urbo Pascal, antes de que
le anadiesen las extensiones orientadas a objetos.
La joya se esconde entre el barro del trabajo con icheros de textos del 1urbo Pascal primigenio. La
rase fichero de texto es enganosa: en los tiempos en que UNIX era el ejemplo a imitar, poda ree-
rirse a un ichero de erdad`, pero tambin a la pantalla, o a la impresora, o si se trataba solamente
de lecturas, podamos estar hablando del mismsimo teclado. Ln pocas palabras, un ichero de texto,
o dispositivo, en la jerga de entonces, era cualquier arteacto capaz de escupir o tragar una ristra de
caracteres. loy hablaramos de streams, eso que traducimos como flujo de datos.
Ln aquellos oscuros tiempos, 1urbo Pascal oreca un mismo tipo Text para todas estas tareas, y este
tipo se declaraba como un record, o registro. No era un ulgar registro, sin embargo. Ll siguiente
diagrama muestra lo que contenan sus campos:

La parte superior del registro contena, como muestra el diagrama, los tipos campos que esperamos
de una estructura de este tipo: esta abierta y uncionando o no, en qu posicin del ichero se
encuentra, qu tamano total tiene el mismo, etctera, etctera. 1ras esta zona, ena lo bueno: los
tres campos que he etiquetado en el diagrama como Open, Read y Close
4
. Lran campos especiales
porque contenan punteros a las unciones que deban ejecutar estas operaciones, y que ariaban en
dependencia del tipo de dispositio representado por el registro. ,Recuerda los punteros a unciones
de la explicacin del quicksort Acabamos de cerrar el crculo.
Naturalmente, estos punteros a procedimientos deban ser cuidadosamente inicializados. De ello se
ocupaban rutinas como Assign, para el acceso a icheros en disco, o AssignPrn, en el caso de escri-
tura en la impresora. Obsere que, si tenemos arios registros de este tipo de manera simultanea,
hay cierto derroche de memoria. Mas adelante, eremos cmo este pequeno problema se resoli
en la Programacin Orientada a Objetos.
Lo que mas nos interesa ahora es que estamos ante una orma de encapsulacin dierente a que por
entonces se consideraba tradicional`: en un mismo tipo se encapsulaban datos, al modo clasico,
pero tambin comportamiento. Dos registros del mismo tipo podan comportarse de manera muy
dierente, gracias a que sus datos incluan punteros a las unciones adecuadas. De esta idea a la pri-
mera ola de la Programacin Orientada a Objetos slo haba un pequeno paso.

4
No recuerdo exactamente la cantidad y nombres de estos campos, pero es irreleante para la explicacin.
Open
kead
C1ose
Cdigo ejecutable
Zona de datos
???
Programacin Orientada a Objetos
Copyright 2004-200S, Ian Marteens
1
17
Un pequeo camb|o en |a s|ntax|s
Lse pequeno paso era de naturaleza sintactica, y la aparente irreleancia del mismo ue lo que des-
pist a programadores y tericos durante un tiempo. Supongamos que a un programador de aquella
poca le pidiesen un conjunto de rutinas para tratar con ectores geomtricos. Ln Pascal se escribi-
ra algo parecido a esto:
type
Vector = record X, Y, Z: Double; end;
Luego, y enatizo lo de luego`, se declararan las rutinas que trabajaran con el nueo tipo:
procedure InitializeVector(var v: Vector);
procedure SumVectors(var result: Vector; const v1, v2: Vector);
function VectorLength(const v: Vector): Double;
Imagine ahora que le toca montar un sistema complejo que requerira el uso de matrices, ectores,
cuaterniones y otras criaturas de raro pelaje. Alguien ha querido simpliicar su trabajo, y ha mez-
clado los icheros de los distintos subsistemas. Pero ese alguien tiene un curioso sentido del orden, y
ha agrupado las declaraciones de tipos en un ichero, escrupulosamente ordenadas alabticamente,
y las rutinas en otro ichero, tambin respetando el mismo orden. S, se trata de una de mis pesadi-
llas aoritas, y siempre despierto lanzando alaridos.
Si en ez de Pascal, el programador hubiese utilizado Ada, con sus paquetes y tipos opacos, la
probabilidad de reoler el cdigo tan desconsideradamente habra sido mucho menor. pero
seguira siendo posible.
La idea que mencionaba consisti en conertir estas rutinas en parte de la declaracin del propio
tipo. ,No estabamos hablando de encapsular comportamiento Ls lgico, entonces, que las rutinas
que deinen ese comportamiento se consideren parte integrante del tipo de datos. Ln la notacin de
1urbo Pascal 5.5, tendramos entonces:
type
Vector = object
public
X, Y, Z: Double;
constructor Create;
function Length: Double;
procedure Add(const V: Vector);
end;
1omemos nota de las transormaciones:
1 La primera es eidente: las tres rutinas originales son ahora parte de la deinicin del tipo, que
ha dejado de ser un record para conertirse en un object ,una piia del amigo lejlsberg, por
cierto, pero una piia eliz, pues dej libre la palabra class para ser usada mas tarde en Delphi,.
2 1odas las rutinas han perdido un parametro de tipo Vector. Ln realidad, se ha conertido en un
parametro implcito.
3 1ambin ha desaparecido parte del nombre de cada rutina: VectorLength, por ejemplo, ahora se
llama Length a secas. Lsta es una eliz consecuencia del moimiento de las rutinas al ambito de
nombres particular del tipo de datos.
4 La rutina que presumiblemente se encargaba de la inicializacin` del ector, se ha conertido
en un constructor.
5 La suma de ectores, en cambio, ha perdido no uno, sino dos parametros. Sobre esto hablare-
mos enseguida.
Vamos a pasar por alto cmo se implementaba un tipo como Vector, en primer lugar, porque no se
parece mucho a la tcnica de C
4
, y en segundo lugar, porque es un estilo de declaracin que ya es
obsoleto incluso para Delphi, el sucesor de 1urbo Pascal. Pero s me interesa mostrar cmo se utili-
zaba el tipo Vector:
Intuitive C
#

Copyright 2004-200S, Ian Marteens
18
1S
var
V1, V2: Vector;
Len: Double;
begin
V1.Create; // En vez de InitializeVector(V1)
V1.X := 1;
Len := V1.Length; // En vez de VectorLength(V1)
V2.Create;
V2.Y := 1;
V1.Add(V2);
// etctera
end;
lemos transormado uno de los parametros en cada una de las rutinas en un parametro implcito, y
ahora muestro cmo se pasa ese parametro implcito: se escribe antes del nombre la rutina, que
cambia su nombre a mtodo. Obsere que el parametro implcito, en este caso, debe pasarse por
reerencia. De no ser as, la llamada a Create no podra modiicar directamente el ector sino, en
todo caso, una copia del mismo.
Ll cambio mas signiicatio, sin embargo, es el que ha surido la rutina SumVectors. Lsta era su
declaracin original:
procedure SumVectors(var result: Vector; const v1, v2: Vector);
Se reciben dos ectores y se sintetiza uno nueo, que representa la suma de los otros dos. Ln reali-
dad, tanto a usted como a m nos habra gustado mas este otro prototipo:
function SumVectors(const v1, v2: Vector): Vector;
Con esta ariante, recibimos dos ectores y deolemos el resultado como un tercer ector tempo-
ral, que podemos almacenar en una ariable o utilizar directamente como operando en otra opera-
cin. As imitamos el comportamiento de las expresiones matematicas con las que nos amiliariza-
mos desde ninos. No existen eectos secundarios. o al menos, no hay necesidad de introducirlos,
con lo que se simpliica enormemente el analisis y comprensin del cdigo. Sobre todo, es impor-
tante que podamos utilizar esta rutina para componer expresiones mas complejas. Ls acil, por ejem-
plo, sumar tres ectores:
SumVectors(SumVectors(v1, v2), v3)
Con la declaracin original sera mas complicado, pues necesitaramos al menos una ariable tempo-
ral:
SumVectors(v_temp1, v1, v2);
SumVectors(v_temp2, v_temp1, v3);
EJERCICIO PROPUESTO
cPodriamos haber reutilizado la primera variable temporal en la segunda llamada? cPor qu? cEn qu condiciones
seria seguro hacerlo?
Uno de los problemas de este paradigma de computacin es que esta muy alejado del hardware de
nuestros ordenadores, y suele resultar muy ineiciente. Por supuesto, la ineiciencia siempre es rela-
tia, y puede aceptarse si se obtienen los suicientes beneicios a cambio. Pero el estilo uncional,
desligado de otras entajas de la programacin uncional, propiamente hablando, no orece lo
necesario a cambio de la ralentizacin en la ejecucin.
Ll prototipo inal de la suma de ectores releja mejor el paradigma de la orientacin a objetos:
procedure Add(const V: Vector);
1enemos un ector o, mas bien, somos un ector!, y nos pasan un segundo ector. Lo agarramos
por las piernas, lo leantamos en peso y zarandeamos hasta aciar el contenido de sus bolsillos.
Luego arrojamos la ctima y nos lleamos el botn. Vale, he mentido un poco: el segundo ector
no sure cambios. Pero ahora no se genera un nueo ector, sino que modiicamos uno ya existente.
Ln la mayora de los casos, esta es la tcnica mas eiciente, aunque sea la que mas nos obligue a
Programacin Orientada a Objetos
Copyright 2004-200S, Ian Marteens
19
19
escribir para combinar operaciones. ,Ls ste un buen sntoma Depende. Si usted quiere programar
un ray tracer, es lo mejor que le poda pasar: usted necesita elocidad, sobre cualquier otra
consideracin.
De hecho, con la aparicin de la Programacin Orientada a Objetos ,aquella historia de SIMULA
no cuenta a eectos practicos,, ciertos tipos de sistemas recibieron un buen empujn, en el buen
sentido de la palabra. Lstos sistemas se caracterizaban por la reduccin de complejidad tras la
encapsulacin del estado. ale, he soltado la rase para hacerme el inteligente, pero se puede expli-
car con acilidad. Por simple agotamiento de las posibilidades, podemos crear nuestras aplicaciones
con mdulos dotados de memoria propia o no. ,Qu cree usted que es mejor
Aunque parezca lo contrario, la respuesta no es triial. Un seridor de Internet que debe atender
miles de respuestas por segundo unciona mejor si no tiene que memorizar, o tcnicamente, recor-
dar el estado de cada una de sus peticiones. Pero existen muchos mas sistemas en los que recom-
pensa mantener una memoria priada. Un ejercicio mental interesante es reisar los tipos de
aplicaciones que se han beneiciado de la Programacin Orientada a Objetos. Para empezar,
tendramos los sistemas guiados por eentos ,event driven, en la jerga sajona,. 1ambin es intere-
sante comprobar qu tipo de aplicaciones no notaron tanto el cambio. Ln mi humilde opinin, es
mas acil escribir un compilador en C
4
que en el antiguo C. pero no estoy seguro de que C
4
su-
pere en este tipo de aplicaciones a un lenguaje uncional clasico.
,Me permite una imagen como resumen Ll mensaje de moda sonaba ahora como un grito
desesperado de autoairmacin en una reunin de Programadores Annimos:
S, soy adicto a la arquitectura Von Neumann! ,\ qu
Durante dcadas, los tericos de la Inormatica haban intentado atenuar, o al menos disrazar, los
problemas de la llamada arquitectura Von Neumann`. Ahora se decan unos a otros: s, somos
sucios, unos erdaderos cochinos, pero si amos a ser puercos, al menos hagamoslo bien`.
herenc|a
Pero la orientacin a objetos nos trajo un segundo regalo que todaa prooca encendidas polmi-
cas: la herencia. La idea, sin embargo, no era nuea. Los pujos ontolgicos de las primeras olas de la
Inteligencia Artiicial haban popularizado la idea. Lsto se debe principalmente a un gremio de
gente a quienes les gusta llamarse filsofos. No son tan nocios como los abogados, pero pueden ser
utilizados como municin de catapulta por los ejrcitos de ideologas totalitarias. Ln todo caso, lo
que distingue a un ilsoo del resto de los mortales es su pretensin de poder ayudar a resoler
cualquier problema preguntandose que hubiera hecho Aristteles, Santo 1omas de Aquino o
\ittgenstein ,todo depende de la tribu originaria del ilsoo, de hallarse en su caso.
Ll caso es que a los ilsoos les encantan las clasiicaciones y jerarquas: Pinky no es una simple
gata, sino un ejemplar de felis catus, del gnero felis, del gnero felidae, etctera, etctera. Sin em-
bargo, en la ida real usted no se encuentra un carnvoro caminando por la calle: se encontrara con
un gato, un chucho o, si tiene muy mala suerte, uno de esos tigres borgianos que te dicen che, qu
puto mundo! antes de zamparte de un bocado. Lo que quiero decir es que las jerarquas no orman
parte de la razn del mundo, sino del mundo de la razn, y en muchos casos conunden mas que
ayudan. Ln particular, la ieja clasiicacin de Lineo se ha conertido en una curiosidad cultural una
ez que conocemos un poco mas sobre genes, cromosomas y esas cosas.
Una ez expresadas las correspondientes protestas, eamos qu nos orece la herencia de la
Programacin Orientada a Objetos:
1 La primera promesa es que podremos comernos el mundo bocado a bocado.
No estoy exagerando. Los inormaticos siempre han sonado con una especie de Biblioteca de
Borges que traiga respuestas enlatadas para todas las preguntas posibles. y que uncione sobre
cualquier hardware con cualquier sistema operatio.
Intuitive C
#

Copyright 2004-200S, Ian Marteens
20
20
2 La programacin orientada a objetos nos permite utilizar sotware que an no se ha escrito.
Dicho as, parece sorprendente, pero luego eremos que la explicacin es sencilla, y que ya
hemos isto la mayor parte de ella.
Lo que ocurre con la herencia es que, con la misma acilidad, se puede emplear bien o mal. Por
ejemplo, ,debera la clase Punto3D deriarse por herencia de Punto2D Lstructuralmente, s: un
punto tridimensional slo anade un campo mas a un punto en dos dimensiones. \ tiene las mismas
operaciones, mas algunas nueas! Sin embargo, no creo que exista algn programador loco que
implemente un punto tridimensional en esta manera. ,Por qu no lay explicaciones para todos los
gustos. Las aoritas del estimable pblico son las que entran en delicadezas ontolgicas. Personal-
mente, preiero senalar que pocas aplicaciones necesitan tratar en plano de igualdad ,s, es un juego
de palabras, puntos en el plano y en el espacio. Si nunca amos a mezclar ambas clases, es una
riolidad complicar, aunque sea de manera mnima, sus implementaciones. 1enga presente que la
propiedad que calculara la distancia al origen de coordenadas de un punto en 3D se tendra que
escribir as en C
4
:
public override double Length
{
get
{
double l2d = base.Length;
return Math.Sqrt(l2d * l2d + this.z * this.z);
}
}


Po||morf|smo
La segunda gran entaja que trae consigo la herencia se conoce como polimorfismo. Ln el contexto
de la programacin orientada a objetos, se reiere a la posibilidad de que el tipo real de una instancia
sea dierente del tipo de la ariable que hace reerencia a ella. Suponga que tenemos dos clases
sencillas:
public class A
{
// campos definidos por A y heredados por B
public A() { } // Un constructor sencillo para A.
}
public class B : A
{
// campos introducidos por B
public B() { } // Un constructor sencillo para B.
}
Si un mtodo determinado acepta como parametro una instancia de la clase A, dicho mtodo
uncionara sin problemas si en ez de la instancia de A le pasamos una instancia de la clase B. Para
simpliicar, supondremos la existencia de un mtodo estatico declarado en una tercera clase:
public class C
{
public static void Metodo(A parametro)
{
//
}
}
Ln estas condiciones, las dos llamadas del siguiente ejemplo son correctas:
Programacin Orientada a Objetos
Copyright 2004-200S, Ian Marteens
21
21
C.Metodo(new A());
C.Metodo(new B());
Ll siguiente diagrama muestra la razn de esta compatibilidad:

Como puede er, las instancias de las clases A y B tienen el mismo ormato en su parte inicial. Si el
mtodo Metodo lee y modiica el campo amarillo de las instancias de A, cuando le pasemos una
instancia de B encontrara tambin un campo amarillo en la misma posicin relatia al inicio de la
instancia.
Obsere, sin embargo, que esta compatibilidad unciona en una sola direccin. Imagine ahora un
segundo mtodo deinido en la clase C:
public class C
{
public static void OtroMetodo(B parametro)
{
//
}
}
Ln estas circunstancias, la segunda llamada sera incorrecta, y el compilador mismo se encargara de
senalar el error:
C.OtroMetodo(new B());
C.OtroMetodo(new A()); // Error de compilacin!
OtroMetoao, en eecto, espera recibir una instancia de la clase B, y es posible que necesite acceder al
campo erde de la clase B. Si pudiramos pasarle una instancia de A, ,qu ocurrira cuando el c-
digo intentase acceder al campo erde
lemos utilizado mtodos de una tercera clase para que el ejemplo uese lo suicientemente claro.
Ahora daremos una uelta adicional a la tuerca, obserando lo que ocurrira con los mtodos
declarados en la clase A:
public class A
{
// campos definidos por A y heredados por B
public A() { } // Un constructor sencillo para A.
public void Metodo()
{
// Manipulamos el campo amarillo.
}
}
public class B : A
{
// campos introducidos por B
public B() { } // Un constructor sencillo para B.
}
Sabemos, a ciencia cierta, que podemos aplicar, usando la jerga de la programacin orientada a
objetos, el mtodo Metodo a cualquier instancia de la clase A:
A a = new A();
a.Metodo();
c1ass c1ass c1ass c1ass A
c1ass c1ass c1ass c1ass 8 : A
Intuitive C
#

Copyright 2004-200S, Ian Marteens
22
22
La propia deinicin de herencia nos asegura que la clase B, por descender de A, debe heredar todos
los campos de esta clase, y tambin sus mtodos. Lsto tiene que ser posible:
B b = new B();
b.Metodo();
\ eectiamente, lo es. Slo que ahora nos es mas acil explicar por qu esta llamada es segura`: el
cdigo de Metodo, programado en la clase A, slo maneja campos deinidos en dicha clase, e ignora
cualquier campo adicional introducido por sus descendientes. Si le pasamos a Metodo una instancia
de B, el cdigo se encontrara con los mismos campos, en las mismas direcciones relatias al origen
de la instancia.
La reg|a de as|gnac|n po||mrf|ca




Htodos v|rtua|es



La Tab|a de Htodos V|rtua|es

,Recuerda aquella historia sobre los icheros de texto en el iejo 1urbo Pascal


Gracias a este ormato, podramos aeriguar si dos objetos pertenecen a la misma clase compa-
rando sus punteros a sus respectias tablas de mtodos irtuales.
Los ||m|tes de |a tecno|og|a

Open
kead
C1ose
Cdigo ejecutable
VMT
Programacin Orientada a Objetos
Copyright 2004-200S, Ian Marteens
23
23
Ln el ondo del problema esta el propio leitmoti de la descomposicin modular. Ln nuestro aan
de aislar al componente de su entorno, hacemos que ste desperdicie toda la rica inormacin
disponible sobre el sitio donde le ha tocado uncionar.


La 8|ngu|ar|dad
Un buen da, un programador, que puede ser usted o uno de sus hijos, creara una aplicacin que
nos dara respuestas tiles sobre temas mas o menos arbitrarios. Ahora podemos pedirle a una
calculadora que nos diga cual es la raz cuadrada de , pero a este programa le podremos preguntar
cmo trasladar un lobo, una oeja y una col a la otra orilla si en el bote slo cabemos yo y uno de
estos tres objetos. Oh, s, ahora tambin. pero necesitamos antes programarlo.
No hara alta que el programa sea perecto. 1ampoco lo somos ni usted ni yo, y nos pagan por
nuestro trabajo. Bastara con que las respuestas sean tiles. Normalmente, en pocas anteriores, un
aance de este tipo proocara una pequena tormenta de nueas ideas, y la disciplina recibira un
buen empujn que durara unos cuantos anos. Lsta ez, sin embargo, ocurrira algo muy dierente: la
aplicacin caera en manos de los expertos en el genoma humano.
Nuestro ADN es uno de los programas mas complejos que existe:
ha sido escrito por los diez mil y un hermanos de lanuman acopla-
dos a un bucle de retroalimentacin positia. No slo por la
complejidad de la propia codiicacin, sino por la propia maquina
que debe ejecutar` dicho cdigo. Los genes egostas controlan los
mandos de una maquina protenica, a la cual no le puedes pedir
despreocupadamente traeme un batido de chocolate`. porque te
puede traer el batido, s, pero tambin una tendencia a padecer
migranas o una incmoda mutacin en sala sea la parte. De modo
que el pequeno programa de mi historia sera recibido como regalo
del cielo de los monos por los bioingenieros de por entonces.
Uno de estos inestigadores encontrara la orma de mejorar
protenicamente el rendimiento intelectual. No bromeo. La
productiidad del trabajo en los Lstados Unidos es mucho mayor que la europea, y la dierencia
crece con los anos. Lxisten hiptesis que atribuyen la dierencia al auge de los psicoarmacos, y en
especial, al Prozac. Ls complicado demostrarlo, pero la hiptesis tiene su lgica: un trabajador o un
empresario deprimido aproecha peor su jornada. ,\ si uese posible llegar mucho mas lejos con
unos pequenos retoques en nuestra qumica cerebral Ll inestigador de marras lograra precisa-
mente eso. e iniciara un perodo de cambios explosios debido a la ineitable retroalimentacin
positia: programadores con cerebros mejorados` produciran aplicaciones mas inteligentes, que
seran utilizadas por ingenieros genticos cada ez mas inteligentes para mejorar el uncionamiento
de los programadores y de ellos mismos.
Ln un bree plazo de diez anos, y tras algunos sonoros
racasos, la lumanidad dispondra de una generacin de
autmatas moleculares programados mediante un proto-
colo estandar que se utilizaran en un procedimiento mdico
llamado La Mejora. Sera un protocolo estandar porque los
gobiernos sentiran paor ante la idea de perder el control
sobre la mejora gentica de la especie. Sera tambin un
proceso al que cada persona podra someterse oluntaria-
mente una sola ez en su ida, pues no slo reparara los
danos celulares ya existentes, sino que reprogramara mediante ciertas rutinas genmicas
estandarizadas el propio ADN del anitrin, por lo que los hijos de ste ya naceran mejorados`.
Intuitive C
#

Copyright 2004-200S, Ian Marteens
24
24
Las consecuencias de una mejora seran espectaculares: salud, longeidad extraordinaria, una mente
mas eiciente, sin eectos secundarios desagradables. e incluso una superior belleza sica. Si usted
o yo isemos un mejorado, probablemente lo conundiramos con un angel. Nuestra especie habra
ascendido entonces, sin sangre ni dolor, a un peldano superior.
. pero no toda nuestra especie. Siempre existiran personas que, por uno u otro motio, se negaran
a ser mejorados`. Lntre ellos se llamaran naturales`, pues los mejorados se apropiaran del tr-
mino humanos`. Viiran en pequenas reseras, aislados del resto de la sociedad, en condiciones
cada ez peor, y condenados a la extincin por el goteo constante de disidentes que querran
transormarse en humanos`. \ entonces.


Copyright 2004-200S, Ian Marteens
25

CLA8E8 Y OBJETO8

UNQUL C
4
OlRLCL UNA RICA paleta de tipos de datos al programador, no hay duda de que el
color de las clases es el aorito: tanto por ser los tipos mas usados, como porque las restantes
ariedades de tipos de datos son, estrictamente hablando, ariaciones sobre el concepto de clase.
lay incluso lenguajes, como Jaa, que slo orecen clases en su repertorio, no es un ejemplo a se-
guir, pero debemos tenerlo presente.
|dent|dad
Las clases de la plataorma .NL1 son tipos con semntica de asignacin por referencia. Lsto signi-
ica, en primer lugar, que una ariable o campo perteneciente al tipo, almacena un puntero a una
instancia del tipo, nunca directamente una de estas instancias.
,Consecuencias Unas cuantas, y entre ellas, stas:
lace alta una instruccin especial para crear instancias de estos tipos. Lsta operacin tiene un
coste asociado. Por s mismo, es un coste pequeno, pero hay que anadirle el coste de la poste-
rior deolucin de la memoria, cuando el objeto ya no es necesario. Veremos luego cmo los ti-
pos de estructuras pueden ayudar a eitar estos costes, en muchos casos.
Dos ariables dierentes pueden compartir un mismo objeto. Se puede modiicar el estado de
un mismo objeto por medio de ariables, o campos, sin relacin aparente.
N
O
T
A

Aunque seria correcto llamar tipos de referencia a las clases, esta terminologia podria inducir a error
en .NET. Para el Common Language Runtime, un tipo de referencia, hablando con propiedad, es el
equivalente de un puntero a otro tipo en lenguajes tradicionales. Aunque estos tipos no afloran
explicitamente en la especificacin de C
#
, son indispensables para implementar tcnicas como el
traspaso de parametros por referencia.

0u hay dentro de una c|ase
Una clase es un tipo compuesto: sire como contenedor y organizador para otras entidades deini-
das en su interior. Las clases en .NL1 pueden contener estos tipos de declaraciones:
Campos: ariables declaradas dentro de la clase. Ls costumbre prohibir el acceso directo a los
mismos desde uera del objeto.
Metodos: todo el cdigo ejecutable en un lenguaje orientado a objetos debe estar encerrado
dentro de mtodos, es decir, unciones deinidas dentro de una clase. Normalmente, estos mto-
dos actan sobre un objeto de la clase dentro de la cual han sido declarados. Para llamar uno de
estos mtodos se debe indicar la instancia sobre la cual queremos aplicar dicho mtodo, si no
especiicamos tal instancia, se asume el uso de la instancia implcita del mtodo donde se realiza
la llamada. Lsto implica, por supuesto, que tanto el mtodo llamado como el mtodo que con-
tiene la llamada pertenecen a la misma clase. en un sentido relajado, porque hay que tener en
cuenta la herencia de clases. Lo siento, pero es as de complicado. Para terminar de liarle la ida,
eremos dentro de nada que existen tambin mtodos y campos estaticos, que obedecen a un
conjunto de reglas ligeramente distintas.
Constructores: parecen mtodos, porque contienen cdigo ejecutable, pero slo pueden
usarse dentro de una expresin new, o usando una sintaxis especial, antes de que empiece a eje-
A
Intuitive C
#

Copyright 2004-200S, Ian Marteens
26
26
cutarse el cdigo de otro constructor. No es necesario adertir que los constructores construyen
instancias de objetos a partir de clases. y bueno, de estructuras. Ah, y si tiene alguna experien-
cia con otros lenguajes orientados a objetos, sepa que C
4
soporta algo que se parece mucho a
un destructor ,incluso se llaman destructores!, pero que no se parecen en nada a un destructor
tradicional.
Propiedades: campos con superpoderes. Simulan la lectura del alor de un campo por medio
de una uncin, y la escritura mediante otra. Lsto permite anadir eectos secundarios a las lectu-
ras y escrituras de miembros de la clase que aparentan ser campos, y es uno de los pilares de la
programacin mediante componentes.
Lventos: es el mecanismo preerido para el eno de notiicaciones entre componentes. Un
eento utiliza casi siempre un campo oculto para mantener una cadena de punteros a mtodos.
Cuando la clase que deine el eento lo considera apropiado, actia esa cadena de unciones
mediante el disparo` del eento. Microsot preiere que se hable de la eleacin` ,raising, del
eento. pero da igual.
Cuando declaramos un campo, lo normal es que se resere memoria para ste en cada una de las
instancias de la clase. Si le parece anormal la palabra normal` para hablar de estos campos, le per-
mito que tambin los llame campos de instancia. Podemos tambin usar el modiicador static en la
declaracin de campos, propiedades, eentos, mtodos e incluso constructores. Por ejemplo:
// Un campo de instancia
public string nombre;
// Un campo "esttico"
public static string prefijo;
La particularidad de los campos estaticos es que slo se resere memoria para cada declaracin una
ez por clase. Siguiendo las declaraciones anteriores, tendremos a nuestra disposicin un campo
nombre por cada instancia que creemos de la clase donde ha sido declarado. Sin embargo, slo
tendremos un campo prefijo, sin importar el nmero de instancias creadas. De hecho, tendremos
un campo prefijo incluso antes de que creemos instancia alguna!
Un mtodo normal`, quiero decir, de instancia, acta sobre el estado de los campos y propiedades
de una instancia que debemos suministrarle, ya sea de orma explcita o implcita. Cuando declara-
mos un mtodo esttico, ste no tiene acceso a los campos de instancia, como era de esperar, sino
solamente a los campos estaticos declarados dentro de la clase. \ tampoco necesitamos pasarle una
instancia al mtodo: nos basta con indicar, implcita o explcitamente, el nombre de la clase donde
ha sido declarado el mtodo.
Lsto hace que podamos decir que, al declarar una clase con miembros estaticos, es como si
estuisemos declarando dos clases normales`, sin miembros estaticos:

Una de estas clases imaginarias slo contendra las declaraciones de instancias. La otra clase
imaginaria implcita contendra todas las declaraciones que utilizan el modiicar static, con la
peculiaridad de que de esta segunda clase imaginaria slo tendramos una instancia a nuestra
disposicin, que siempre estara creada. mas o menos. No es una metaora para ser tomada muy
en serio, pero si el CLR no oreciese soporte explcito para miembros estaticos, un compilador de
Clase
estado de instancias estado estatico
Clases y objetos
Copyright 2004-200S, Ian Marteens
2
27
un lenguaje como C
4
podra simularlos mediante este truco de las dos clases, de orma oculta para
el programador.
V|s|b|||dad
Cada miembro declarado dentro de una clase en C
4
debe ir acompanado de un modiicador que
indique su nivel de acceso: desde qu zonas de un proyecto puede ser utilizado dicho miembro. Ll
objetio de este control de acceso es ocultar la mayor cantidad de detalles de la ista de los
potenciales usuarios de la clase. No se trata de esconder ideas o secretos tcnicos, sino de simplii-
car la orma de usar la clase y eitar dependencias innecesarias. Ln realidad, para un usuario con los
permisos necesarios no hay secretos dentro de un ensamblado .NL1. Si esto uese un problema,
debera utilizar tcnicas adicionales como el ouscamiento del cdigo intermedio.
Ll modelo de control de acceso en C
4
esta determinado en su mayor parte por la implementacin
del CLR y orece cinco nieles. 1res de ellos tienen mucho en comn con el modelo de acceso de
lenguajes orientados a objetos clasicos como C--:
public
Los miembros declarados public pueden ser utilizados desde cualquier parte del proyecto, ya
sea en el mismo ensamblado donde se esta declarando la clase, como desde un ensamblado
dierente.
private
Por el contrario, los miembros private slo pueden ser utilizados por la propia clase que los de-
clara.
protected
Los miembros protected pueden ser utilizados por la propia clase que los declara, y ademas,
por cualquier clase que descienda de la clase original, no importa en qu ensamblado se encuen-
tre.
lay otros dos nieles de acceso que tienen que er con los ensamblados:
internal
Un miembro declarado internal slo puede ser utilizado por clases ubicadas dentro del mismo
ensamblado que la clase original. Ls una ersin menos estricta de private.
protected internal
Un miembro declarado protected internal puede ser utilizado desde cualquier clase ubicada
dentro del mismo ensamblado. luera de ste, slo tendran acceso al miembro las clases deria-
das de la clase original.
1enga presente que estos cinco nieles se reieren a miembros declarados dentro de una clase,
incluyendo otros tipos anidados. Ln cambio, para los tipos declarados directamente dentro de un
espacio de nombre, es decir, para aquellos tipos que no son tipos anidados, slo hay dos nieles
aplicables:
internal
Ll tipo slo puede ser usado dentro del ensamblado.
public
Barra libre!
Se asume que todos los miembros declarados dentro de un tipo de interaz son pblicos, por lo que
no se debe usar un modiicador de acceso en la declaracin de estos. Debe saber tambin que algu-
nos miembros especiales son considerados automaticamente como priados. Ln esta categora se
encuentran los constructores de tipos, tambin llamados constructores estticos, los mtodos usados
para la implementacin explcita de tipos de interaz y los finalizadores ,p.2,.
Intuitive C
#

Copyright 2004-200S, Ian Marteens
28
2S
6onstructores
Aunque sea usted un experto en C--, Jaa o cualquier otro lenguaje orientado a objetos, no le
endra mal echar un istazo a cmo unciona la inicializacin de instancias en C
4
, pues existen en
este lenguaje algunas particularidades dignas de mencin. Aqu nos ocuparemos solamente de los
tipos con semantica de asignacin de reerencia, en otra palabra, de las clases. Las estructuras tienen
otro conjunto bastante embrollado de reglas que examinaremos a su debido momento.
No hay grandes cambios en la sintaxis, comparando sobre todo con Jaa. Los constructores se
declaran usando el mismo nombre de la clase, y no tienen un tipo de retorno asociado. La exigencia
sobre la coincidencia del nombre trae como consecuencia que dos constructores dierentes tengan
que distinguirse obligatoriamente por el nmero o tipo de sus parametros. Podramos llamar a esta
la primera regla sobre constructores, y a acompanada de polmica, porque releja una limitacin
impuesta al propio Common Language Runtime. Los lenguajes como C
4
, C-- y Jaa no tienen
problemas, pero otros lenguajes, como Delphi o Liel, permiten deinir constructores con nom-
bres dierentes y tienen que recurrir a trucos sucios para implementarlos.
N
O
T
A

cEs tan importante poder dar nombres a los constructores? Tomemos como ejemplo una clase que
implemente numeros complejos. Hay una forma evidente de crear un numero complejo: pasar al
constructor su parte real y su parte imaginaria. Pero tambin podriamos desear un constructor que
recibiese los componentes del complejo en coordenadas polares. Ambos constructores tendrian
entonces idnticos prototipos. En C
#
, cuando se da este problema, la unica solucin consiste en
implementar uno de los constructores como un mtodo estatico.

Segunda regla: toda clase debe tener al menos un constructor. Si un programador no deine
constructores para una clase, entonces el compilador sintetiza uno por su cuenta. Lse constructor
sinttico no tiene parametros.
1ercera regla: los constructores no se heredan. Bueno, no exactamente: usted los hereda, pero no
puede mostrarselos a las isitas. Solamente puede usar un constructor heredado para inocarlo
antes del cdigo uente de sus propios constructores. Si una clase no declara sus propios
constructores, entonces no tiene constructores, a pesar de todos los que haya podido heredar de su
clase base. Por lo tanto, el compilador creara un constructor sinttico, de acuerdo a la segunda
regla.
Cuarta regla: todo constructor, antes de comenzar a ejecutar sus instrucciones, debe ejecutar una
llamada a un constructor heredado o a otro de los constructores deinidos en la misma clase. La
llamada al constructor heredado puede ser explcita, o implcita. Si es implcita, se asume que se esta
llamando a un constructor heredado que no tiene parametros. Si ese constructor heredado no
existe, entonces tenemos un error. Suponga que hemos deinido una clase como la siguiente:
public class A
{
public A(int i) { }

}
,Ve alguna llamada al constructor heredado Se supone entonces que el constructor anterior es
equialente al siguiente:
public A(int i) : base() { }
Ln este caso, no hay problemas: la clase ancestro de A es System.Object, y esta tiene un constructor
sin parametros. Obsere la sintaxis que se utiliza para ejecutar el constructor heredado, a imitacin
de Jaa. Sobre todo, tome nota de que la llamada al constructor heredado se escribe antes del blo-
que de instrucciones del propio constructor.
La clase que s tendra problemas sera la siguiente:
public class B : A
{
public B() { }
Clases y objetos
Copyright 2004-200S, Ian Marteens
29
29
public B(int i): base(i) { }
}
Ll error se producira en el primer constructor, porque la clase ancestro no tiene constructores sin
parametros. Obsere, sin embargo, que esto s sera aceptado:
public B() : this(0) { }
Ln este caso, this se ha utilizado para ejecutar el constructor hermano`, declarado en la misma
clase B. La sintaxis es la misma que hemos isto para las llamadas mediante base: slo cambia la
palabra clae, y por supuesto, su signiicado. Lxisten restricciones en las expresiones que se pueden
pasar como parametros a un constructor heredado o hermano. No podemos, por ejemplo, utilizar
campos de instancia en estas llamadas. Se supone que, en este punto de la ejecucin del constructor,
dichos campos an no estan correctamente inicializados.
|n|c|a||zac|n de campos
A pesar de la aparente sencillez de la construccin de instancias, hay que tener en cuenta dos
hechos al programar un constructor:
1 1ratandose de clases, antes de la llamada al constructor, se produce la resera de memoria dina-
mica para la instancia. Como parte de esta resera de memoria, todos los campos de la instancia
se inicializan automaticamente con ceros.
Lsto es bueno. Incluso si no hacemos nada en un constructor, podemos contar con que todos los
campos de la instancia estaran inicializados con ceros.
2 Ln la declaracin de un campo, se puede anadir una expresin de inicializacin opcional. Antes
de que comience a ejecutarse la llamada implcita o explcita al constructor heredado, se ejecu-
tan todas las asignaciones debidas a expresiones de inicializacin de campos de instancia.
Lste comportamiento suele pillar por sorpresa incluso a iejos lobos de mar. Antes de seguir, ob-
sere que lo anterior no se aplica a los constructores que llaman a un constructor hermano. De
todos modos, en alguna parte de la cadena de llamada a constructores, habra uno que llamara a un
constructor heredado, y entonces tendra lugar la mencionada paradoja.
Veamos un ejemplo:
public class A
{
public A()
{
Console.WriteLine("A");
}
}
public class B : A
{
private int i = 1234;
public B() : base()
{
Console.WriteLine("B");
}
}
Imagine que creamos una instancia de la clase B. ,Ln qu orden cree usted que se ejecutan las dos
escrituras en la consola y la asignacin al campo entero deinido en B Valo con sus propios ojos:
.method public hidebysig specialname rtspecialname instance void .ctor()
cil managed
{
.maxstack 8
L_0000: ldarg.0
L_0001: ldc.i4 1234
Intuitive C
#

Copyright 2004-200S, Ian Marteens
30
30
L_0006: stfld int32 ConsoleApplication1.B::i
L_000b: ldarg.0
L_000c: call instance void ConsoleApplication1.A::.ctor()
L_0011: ldstr "B"
L_0016: call void [mscorlib]System.Console::WriteLine(string)
L_001b: ret
}
Lste es el cdigo IL generado para el constructor de B, obtenido mediante Reflector. Como puede
er, primero se asigna la ariable entera, luego se llama al constructor heredado y inalmente se
ejecutan las instrucciones que an en el bloque del constructor de B.
No tiene la menor importancia`, puedo escucharle desde aqu. Ln deinitia, no se puede acceder
al contenido del campo i desde el constructor de la clase A`. Bien, si alguien dice lo anterior,
apuesto que es un programador muy ersado en C-- aunque conoce algo menos de Jaa y Delphi.
Lectiamente, si esto uese C--, sera imposible que la clase A estirase tanto su brazo. Sin em-
bargo, tanto C
4
, como Jaa y Delphi, lo permiten. Obsere:
public class A
{
public A()
{
Console.WriteLine("A: i = " + LecturaFutura());
}
protected virtual int LecturaFutura() { return 0; }
}
public class B : A
{
private int i = 1234;
public B() : base()
{
Console.WriteLine("B");
}
protected override int LecturaFutura() { return i; }
}
,Qu aparecera escrito en la pared si creamos una instancia de B ,Acaso half dollar, half dollar, one
penny and two bits
Si hacemos el experimento con C--, con los debidos cambios sintacticos, la llamada a Lectura-
Futura desde el constructor de A deolera cero.
Ln Delphi, Jaa y C
4
, LecturaFutura siempre deolera 1234.
C--, a dierencia del resto de la banda, no limpia con ceros sus instancias antes de ejecutar un
constructor. Si C-- permitiese que un constructor ejecutase una uncin irtual redeinida en un
descendiente remoto, se encontrara con campos de alor impredecible. LecturaFutura ha deuelto
1234 en C
4
porque el campo se ha inicializado antes de la llamada al constructor, pero incluso si no
existiese la expresin de inicializacin, LecturaFutura deolera un predecible cero, gracias a que el
campo i se habra inicializado automaticamente con ceros al reserar memoria para la instancia.
Para eitar la situacin antes descrita, C-- modiica la semantica de las llamadas a mtodos irtua-
les que se realizan dentro de un constructor: mientras se ejecuta el cdigo del constructor deinido
en A, la instancia se comporta como si slo` uese una instancia de la clase A. Ln concreto, los
mtodos irtuales que se ejecuten desde el constructor de A utilizaran la ersin mas cercana que
no sea posterior a la clase A.
La estrategia de C-- es robusta, coherente y, por lo tanto, correcta. Pero restringe bastante lo que
podemos hacer dentro de un constructor, y es complicado predecir, con una jerarqua compleja de
clases, el comportamiento de subsistemas de la clase basados en mtodos irtuales. La ruta seguida
Clases y objetos
Copyright 2004-200S, Ian Marteens
31
31
por Delphi, Jaa y C
4
es quizas menos elegante desde el punto de ista terico, pero es correcta
gracias a la inicializacin automatica de instancias, y al inal, resulta ser mas potente que la de C--.
Volamos a las inicializaciones de campos: esta claro que es el compilador quien se encarga de
recopilar todas las asignaciones a campos de instancias para crear el cdigo que se ejecuta antes de
la ineitable llamada al constructor heredado que corresponda. Ln el caso en que no existen
constructores en una clase, el compilador sintetiza uno recopilando las asignaciones a campos de
instancia y anadiendo al inal una llamada al constructor heredado que no tiene parametros. Lsta
recopilacin de expresiones de inicializacin de campos no tiene lugar para los constructores que
llaman a constructores hermanos: el constructor hermano se encargara, directa o indirectamente, de
la inicializacin de los campos.
Para demostrarle que los campos se inicializan antes de la llamada al constructor heredado, preer
presentarle el cdigo intermedio extrado mediante Reflector. Pero resulta que existe otra orma de
demostracin, y que la tcnica necesaria puede serle til en algn otro caso. Obsere:
public class A
{
public A()
{
Console.WriteLine("A");
}
}
public class B : A
{
private int i = Devuelve1234();
public B() : base()
{
Console.WriteLine("B");
}
private static int Devuelve1234()
{
Console.WriteLine("Inicializando campos");
return 1234;
}
}
Ln este ejemplo, para inicializar un campo de instancia, hemos utilizado un mtodo estatico. C
4
nos
prohbe usar mtodos de instancia para inicializar campos, pero no pone objeciones a los mtodos
estaticos. \ un mtodo estatico puede proocar eectos secundarios interesantes. aunque nunca
sobre la instancia que se esta construyendo. Por ejemplo, se nos podra ocurrir la malada idea de
anadir un parametro de tipo B al mtodo estatico, con la inalidad de zarandear un poco la instancia
de B:
private static int Devuelve1234(B instancia)
{

}
Pero el compilador no permitira entonces pasar this a Devuelve1234 en la expresin de inicializa-
cin, con lo el peligro desaparece.
E| constructor estt|co
Como ya sabe, cualquier clase encierra dos almas en su interior: aquella que se traduce en instancias
y eso que podramos llamar el alma estatica`. Poda haberme ahorrado el misticismo, pero dicho
as, suena mas entretenido. Lo que quiero decir es que podemos deinir campos estaticos, mtodos
estaticos, propiedades estaticas e incluso eentos estaticos. ,Podremos entonces deinir constructo-
res estaticos Claro que podemos, aunque estos constructores estaticos tendran sus obias y manas.
Intuitive C
#

Copyright 2004-200S, Ian Marteens
32
32
La responsabilidad de un constructor estatico sera inicializar la parte estatica` de la clase, en otras
palabras, los campos estaticos de la misma. La dierencia respecto a un constructor de instancias es
que el constructor estatico nunca es ejecutado explcitamente. Una consecuencia inmediata de esto
es que el constructor estatico no puede tener parametros, y una consecuencia indirecta es que slo
puede declararse un constructor estatico, como maximo, para cada clase.
Otra peculiaridad del constructor estatico es la orma en que se declara:
public class Ejemplo
{
public static double Sqrt2;
// Este es un constructor esttico!
static Ejemplo()
{
Sqrt2 = Math.Sqrt(2.0);
}
}
Como era de esperar, el constructor estatico se declara con la palabra clae static, pero ademas, no
admite que se le asocie explcitamente un modiicador de niel de acceso.
Al igual que ocurre con los campos de instancia, los campos estaticos puede tambin inicializarse in
situ, sin esperar al constructor estatico, y estas inicializaciones se ejecutan antes del cdigo del cons-
tructor:
public class Ejemplo
{
public static double Sqrt3 = Math.Sqrt(3.0);
public static double Sqrt2;
static Ejemplo()
{
Sqrt2 = Math.Sqrt(2.0);
}
}
Aqu, sin embargo, debo adertirle con toda seriedad:
No declare constructores estaticos, al menos mientras pueda eitarlo! Utilice, dentro de lo
posible, expresiones de inicializacin para los campos estaticos.
Para explicar lo que hay en juego, hay que echar mano nueamente de Reflector. Lsta es la cabecera,
en lenguaje intermedio, de la declaracin de una clase que no tiene constructor estatico:
.class public auto ansi beforefieldinit A extends object
Si anadimos un constructor estatico, la declaracin cambia:
.class public auto ansi A extends object
la desaparecido cierto misterioso modiicador beforefieldinit. y el acceso a la clase se ha ralenti-
zado al mismo tiempo. Ll modiicador controla el momento en que se inicializan los campos estati-
cos de la clase. Mientras no haya un constructor estatico explcito, el entorno de ejecucin se siente
con libertad de inicializar esas ariables en cualquier momento, como por ejemplo, al arrancar la
aplicacin. Destaco lo de explcito`, porque si existen inicializadores de campos estaticos, el
compilador sintetizara un constructor estatico, pero tendra la misma libertad de elegir el momento
en que tiene lugar la inicializacin. Lso es lo que signiica beforefieldinit: que la plataorma decidira
cuando le iene bien inicializar la parte estatica de la clase.
Si por el contrario, hay un constructor estatico explcito, la especiicacin ormal de C
4
deja muy
claro en qu momento debe ejecutarse. Debe hacerlo justo antes de que ocurra la primera de estas
dos acciones:
Cuando se cree la primera instancia de la clase.
Clases y objetos
Copyright 2004-200S, Ian Marteens
33
33
Cuando alguien intente acceder a campos estaticos de la clase.
La nica orma de implementar este comportamiento es comprobar si la clase ha sido inicializada
cada ez que se accede a un campo estatico de la clase y cada ez que se ejecutan sus constructores
de instancias. ,Resultado Menos elocidad al trabajar con estos recursos de la clase.




Copyright 2004-200S, Ian Marteens
34
- -- -
TPO8 DE NTERFAZ

N LA MISMA LPOCA LN LA que C-- aianzaba su dominio en la programacin y liquidaba los
ltimos ocos de resistencia, tena lugar una callada reolucin subterranea que, con el trans-
curso del tiempo, signiicara el in de la hegemona de C--.
herenc|a e |nterfaces
Puede que el truco mas popular de la chistera de la Programacin Orientada a Objetos sea la heren-
cia de clases. Cuando declaramos una clase e indicamos que desciende de otra, hacemos que nuestra
clase herede` todas las declaraciones de la clase ancestro, con la posibilidad de anadir nueos
miembros y modiicar algunos de los heredados. Se dice en estos casos que heredamos tanto la
estructura como el comportamiento.
Ln un momento dado, los tericos de la programacin pensaron que era deseable que una clase
pudiese heredar de mas de una clase base. Pero en la practica, los lenguajes que soportaban herencia
mltiple, como el popular C--, demostraron que:
Muchas de las tcnicas necesarias para eitar conlictos de nombres eran, o demasiado comple-
jas, o demasiado limitantes.
Como consecuencia, muchos programadores ni siquiera se aenturaban a usarla.
Una buena parte de los osados cometa errores de dicil diagnstico.
Lra un erdadero dolor en las rtebras implementar un compilador con soporte para herencia
mltiple.
Da la casualidad que el principal caso practico en el que merece la pena utilizar herencia mlti-
ple se puede resoler con otra tcnica que enseguida eremos.
Ll misterioso caso tiene que er con las llamadas clases abstractas. Una clase puede declarar que
uno o mas de sus mtodos es abstracto, para que sea implementado en alguna clase deriada. Lsta
situacin es relatiamente recuente cuando se disenan jerarquas de herencia. Por ejemplo, para
programar un sintetizador de sonidos por sotware ,estan de moda, por cierto,, se puede partir de
una clase base llamada Oscilador, de la que deriaremos luego arias clases que proporcionen el
comportamiento de distintas ormas de onda. Ln un diagrama, representaramos tal situacin de
esta manera:

Ll triangulo de la imagen no es cuestin esttica: se utiliza en UML para representar las relaciones
de herencia. Ln cdigo, tendramos esto, si nos ahorramos detalles innecesarios:
public abstract class Oscilador
{
|
Osc11ado
11angu1a Cuadada D1enfes51ea
Tipos de interfaz
Copyright 2004-200S, Ian Marteens
35
35
public abstract double Voltaje(double tiempo);
// Otros miembros, no necesariamente abstractos
}
public class Triangular
{
public override double Voltaje(double tiempo)
{
// Implementacin de la onda triangular
}
// Otros miembros
}
Podemos llegar al extremo y declarar alguna que otra clase solamente con mtodos, propiedades y
eentos abstractos, no tiene sentido hablar de campos abstractos, y .NL1 no soporta constructores
abstractos. Ll caso especial al que antes me reera consiste en declarar una clase que herede de una
sola clase concreta y de una o mas clases abstractas. ,Qu hay de especial en este caso
La estructura sica se hereda de la nica clase concreta. Lsto simpliica mucho la implementa-
cin de compiladores.
Los conlictos y ambigedades, aunque no desaparecen del todo, se reducen al mnimo posible.
Desde el punto de ista del sistema de tipos, la nuea clase sigue siendo compatible para la
asignacin respecto a todas sus clases bases.
Para eitar la herencia mltiple y seguir soportando este til caso especial, los lenguajes como Jaa y
C
4
permiten el uso de tipos de interfaz. Lstos tipos son muy similares a las clases, pero slo permi-
ten declaraciones de mtodos, propiedades y eentos. \ muy importante: para ninguno de estos
miembros se puede suministrar una implementacin. Por ejemplo, en ez de declarar una clase
abstracta para deinir las habilidades de un oscilador digital, podramos declarar una interaz:
public interface IOscilador
{
double Voltaje(double tiempo);
}
Los tipos de interaz se interpretan como especiicacin de una uncionalidad determinada. La
interaz IOscilador es muy sencilla: describe elementos que pueden suministrar un oltaje que ara
con el tiempo. Ahora tenemos que establecer las reglas que deben cumplir C
4
y la mayora de los
lenguajes .NL1:
1 1oda clase debe heredar exactamente de una sola clase.
2 La excepcin: la clase System.Object, en C
4
, object a secas.
3 Una clase puede omitir la clase base en su declaracin, y en tal caso se asume que desciende de
System.Object.
4 Por el contrario, una clase puede implementar cero o mas tipos de interaz.
le resaltado heredar e implementar porque esa es la terminologa oicial. Implementar signiica que
una clase menciona el tipo de interaz en su declaracin, con lo cual se obliga a implementar los
mtodos, propiedades y eentos declarados en la interaz.
|mp|ementac|n de t|pos de |nterfaz
lay dos tcnicas para implementar un tipo de interaz en una clase. La primera, llamada
implementacin implcita, consiste en declarar mtodos y propiedades en la clase cuyos prototipos
coincidan con los de los mtodos y propiedades del tipo de interaz:
public class Triangular : Chip, IOscilador, IConfigurable
{
public double Voltaje(double tiempo)
{
// Implementacin de la onda triangular
Intuitive C
#

Copyright 2004-200S, Ian Marteens
36
36
}
// Otros miembros
}
Ll primer tipo de la lista de herencia debe ser una clase. Ln este caso, me he inentado una clase a la
que he llamado Chip, para mostrar que ahora tenemos mas libertad al elegir una clase base. A
continuacin se menciona IOscilador, y eectiamente: la clase Triangular implementa su mtodo
Voltaje. Obsere que he mencionado otro tipo de interaz, IConfigurable: esto podra indicar que
nuestra clase puede leer y guardar su coniguracin desde algn tipo de medio persistente, como un
ichero XML, el registro de \indows o incluso una base de datos. Al mtodo Voltaje se le exige que
sea pblico. Internamente, el compilador anade los modiicadores virtual y sealed: se trata de un
requisito exigido por el CLR.
La otra orma de implementacin se conoce como implementacin explcita:
double IOscilador.Voltaje(double tiempo)
{
// Implementacin de la onda triangular
}
Dos detalles han cambiado en la declaracin del mtodo:
1 Ll nombre del mtodo, que ahora aparece con el nombre del tipo de interaz como preijo.
2 No se ha mencionado el modiicador de acceso al declarar el mtodo.
De esta manera, el mtodo no puede ser ejecutado directamente a tras de una ariable de tipo
Triangular. Ln primer lugar, porque el nombre del mtodo contiene un punto, que no se permite en
un identiicador, y en segundo lugar, porque el mtodo se genera con niel de acceso priado.
La principal entaja de la implementacin explcita es que permite resoler potenciales conlictos de
nombres proocados por interaces dierentes que declaran mtodos con el mismo nombre. Supon-
gamos que hay dos tipos de interaz llamados I1 y I2, y que ambos exigen la presencia de un m-
todo al que llamaremos M. Digamos entonces que la clase C implementa ambas interaces. Si la
implementacin es implcita, el mtodo M declarado en C implementara a la ez los mtodos M de
los dos tipos de interaz. Puede que esto sea lo que deseemos. Si no es as, entonces debemos recu-
rrir a la implementacin explcita, declarando mtodos I1.M e I2.M en la clase C.
Ln determinadas situaciones, nos puede interesar la implementacin explcita, por razones mas
sutiles, de tipo metodolgico. Ls mas arriesgado depender del prototipo de una clase que de un tipo
de interaz: con la clase, se nos pueden escapar dependencias diciles de detectar. Con recuencia,
se disenan sistemas alrededor de un tipo de interaz que es implementado por toda una lista de
clases. Si estas clases implementan la interaz de manera explcita, no existe el riesgo de que, por
descuido, se nos escape una llamada a uno de los mtodos de la interaz. pero realizada a tras
de una reerencia a una clase. Slo tenemos que tener en cuenta que, con las implementaciones
explcitas, no podemos tampoco llamar a los mtodos as implementados desde la propia clase:
public class Triangular : Chip, IOscilador, IConfigurable
{
double IOscilador.Voltaje(double tiempo)
{
// Implementacin de la onda triangular
}
public void Test()
{
// Cmo ejecutar el mtodo anterior aqu?
}
}
Ln estos casos, la nica solucin consiste en realizar una conersin de tipos:
Tipos de interfaz
Copyright 2004-200S, Ian Marteens
3
37
public void Test()
{
double d = ((IOscilador)this).Voltaje(0.0);
Console.WriteLine(d);
}
Conertimos una reerencia a la clase Triangular en una reerencia a la interaz IOscilador. Men-
ciono esta posibilidad porque he comprobado que el compilador de C
4
realiza esta conersin ei-
cientemente. De hecho, no genera cdigo alguno para la conersin! Lsto ocurre porque el CLR
permite directamente la llamada a Voltaje sobre una reerencia a Triangular.
|nterfaces y po||morf|smo
Los tipos de interaz aumentan la complejidad y potencia de las reglas de asignacin polimrica.
Suponga que tenemos la siguiente asignacin.
x = y;
Ahora tenemos que contemplar estas dos posibilidades adicionales:
1 Que el tipo de x sea una interaz, y que el tipo de y sea una clase que implemente dicha interaz.
2 Que el tipo de x sea una interaz, y que el tipo de y sea un tipo de interaz deriado del mismo.
Ln principio, parece acil decidir a simple ista si se cumple uno de estos casos, pero en la practica
puede resultar complicado. si el compilador no nos echase una mano, claro. Ll criterio se com-
plica porque implementar y derivar son aceptadas tanto en sus ariantes directas como indirectas.
Por ejemplo, podemos tener una expresin cuyo tipo sea la clase A, para la cual se cumple:
La interaz IDisposable se utiliz para deriar la interaz IVentana, que ue implementada por la
clase Ventana, que engendr a VentanaRedonda, que engendr a Claraboya.
\ claro, podemos asignar una claraboya en una ariable de tipo IDisposable sin que salten las alar-
mas. Ll siguiente esquema muestra un ejemplo no muy retorcido de cmo pueden complicarse las
relaciones entre clases e interaces:

Descirar esta cabala es un poco mas sencillo que leer el uturo en las hojas de t. Como clases e
interaces son dierentes especies, he dibujado de azul las primeras, y las segundas de erde. As se
nota a simple ista que las clases se agrupan en una sola rama, como consecuencia de la herencia
simple. La clase que analizamos es la de la esquina superior derecha: no se deje enganar por el sen-
tido de las lechas, pues se trata slo de un conenio de los diagramas de clases. Nuestra clase esta a
dos pasos de distancia de System.Object, la clase base comn en .NL1, e implementa directamente
dos tipos de interaz. Su clase base implementa una sola interaz. La mayor complicacin iene dada
por una de las interaces, que combina dos interaces bases. Ademas, aunque no se puede er en el
diagrama, algunas de estas interaces pueden aparecer en mas de un nodo. Cuando un compilador
desea aeriguar si dos tipos son compatibles para la asignacin, debe explorar un arbol como el que
acabo de mostrar.
C
C
C
l l
l l
l
l
Intuitive C
#

Copyright 2004-200S, Ian Marteens
38
3S
Htodos de extens|n
Antes de explicar qu es un mtodo de extensin ,extension method,, debo aclararle que este recurso
no esta necesariamente inculado a los tipos de interaz. Lo que amos a hacer en esta seccin gra-
cias a dichos mtodos es posible repetirlo con clases y estructuras. No obstante, he decidido expli-
car los mtodos de extensin en el captulo dedicado a las interaces porque es al usarlo con estos
tipos que los mtodos de extensin cobran mas sentido. y presentan menos problemas.
Imagine que acabamos de declarar un tipo de interaz como ste:
public interface IStack
{
bool IsEmpty { get; }
bool Top { get; }
void Push(object value);
void Pop();
}
Quien desee soportar este contrato, tendra que programar dos mtodos y dos propiedades con los
prototipos apropiados, ya sea en una nuea clase o en una estructura. A partir de este ncleo m-
nimo y consistente de uncionalidad, sin embargo, podramos deinir automaticamente otros mto-
dos y propiedades potencialmente tiles. Por ejemplo, ,qu tal si el usuario de IStack pudiese usar
un mtodo que eliminase todos los elementos de una pila, o que insertase arios objetos con una
sola llamada Si incluysemos estos mtodos en la declaracin de IStack, estaramos jugandole una
mala pasada al uturo implementador de la interaz, obligandole a implementar dos mtodos
adicionales sin ayuda alguna por parte nuestra. Lo peor de todo es que cada implementacin de
IStack tendra su propia ersin de estos mtodos adicionales, an cuando hagan exactamente lo
mismo y de la misma manera.
La solucin clasica consiste en implementar estos mtodos en una clase o estructura auxiliar, como
la siguiente:
public static class StackExt
{
public static void PushList(IStack stack, params object[] list)
{
foreach (object item in list)
stack.Push(item);
}
public static void Clear(IStack stack)
{
while (!stack.IsEmpty)
stack.Pop();
}
}
De momento, lo de declarar la clase con static es un aparente tecnicismo: los mtodos an a
modiicar una pila, y no necesitan inormacin de instancia adicional. Lsta decisin nos obliga a
declarar tambin los dos mtodos como estaticos, pero eso es lo que deseabamos!
Supongamos ahora que ya tenemos una clase StackImpl que implementa la interaz IStack. Con la
ayuda de la clase auxiliar, podemos ya ejecutar instrucciones como las siguientes:
IStack stack = new StackImpl();
StackExt.PushList(stack, "all", "mimsy", "were", "the", "borogoves");
//
StackExt.Clear(stack);
,No le deja cierto poso de insatisaccin el listado anterior Lstamos obligando al usuario de la
interaz a recordar un nombre de tipo adicional, StackExt, que resulta ser bastante eo, para rematar.
La sintaxis de la llamada es tambin mas larga y engorrosa, y diiculta la escritura de object paths, o
cadenas de llamadas, de cierta complejidad. ,Qu tal si pudiramos reescribir el cdigo anterior de
la siguiente manera
Tipos de interfaz
Copyright 2004-200S, Ian Marteens
39
39
IStack stack = new StackImpl();
stack.PushList("and", "the", "mome", "raths", "outgrabe");
//
stack.Clear();
Lsto es posible en C
4
3.0 mediante un truco bastante eo, para mi gusto. lay que declarar los mto-
dos adicionales dentro de una clase estatica, como mtodos tambin estaticos, y tenemos que
decorar` el primer parametro de cada uno de ellos mediante el modiicador this:
public static class StackExt
{
public static void PushList(this IStack stack, params object[] list)
{
foreach (object item in list)
stack.Push(item);
}
public static void Clear(this IStack stack)
{
while (!stack.IsEmpty)
stack.Pop();
}
}
Lo bueno del truco es que, aunque en mi ejemplo original se aplic sobre el tipo de interaz, tam-
bin podramos aplicarlo sobre cualquier clase que implementase dicha interaz:
StackImpl stack = new StackImpl();
stack.PushList("and", "the", "mome", "raths", "outgrabe");
//
stack.Clear();
Ahora parece que tambin existe un PushList y un Clear en la clase StackImpl. Lo que ocurre, en
realidad, es que la regla de asignacin polimrica permite usar un StackImpl donde quiera que se
pueda usar un IStack.
La cara oscura de |os mtodos de extens|n
Los mtodos de extensin simulan la existencia de mtodos en un tipo arbitrario: mtodos que no
estaban en la mente del programador al disenarlo. Ls decir, no slo etievaev interaces, sino tam-
bin clases, estructuras e incluso tipos enumeratios. Por ejemplo, tomemos el siguiente tipo
enumeratio:
public enum Estados
{
Slido, Lquido, Gaseoso, Plasma
}
Con un poco de mana podemos simular que Estados tiene un mtodo que deuele el sucesor` de
un estado, retornando al primero tras el ltimo:
public static class EstadosExtender
{
public static Estados Succ(this Estados estado)
{
return (Estados)(((int)estado + 1) % (int)Estados.Plasma);
}
}
Antes dije que mi sentido de la esttica se e oendido por estos mtodos, y ahora me explico: tiene
sentido extender` una interaz, pero ,ocurre lo mismo con una clase ,Por qu no deinimos
directamente el mtodo deseado dentro de la clase ,Porque es una clase creada por otros, cuyo
cdigo uente no podemos o debemos modiicar Ln tal caso, hay cierta justiicacin en la comodi-
dad que orece el recurso, pero enseguida eremos el peligro que se oculta tras tanta amabilidad y
coneniencia.
Intuitive C
#

Copyright 2004-200S, Ian Marteens
40
40
Uno de los puntos negros de los mtodos de extensin es la orma en la que se actian`. Suponga-
mos que el ensamblado A deine una clase C, y que el ensamblado B contiene una clase con mto-
dos que extienden C, deinidos dentro de un espacio de nombres N. Para poder usar estos mtodos,
hay que incluir una clausula using que haga reerencia al espacio de nombres donde reside la clase
extensora, en este caso:
using N;
Lste mecanismo tan absurdo hace que sea muy complicado detectar si determinado tipo esta siendo
extendido por alguna clase cuya existencia desconocemos.
Por otra parte, si la clase extendida tiene un mtodo con el mismo nombre que un mtodo de
extensin, el mtodo de instancia original tiene prioridad. Por ejemplo, no tiene sentido deinir un
mtodo de extensin ToString porque, sea cual sea el tipo extendido, ya tendra un ToString predei-
nido, que ocultara cualquier mtodo de extensin con el mismo nombre.
\ es aqu donde el peligro acecha:
1 Usted tiene entre manos una clase a la que le alta` un mtodo llamado Oops.
2 Declara entonces una clase auxiliar con un mtodo de extensin Oops, y llena su cdigo uente
de llamadas disrazadas a Oops.
3 Un buen da, el programador de la clase original descubre lo bien que estara tener un mtodo
Oops en su clase, y lo anade.
4 De repente, todas las llamadas a Oops en su cdigo uente se conierten en llamadas al nueo
Oops. Si ambos hacen exactamente lo mismo, no es un problema grae. Pero supongamos lo
peor.
5 Lo peor es que a usted le costara sangre aeriguar qu demonios se ha roto en su aplicacin.
Ln mi humilde opinin, hubiera sido preerible permitir directamente la declaracin e implementa-
cin de mtodos dentro del tipo de interaz, y dejar que uese el compilador quien separase los
intrusos en una clase auxiliar aparte:
// ADVERTENCIA: Esto no es C#!!!
public interface IStack
{
bool IsEmpty { get; }
bool Top { get; }
void Push(object value);
void Pop();
void Push(params object[] values)
{
foreach (object value in values)
Push(value);
}
void Clear()
{
while (!IsEmpty)
Pop();
}
}
Mi propuesta tiene una entaja sobre la de Microsot: permitira deinir e implementar propiedades,
mientras que de momento no existen propiedades de extensin`. Pero el objetio de Microsot al
idear este recurso no se reduca a los tipos de interaz: los mtodos de extensin orman parte
importante de LINQ.
De todos modos, una ez reconocido el peligro, es erdad que podemos sacarle bastante partido a
este recurso. Ll tipo de cadenas, System.String, es un tipo de reerencia. Lxisten dos ormas distintas
de representar una cadena aca: mediante un objeto de cadena con longitud cero. y en dependen-
cia del mtodo, puede que se acepte tambin un puntero nulo como cadena aca. Ln consecuencia,
para detectar una cadena aca, la clase String nos orece el siguiente mtodo:
Tipos de interfaz
Copyright 2004-200S, Ian Marteens
41
41
public static bool IsNullOrEmpty(string s);
Como puede er, se trata de un mtodo estatico. No poda programarse como mtodo de instancias
porque C
4
prohbe explcitamente usar un puntero nulo para llamar un mtodo de instancia:
TipoClase objeto = null;
// La siguiente instruccin provocar una excepcin.
objeto.HazAlgo();
Sin embargo, esta restriccin no existe para los mtodos de extensin. Podemos declarar un mtodo
que extienda la clase String:
public static bool IsEmpty(this string s)
{
return !String.IsNullOrEmpty(s);
}
Ahora podemos escribir cdigo como el siguiente:
string cadena = null;
Console.WriteLine(cadena.IsEmpty());
Resumiendo: los mtodos de extensin son un recurso muy potente, pero no estan exentos de peli-
gro. selos con prudencia, preeriblemente para extender` tipos de interaz. Mas adelante,
oleremos a tratar con ellos, al estudiar LINQ.
N
O
T
A

Para comprobar que el objeto al que se le aplica un mtodo no es una referencia nula, el compilador
de C
#
traduce la llamada mediante la instruccin callvirt de !L aunque, tericamente, los mtodos
no virtuales pueden ejecutarse mediante la instruccin call. Cuando un mtodo no virtual se ejecuta
mediante callvirt, el compilador J!T detecta que no existe una tabla de mtodos virtuales y llama
directamente al mtodo deseado. No obstante, antes comprueba si el objeto activo del mtodo es
nulo. Cuando llamamos a un mtodo estatico o a un mtodo de extensin, sin embargo, siempre se
utiliza la instruccin call, que omite esta verificacin. Es una ironia que este tipo de llamada sea
ligeramente mas eficiente que si hubisemos declarado el mtodo directamente en la clase deseada.


Copyright 2004-200S, Ian Marteens
42
El Conocimiento Secreto
Descomponga las interaces con muchos miembros en interaces mas sencillas.
Preste atencin, por lo tanto, al tamano estimado de las estructuras que deina. Si son lo
suicientemente grandes, es preerible pasar la estructura como instancia que como
parametro.
Utilice interaces para jerarquas relatiamente planas. Para jerarquas proundas, es mejor
utilizar la herencia de clases tradicional.
No utilice tipos de interaz como sistema para marcar e identiicar tipos concretos.
Recuerde: si hay algo al otro extremo de una reerencia de interaz, tiene que tratarse de
un objeto`, obligatoriamente. Por lo tanto, aunque la deinicin de la interaz no lo esta-
blezca explcitamente, puede utilizar los mtodos de ,.tev.Ob;ect con esta reerencia.




Copyright 2004-200S, Ian Marteens
43
o oo o
E8TRUCTURA8

ARA DIBUJAR CUALQUILR 1ON1LRA LN un monitor, necesitamos puntos, muchos puntos.
Una lnea comienza en un punto y termina en otro punto. Para dibujar el texto de una cadena
de caracteres, hay que indicar dnde, y en ocasiones se utiliza otro punto para delimitar la zona de
dibujo. ,Rectangulos Puntos. ,Mapas de bits Mas puntos.
Cuerra a| desp||farro de memor|a
Si todos estos puntos uesen instancias de una clase, es decir, si estos puntos se construyesen con
cargo a la memoria dinamica del proceso. bueno, sera posible que estuisemos trabajando en
Jaa, que dejando a un lado los tipos primitios, slo orece clases como tipos de datos.
1oda esta creacin de objetos que inmediatamente son abandonados tiene un coste muy alto. No se
trata del precio pagado por la construccin en s, porque en un sistema con recoleccin automatica
de basura, la memoria dinamica suele mantenerse compactada. Gracias a ello, una peticin de
memoria slo exige moer un puntero interno que indica el tope de la memoria disponible.
Pero no podemos apulear la memoria impunemente: tanto objeto perdido aumenta la recuencia
con la que se dispara la recoleccin automatica de basura. le podido comprobar esta regla en dos
aplicaciones muy exigentes con el reloj, el compilador de lreya y el proyecto XSight Ray 1racer. Ln
ambos casos, la mejora mas espectacular consisti en habilitar una cach para los objetos mas
socorridos: nodos de la pila LALR en el compilador, y rayos en el ray tracer.
Implementar una cach de objetos reutilizables tampoco es tarea sencilla. Para empezar, debemos
tener un modelo claro del tiempo de ida de los objetos implicados. Si la aplicacin accede a estos
objetos desde arios hilos paralelos, hay un riesgo eleado de que las cosas empeoren mucho antes
de empezar a mejorar. Por estos motios, antes de recurrir a una cach de instancia debemos
comprobar si nuestro problema se puede resoler con la receta oicial de .NL1 para estos sntomas:
las estructuras.
6reac|n ef|c|ente, destrucc|n |ndo|ora
,Ln qu se dierencia un tipo de estructura de un tipo de clase Basicamente, en que al declarar una
ariable, un campo o un parametro de una clase, lo que realmente hacemos es reserar memoria
para un puntero. Mas adelante, tendremos que asignar algo` a dicho puntero, ya sea un nueo
objeto o un objeto creado en algn otro momento. Por el contrario, cuando declaramos una aria-
ble, un campo o un parametro de un tipo de estructura, estamos reserando memoria directamente
para el objeto, sin necesidad de usar una reerencia como intermediario.
La idea se explica mejor graicamente. Suponga que queremos utilizar puntos geomtricos en el
espacio tridimensional. Podemos utilizar una clase regular para representar puntos:
public class PuntoC
{
public double X, Y, Z;

}
I
Intuitive C
#

Copyright 2004-200S, Ian Marteens
44
44
La alternatia es declarar los puntos como estructuras:
public struct PuntoS
{
public double X, Y, Z;

}
Imagine ahora que usamos estos dos tipos en un mtodo, para declarar ariables locales:
public static void Main()
{
PuntoC p1;
PuntoS p2;

}
Cuando Main se ejecuta, resera espacio automaticamente para las dos ariables declaradas en su
memoria local, o memoria de pila. 1cnicamente hablando, no podemos usar ninguna de las dos
ariables, porque no han sido inicializadas. Pero ya sabemos con certeza el ormato de la memoria
local del mtodo:

Para el punto representado por una clase, se ha reserado el espacio necesario para un puntero. Si
estamos ejecutando la aplicacin sobre un procesador de 32 bits, se trata entonces de una zona de
cuatro bytes de tamano. Por el contrario, para el punto representado mediante la estructura, se
reseran 24 bytes: un campo de tipo double ocupa ocho bytes en esta misma arquitectura, y cada
punto contiene tres campos de este tipo.
C
4
exige que inicialicemos ambas ariables antes de utilizarlas:
public static void Main()
{
PuntoC p1 = new PuntoC();
PuntoS p2 = new PuntoS();

}
A pesar del parecido entre ambas asignaciones, ocurren cosas dierentes para cada ariable:

X
Y
Z
X
Y
Z
p1: PunfoC
p2: Punfo5
Pila local de Ma1n Memoria dinmica
X
Y
Z
p1: PunfoC
p2: Punfo5
Pila local de Ma1n Memoria dinmica
Estructuras
Copyright 2004-200S, Ian Marteens
45
45
Ll operador new aplicado a PuntoC, el tipo de clase, resera memoria para un punto en la memoria
dinamica, inicializa sus campos con ceros y deuele el puntero al objeto recin creado, que inal-
mente es almacenado en la ariable local p1. Ln cambio, la segunda operacin es traducida por el
operador de orma totalmente distinta. Las instrucciones generadas limpian con ceros la memoria
correspondiente al punto en la zona reserada para ella. Para comprobarlo, podemos analizar el
cdigo IL generado por el compilador con Relector:
.method private hidebysig static void Main(string[] args)
cil managed
{
.entrypoint
.maxstack 1
.locals init (
Estructuras.PuntoC oc1,
Estructuras.PuntoS os1)
newobj instance void Estructuras.PuntoC::.ctor()
stloc.0
ldloca.s os1
initobj Estructuras.PuntoS
}
La creacin de la instancia de clase tiene lugar ejecutando la instruccin newobj, que deja la instan-
cia recin construida en la pila. A continuacin, esta reerencia se almacena en la ariable global
mediante la instruccin stloc. Por el contrario, para construir una instancia de una estructura, pri-
mero se obtiene la direccin a la zona donde comienza la instancia mediante ldloca ,Load Local
Address,. Ll puntero obtenido es utilizado de inmediato por la instruccin initobj. Mas que hablar
de construccin, tratandose de estructuras, deberamos hablar de inicializacin.
Debemos tener cuidado al intentar adiinar el uncionamiento real de una instruccin del lenguaje
intermedio. Al ejecutarse la aplicacin, el compilador JI1 tiene mucha libertad para elegir la imple-
mentacin mas eiciente. Aunque no es recomendable obsesionarse con la traduccin inal a cdigo
natio, creo que esta ez merece la pena echar un istazo a lo que ocurre entre bambalinas. Lste es
el cdigo natio generado por el compilador JI1 para un AMD Sempron, un procesador de 32 bits:
; PuntoC p1 = new PuntoC();
mov ecx,969668h
call FFCB0EAC
mov edx,eax
; PuntoS p2 = new PuntoS();
lea edi,[esp]
pxor xmm0,xmm0
movq mmword ptr [edi],xmm0
movq mmword ptr [edi+8],xmm0
movq mmword ptr [edi+10h],xmm0
Ll primer punto se construye llamando a una rutina en la que Visual Studio no nos permite entrar,
existen ormas, pero no merece la pena considerarlas en este punto. No importa lo que haga
internamente la llamada: debe reserar memoria, probablemente mediante una segunda llamada,
para luego inicializarla, y en todo caso. se trata de una llamada, como mnimo, y stas suelen ser
costosas! Obsere que el resultado se ha almacenado en el registro edx.
Ln contraste, para crear el segundo punto se obtiene la direccin eectia del punto, dentro del
marco de pila utilizado por el mtodo. Las instrucciones que siguen utilizan las extensiones
multimedia del procesador: se limpia un registro de ocho bytes, y se asigna directamente su alor en
los tres campos que contiene el punto. No es necesario llamar a rutina alguna.
Con esta explicacin, he intentado demostrarle que la construccin de una instancia de estructura
suele resultar mas eiciente que la de una instancia de clase. No obstante, los beneicios no terminan
aqu. Cuando el mtodo Main termina, la instancia de la clase PuntoC se uele inaccesible, si no la
hemos asignado a una ariable con un tiempo de ida mas amplio. Ll coste de la liberacin de la
memoria no se paga en ese momento, pero la actura nos llega con intereses cuando se produce la
siguiente pasada del recolector de basura. Ln cambio, la memoria de la instancia de la estructura
Intuitive C
#

Copyright 2004-200S, Ian Marteens
46
46
PuntoS se libera automaticamente cuando el mtodo termina su ejecucin, sin que nos cueste un
nanosegundo adicional.
,Cmo se comportan las instancias de estructuras con el resto de las operaciones lay que tener
mucho cuidado. Cuando se trata de acceder a campos de la instancia, el acceso suele ser muy ei-
ciente:
Console.WriteLine(p2.X);
Ln este caso, el cdigo generado carga el alor de tipo double directamente desde su ubicacin en
la pila del mtodo actio. Pero las tornas pueden cambiar si pasamos el objeto completo como para-
metro de otros objetos. Imagine que ha escrito el siguiente mtodo auxiliar, en la misma clase que
deine el mtodo Main que hemos enido utilizando:
private static void Imprimir(PuntoS p)
{
Console.WriteLine(p.X);
}
Para ejecutar este nueo mtodo es necesario pasar una copia de todo el punto. Ls cierto que el
compilador JI1 puede sorprendernos al elegir una implementacin en cdigo natio, pero se trata
de una copia en toda regla. Lsta situacin prooca la primera regla de uso para tipos de estructuras:
Utilice estructuras para tipos de datos relativamente pequeos.
Si la estructura que desea declarar contiene muchos campos, pinselo otra ez. a no ser que nunca
utilice la estructura como parametro de otros mtodos.
lay mas casos en los que una estructura puede comportarse ineicientemente, pero los eremos
mas adelante.
L|m|tac|ones: constructores
Las entajas de las estructuras se pagan mediante algunas limitaciones. Por ejemplo:
1 No podemos deinir un constructor sin parametros para una estructura. Lstamos obligados a
coniir con el constructor sin parametros que sintetiza el sistema.
Lste constructor implcito, ademas, inicializa con ceros todos los campos de la estructura. Su-
ponga que deinimos un tipo para representar ectores tridimensionales:
public struct Vector
{
private double x, y, z;
public double X { get { return x; } set { x = value; } }
public double Y { get { return y; } set { y = value; } }
public double Z { get { return z; } set { z = value; } }
}
Ll constructor implcito asignara cero a las tres componentes del ector.
2 Los campos de una estructura no pueden tener una expresin de inicializacin.
Lsta es una consecuencia inmediata de la restriccin anterior. Si pudisemos adjuntar una
expresin de inicializacin a un campo de una estructura, estaramos inteririendo en el eecto
del constructor implcito.
3 Si deinimos un constructor con parametros, estamos obligados a inicializar todos los campos
de la estructura antes de abandonarlo. Ln particular, esto tambin se exige para poder llamar
mtodos de la propia estructura dentro del cdigo del constructor.
Lsta regla existe para acilitar el trabajo del eriicador de .NL1. La siguiente declaracin con-
tiene un error que es detectado por el compilador:
Estructuras
Copyright 2004-200S, Ian Marteens
4
47
// Esta estructura contiene errores de compilacin
public struct Contador
{
private int valor;
public Contador(int v)
{
Limpiar(); // Esta llamada genera un error de compilacin
valor += v;
}
public void Limpiar()
{
valor = 0;
}

}
Ll conlicto esta en el cdigo del constructor: la llamada a Limpiar se ejecuta antes de que haya-
mos podido inicializar el campo valor. Ls cierto que Limpiar se encarga de ello. pero el
compilador no lo sabe, porque para eriicar si se cumple la regla mencionada, slo examina el
cdigo deinido dentro del propio constructor.
,Por qu C
4
impone estas limitaciones La razn hay que buscarla en el CLR: si uese posible escri-
bir constructores sin parametros para las estructuras, tendramos un serio problema de eiciencia, y
nos enrentaramos a una complicacin considerable del motor de ejecucin. Piense en lo que ocu-
rre cuando pedimos memoria para un ector de puntos:
PuntoC[] puntos1 = new PuntoC[128];
PuntoS[] puntos2 = new PuntoS[128];
Ln ambos casos, la estructura interna que almacena los 128 puntos, se limpia con ceros. Ln el caso
de las clases, estos ceros representan reerencias nulas, lo cual es aceptable. Ln el caso de las
estructuras, se trata de puntos inicializados con ceros: habamos quedado en que los consideraba-
mos aceptables. De lo contrario, tendramos que ejecutar 128 eces el cdigo de inicializacin de
puntos que hubiramos deinido mediante el constructor sin parametros.
L|m|tac|ones: herenc|a y v|s|b|||dad
1cnicamente hablando, se considera que todas las estructuras descienden de la clase ValueType, una
clase deriada directamente de System.Object. Aunque las estructuras son tipos con semantica de
copia por alor, se considera que ValueType, el tipo comn a todas ellas, es todaa` un tipo con
semantica de copia por reerencia:

Las estructuras tambin presentan una importante limitacin relacionada con la herencia:
4 No se puede heredar de una estructura.
1oda estructura se considera automaticamente como si hubiese sido declarada con el modiicador
sealed. Dada la implementacin actual del CLR, para soportar la herencia de estructuras habra que
5ysfem.Obecf
5ysfem.va1ue1ype
My5fucf
Intuitive C
#

Copyright 2004-200S, Ian Marteens
48
4S
trabajar con punteros a estructuras en arias operaciones, y eso complicara tanto la implementacin
en s como la eriicacin del cdigo. Ln cualquier caso, no se trata de una gran prdida. 1enemos
que aceptar las estructuras como un recurso muy til relacionado con la eiciencia, y reconocer que
cumplen muy bien con su papel.
Lsta limitacin trae consecuencias en los nieles de isibilidad soportados por las estructuras. De
los cinco nieles admitidos por las clases, desaparecen protected y protected internal, y slo
sobreien estos tres:
public
Los miembros declarados public pueden ser utilizados desde cualquier parte del proyecto, ya
sea en el mismo ensamblado donde se esta declarando la clase, como desde un ensamblado
dierente.
private
Por el contrario, los miembros private slo pueden ser utilizados por la propia clase que los de-
clara.
internal
Un miembro declarado internal slo puede ser utilizado por clases ubicadas dentro del mismo
ensamblado que la clase original. Ls una ersin menos estricta de private.
La explicacin esta ez es sencilla: ,para qu declarar miembros protegidos, si la estructura no ten-
dra descendientes
Htodos v|rtua|es y estructuras
Las estructuras no pueden tener descendientes, pero ellas mismas descienden de una clase que les
lega unos cuantos mtodos irtuales. ,Quin podra negarles la potestad de redeinirlos Para ser
exactos, son tres los mtodos irtuales heredados de ValueType:
public override bool Equals(object obj);
public override int GetHashCode();
public override string ToString();
De ellos, Equals ha sido redeinido en ValueType, para implementar una comparacin inicial acepta-
ble para un tipo de estructura, y algo parecido ocurre con GetHashCode. La implementacin por
omisin que orece Equals utiliza relexin, y eso signiica algo de lentitud anadida. Un consejo
importante, si a a comparar instancias de una estructura, o si a a usarla en contenedores con
capacidad de bsqueda, consiste en redeinir Equals, para hacerla eiciente:
public struct Vector
{
private double x, y, z;
public double X { get { return x; } set { x = value; } }
public double Y { get { return y; } set { y = value; } }
public double Z { get { return z; } set { z = value; } }
public override bool Equals(object obj)
{
if (obj is Vector)
{
Vector v = (Vector)obj;
return x == v.x && y == v.y && z == v.z;
}
else
return false;
}

}
lay que tener mucho cuidado si amos a redeinir Equals. Para empezar, no se espera que este m-
todo prooque excepciones al ser ejecutado: aunque su parametro se declara como object y puede
Estructuras
Copyright 2004-200S, Ian Marteens
49
49
recibir alores de cualquier clase, en el caso de que el parametro no sea otro ector, debemos deol-
er false, en ez de disparar una excepcin.
Otro detalle importante es la orma en que comprobamos si el parametro es un ector. Si Vector
uese una clase, en lugar de una estructura, habramos mezclado la comprobacin y la conersin
en una sola operacin, por medio del operador as de conersin de tipos. Lste operador, sin em-
bargo, slo trabaja con tipos de reerencia, por lo que hemos tenido que eriicar antes de conertir.
Si redeinimos Equals, es aconsejable hacer lo mismo con GetHashCode, para que sigan uncio-
nando en mutua armona. De hecho, el propio compilador nos lo recuerda mediante una aderten-
cia si no lo hacemos. Una posible implementacin sera como la siguiente:
public override int GetHashCode()
{
return x.GetHashCode() ^ y.GetHashCode() ^ z.GetHashCode();
}
La nuea implementacin combina los cdigos hash de los campos mediante una disyuncin exclu-
sia, o xor. 1enga en cuenta que los tres campos son de tipo real, si uesen enteros, nos habra bas-
tado combinar los propios alores de los campos. Una uncin de hash debe ser todo lo rapida que
se nos ocurra.
Aunque no es obligatorio, no es mala idea redeinir ToString. Ls poco probable que la necesitemos
en nuestro propio proyecto, pero nos sera de gran ayuda durante la depuracin:
public override string ToString()
{
return String.Format("V({0},{1},{2})", x, y, z);
}
Por ltimo, como se trata de una estructura para la que se ha redeinido Equals, se recomienda que
redeinamos los operadores de igualdad y desigualdad:
public static bool operator ==(Vector v1, Vector v2)
{
return v1.x == v2.x && v1.y == v2.y && v1.z == v2.z;
}
public static bool operator !=(Vector v1, Vector v2)
{
return v1.x != v2.x || v1.y != v2.y || v1.z != v2.z;
}
Si Vector uese una clase, no deberamos redeinir estos operadores, porque lo correcto es que
comparen las reerencias entre s, no los objetos reeridos. Si no estuisemos interesados en expri-
mir la estructura en bsqueda de elocidad de ejecucin, podramos llamar a Equals desde estos
operadores, o iceersa. le preerido, no obstante, crear tres implementaciones independientes.
|nterfaces y estructuras
lemos isto que una estructura permite redeinir los mtodos irtuales que hereda. Ahora bien,
,deberan permitir la deinicin de nueos mtodos irtuales A primera ista, no: al ser tipos sella-
dos, nadie los redeinira. \ eectiamente, no se pueden declarar mtodos irtuales en una estruc-
tura. al menos, de manera explcita. Una estructura puede implementar uno o mas tipos de inter-
az, y ya sabemos que el CLR, internamente, deine como irtuales los mtodos de implementacin
de interaces.
Por ejemplo, nuestra estructura Vector puede implementar la interaz IFormattable, para permitir el
uso de proeedores de ormato y cadenas de ormato:
public struct Vector : IFormattable
{

Intuitive C
#

Copyright 2004-200S, Ian Marteens
50
50
public string ToString(
string format, IFormatProvider formatProvider)
{
if (String.IsNullOrEmpty(format) ||
format == "G" || format == "g")
return String.Format(
formatProvider, "V({0},{1},{2})", x, y, z);
throw new FormatException(String.Format(
"Cadena de formato incorrecta: '{0}'.", format));
}
}
Recuerde que, aunque la implementacin de esta ariante de ToString no esta marcada como irtual,
el compilador internamente anade las marcas necesarias al mtodo compilado.
Ll nueo mtodo ToString me permitira mostrarle el comportamiento de las llamadas a mtodos de
una estructura. Supongamos que, por simpliicar, implementamos la redeinicin del mtodo here-
dado ToString, el que no tiene parametros, por medio de la ersin creada para IFormattable:
public override string ToString()
{
return this.ToString(null,
System.Globalization.CultureInfo.CurrentCulture);
}
Sabemos que la ersin de ToString con dos parametros es un mtodo irtual. ,Cmo se compila
esta llamada Ln este caso, en ez de realizarse una llamada a tras de la tabla de mtodos irtua-
les, el compilador genera una llamada directa al mtodo. La clae esta en que la llamada se ejecuta a
tras de la reerencia this, que en este caso esta asociada a un tipo sellado. No hay posible conu-
sin sobre el mtodo que resultara ejecutado como resultado de la inocacin. Ll compilador lo
sabe y genera un tipo de llamada mucho mas eiciente.
Ahora bien, ,qu ocurrira en este otro caso
IFormattable f = new Vector();
Console.WriteLine(f.ToString(
null, System.Globalization.CultureInfo.CurrentCulture);
Aunque en este ejemplo no hay conusin posible en el tipo de instancia al que apunta la ariable f,
en el caso mas general la ariable podra apuntar a cualquier clase o estructura que implementase la
interaz IFormattable. Lsta llamada tendra que ejecutarse con la ayuda de la tabla de mtodos
irtuales asociada a la instancia. Sin embargo, tenemos dos problemas:
1 Vector es una estructura! ,Cmo es posible entonces que una ariable de interaz pueda apuntar
a una instancia de la estructura
2 Supongamos por un momento que el compilador resoliese la papeleta como lo hara un
compilador natio`: haciendo que la ariable apuntase al inicio de la zona donde se almacena
la estructura. Ln ese caso, seguiramos con problemas: las instancias de una estructura no
contienen, en su representacin habitual, un puntero a la correspondiente tabla de mtodos ir-
tuales.
E| sagrado m|ster|o de| box|ng
La solucin a este acertijo es una operacin llamada boxing, cuyo nombre no traducir, pues tam-
poco lo hace la documentacin de la plataorma. La operacin consiste en crear una instancia en
memoria dinamica a partir de una instancia de una estructura, y tiene lugar, precisamente, cuando
asignamos un tipo de estructura a una ariable, parametro o campo declarado como una reerencia.
Ln nuestro ejemplo, el boxing ocurre gracias a esta asignacin:
IFormattable f = new Vector();
Estructuras
Copyright 2004-200S, Ian Marteens
51
51

Obsere que no se trata de una simple copia de la representacin plana` del ector. Como sugiere
la boina que he puesto sobre el ector representado en la derecha del diagrama, el boxing anade un
puntero a la tabla de mtodos irtuales que corresponde al tipo concreto de estructura copiada. Lse
puntero es el que garantiza el buen uncionamiento de las llamadas a mtodos irtuales que puedan
inocarse a tras de una ariable de interaz.
Lxiste una operacin inersa, el unboxing, que debe deshacer el hechizo sacando el conejo de la
chistera. Si sabemos que una reerencia a interaz apunta realmente a un Vector, con esta asignacin
recuperamos el ector en toda su gloria:
Vector v = (Vector)f;
Como e, esta ltima operacin es un caso particular de una ulgar conersin de tipo. Ls en el
boxing donde reside toda la magia.
N
O
T
A

Aunque aqui he ilustrado el funcionamiento del boxing con variables de interfaz, es mas comun que
se produzca con referencias de tipo object. En cualquier caso, hay que tener mucho cuidado, pues
es una operacin costosa y es muy facil introducirla sin que nos demos cuenta.

,Por qu demonios haba que inentar una historia tan retorcida como esto de meter estructuras en
cajitas para sacarlas despus Por una parte, se trata de un requisito lgico que simpliica
extraordinariamente el diseno e implementacin del lenguaje. 1odos los tipos del Common Type Sys-
tem, ya sean tipos de reerencia o con semantica de asignacin por alor, se consideran deriados
directos o indirectos de la clase Object. Como hemos isto, todos los tipos de estructuras descien-
den de la clase especial ValueType. La herencia implica la compatibilidad para la asignacin, y con el
boxing tenemos la posibilidad de asignar cualquier tipo de alor a una ariable de tipo object.
Un segundo motio: el tipo object sustituye al tipo Variant que utilizaban lenguajes como Delphi y
Visual Basic natio`. A primera ista, y tambin a segunda y tercera, el tipo ariante parece una
chapuza. pero lo cierto es que simpliica mucho la programacin para bases de datos, y en
particular, la capa de acceso genrico sobre la que se pueden construir capas mas seguras desde el
punto de ista del sistema de tipos.
linalmente, estan los problemas planteados por las estructuras de datos, en especial, los contenedo-
res: listas dinamicas, pilas, colas, etc. Ln la ersin 2.0, los tipos genricos orecen la solucin mas
elegante, segura y eiciente, pero estos tipos no estaban disponibles en las ersiones anteriores. Para
poder reutilizar una lista disenada para almacenar objetos`, en general, era necesario poder coner-
tir tipos con semantica de alor en reerencias.
lIomaffab1e f
X
Y
Z
vecfo v




X
Y
Z
vecfo
Copyright 2004-200S, Ian Marteens
52
El Conocimiento Secreto
Los parametros de estructura con traspaso por alor, se pasan sicamente por copia.
Preste atencin, por lo tanto, al tamano estimado de las estructuras que deina. Si son lo
suicientemente grandes, es preerible pasar la estructura como instancia que como
parametro.
1enga mucho cuidado si quiere que una estructura implemente alguna interaz.
Normalmente, esto se desea para manejar estructuras polimricas basadas en el tipo de
la interaz. Ll problema esta en que, para incluir una instancia de estructura en una
estructura de este tipo, es necesario someter la instancia al boivg. No basta con pasar un
punto o reerencia a la zona de memoria donde esta la estructura, sino que hay que crear
un puntero a la tabla de mtodos irtuales, como si se tratase de una instancia de un tipo
de reerencia.
Personalmente, cuando deino una estructura que oy a usar lo suiciente, suelo incluir en
su declaracin algunas interaces basicas como IEquatable<T> o IComparable<T>, segn
proceda. Mientras no intente usar una instancia de la estructura a tras de una ariable
declarada con los tipos mencionados, no hay problema. ,Para qu lo hago, entonces Re-
cuerde que los tipos de interaz expresan un contrato, y que los contratos que establece-
mos en nuestro cdigo deben ser lo mas explcitos que sea posible.
Aunque no lo he comprobado, puede que la idea tenga sentido, bajo determinadas premi-
sas, si amos a utilizar el tipo de estructura para instanciar un tipo genrico

Copyright 2004-200S, Ian Marteens
53
t tt t
TPO8 DELEGADO8

ML PLRMI1L UNA PRLGUN1A tonta Asumo que s, de modo que ah a: ,por qu llamamos
variables a las variables Lo malo de hacer estas preguntas es que termina por contestarlas uno
mismo. Las llamamos ariables porque pueden contener un alor que vara. Si es una ariable en-
tera, puede que contenga un cero, un seiscientos sesenta y seis o un menos uno. Si es una ariable
lgica, slo puede contener erdadero o also. Gracias a este hecho tan elemental, querido \atson,
podemos desarrollar algoritmos que acten sobre un alor entero arbitrario, o sobre un alor lgico
arbitrario, etctera.
0e|egados
,Podemos tener variables de funciones Si uese posible, podramos desarrollar algoritmos que
actuasen sobre una uncin arbitraria. Imagine, por ejemplo, un algoritmo que calcule la media de
los alores de una uncin real ealuada en una lista de puntos.

Para concretar, suponga que tomamos la uncin raz cuadrada, y queremos promediar el resultado
de esta uncin en una lista de puntos pasada como parametro:
public static double MediaRaiz(double[] puntos)
{
// Asumiremos que el vector puntos no est vaco.
double total = 0.0;
for (int i = 0; i < puntos.Length; i++)
total += Math.Sqrt(puntos[i]);
return total / puntos.Length;
}
,\ si queremos usar la uncin exponencial 1endamos que modiicar el bucle de esta orma:
for (int i = 0; i < puntos.Length; i++)
total += Math.Exp(puntos[i]);
Viendo que se trata de modiicaciones mnimas, ,por qu no programar la uncin Media de orma
tal que acepte cualquier uncin real que le suministremos Ln los dos ejemplos anteriores, he resal-
tado en rojo la uncin utilizada. Para lograr nuestro propsito, tendramos que sustituir la llamada a
una uncin concreta por una llamada a una uncin. que puede ariar. le aqu que necesitamos
una variable de funcin: una ariable, o parametro, que contenga una reerencia` a una uncin.

Intuitive C
#

Copyright 2004-200S, Ian Marteens
54
54
Lsta claro que no puede ser una uncin cualquiera: las unciones que debe aceptar dicha ariable
deben admitir un parametro de tipo real y retornar un alor tambin real. Necesitamos poder
programar lo siguiente:
public static double MediaRaiz(double[] puntos,
TipoEspecialAunPorDeterminar funcion)
{
double total = 0.0;
for (int i = 0; i < puntos.Length; i++)
total += funcion(puntos[i]);
return total / puntos.Length;
}
Parece conincente. pero si le cuenta esta historia a su proesor de Jaa, era como runce el ceno
y, como mnimo, le llama psicpata perertido que nunca comprendera la belleza de la OOP`.
N
O
T
A

A Nicrosoft, modificar la JvN para permitir estas cosas le cost un pleito de aupa con Sun, que ade-
mas perdi. Ya es triste que el J++ mutante fuese mas rapido y eficiente que el trilobites antedilu-
viano de Jaime Gansito y compania. Nas triste aun, si cabe, es ver que quienes aplauden el vere-
dicto de ese juicio son luego quienes se oponen mas radicalmente a las patentes de software. Habia
una vez una hermosa dama llamada Coherencia, alabada por todos pero amada por nadie.

Me atar una mano a la espalda para mostrar dos ormas de bordear el problema al estilo Jaa, aun-
que usar C
4
La primera consiste en encapsular el algoritmo de la media dentro de una clase:
public abstract class Promediador
{
protected abstract double Funcion(double valor);
public double Calcular(double[] puntos)
{
double total = 0.0;
for (int i = 0; i < puntos.Length; i++)
total += Funcion(puntos[i]);
return total / puntos.Length;
}
}
Imagino que ya estara iendo el disparate. Cada ez que quiera usar el algoritmo con una nuea
uncin, tendra que crear una clase deriada:
public class PromedioRaiz : Promediador
{
protected override double Funcion(double valor)
{
return Math.Sqrt(valor);
}
}
Una clase para poder usar una uncin! Le aseguro, ademas, que esto hace mas lento el algoritmo,
porque para ealuar la raz en cada punto se realiza primero una llamada irtual, y luego iene la
llamada a la erdadera uncin. Para poder ejecutar el algoritmo, necesitaremos crear tambin una
instancia de la nuea clase:
double[] puntos = new double[]{ 0.0, 1.0, 2.0 };
Promediador prom = new PromedioRaiz();
Console.WriteLine(prom.Calcular(puntos));
Ln realidad, casi nadie seguira este camino de perdicin: un jaans tomado al azar que posea un
mnimo de sensatez usara un tipo de interaz:
public interface IFuncion
{
double Funcion(double valor);
}
Tipos delegados
Copyright 2004-200S, Ian Marteens
55
55
Podramos programar ahora una uncin similar a la presentada al iniciar la seccin, usando el tipo
de interaz en el papel del TipoEspecialAunPorDeterminar:
public static double MediaRaiz(double[] puntos, IFuncion funcion)
{
double total = 0.0;
for (int i = 0; i < puntos.Length; i++)
total += funcion.Funcion(puntos[i]);
return total / puntos.Length;
}
,Le parece interesante Si responde que s, es que no se ha dado cuenta todaa de que estamos
repitiendo casi al pie de la letra la tontera de la clase abstracta. Para que esto uncione, seguimos
necesitando una nuea clase para cada uncin que se nos ocurra promediar. Lo nico que ha cam-
biado es que, donde antes haba herencia de clases, ahora tenemos la implementacin de un tipo de
interaz:
public class PromedioRaiz : IFuncion
{
public double Funcion(double valor)
{
return Math.Sqrt(valor);
}
}
La orma de ejecutar el algoritmo tampoco ha cambiado mucho:
double[] puntos = new double[]{ 0.0, 1.0, 2.0 };
IFuncion raizCuadrada = new PromedioRaiz();
Console.WriteLine(MediaRaiz(puntos, raizCuadrada));
,Listo para la solucin de erdad`
public delegate double Funcion(double valor);
public static double MediaRaiz(double[] puntos, IFuncion funcion)
{
double total = 0.0;
for (int i = 0; i < puntos.Length; i++)
total += funcion(puntos[i]);
return total / puntos.Length;
}
La primera lnea es una declaracin de tipo: declaramos un tipo Funcion que representa aquellas
unciones o mtodos que reciben un alor de doble precisin y deuelen un alor del mismo tipo.
Veamos ahora cmo usar el algoritmo:
double[] puntos = new double[]{ 0.0, 1.0, 2.0 };
Console.WriteLine(prom.Calcular(puntos, new Funcion(Math.Sqrt)));
Puede que me est llamando tramposo. ,Por qu he omitido la ariable temporal que s he utilizado
con las soluciones tipo Jaa Quizas debera haber escrito esto:
double[] puntos = new double[]{ 0.0, 1.0, 2.0 };
Funcion raizCuadrada = new Funcion(Math.Sqrt);
Console.WriteLine(MediaRaiz(puntos, raizCuadrada));
Pero es que en C
4
2.0 se puede usar una sintaxis abreiada:
double[] puntos = new double[]{ 0.0, 1.0, 2.0 };
Console.WriteLine(prom.Calcular(puntos, Math.Sqrt));
No es la breedad lo que mas me gusta de esta solucin: repase los ejemplos anteriores, para que
ea que estas dos lneas son las que mejor se comprenden, las que mejor expresan el problema plan-
teado y su solucin. Ll tipo Funcion nos permite declarar ariables, campos y parametros para
representar las variables de funcin por las que suspirabamos al principio. \ gracias a la nuea sin-
Intuitive C
#

Copyright 2004-200S, Ian Marteens
56
56
taxis disponible a partir de C
4
2.0, podemos usar el nombre de una uncin concreta, como
Math.Sqrt, como si se tratase de una constante de funcin! Compare:
// A una variable entera le asignamos una constante entera
int i = 0;
// A una variable de funcin le asignamos una constante de funcin
Funcion f = Math.Sqrt;
Obsere que, en la segunda instruccin no se esta pidiendo la ejecucin de la uncin que calcula la
raz cuadrada. La dierencia es pequena y merece ser destacada:
// A una variable real le asignamos el resultado de una funcin
Funcion f = Math.Sqrt(3.0);
Ls la ausencia de parntesis, a continuacin del nombre de la uncin, la que indica que queremos
una reerencia a la uncin, no el resultado de su ealuacin.
6adenas de de|egados
le simpliicado un poco las cosas en la explicacin anterior, de modo que los tipos delegados de
.NL1 se pareciesen lo mas posible a los punteros a unciones de la programacin mas tradicional.
Los punteros a unciones han estado en C desde siempre, y ueron anadidos a Pascal poco despus
de su creacin. Lra mas complicado anadir este recurso a Pascal principalmente por culpa de los
procedimientos anidados de este lenguaje, que ya de por s anaden complejidad a la implementacin
de compiladores. La solucin mas popular ha sido permitir que estos punteros slo puedan hacer
reerencia a unciones y procedimientos no anidados, tal como ocurri en 1urbo Pascal 4.
De todos modos, hay una pequena dierencia entre los punteros a unciones tradicionales y los
delegados: en un lenguaje orientado a objetos puro, los mtodos no existen separados de las clases,
como entidades independientes. Cuando guardamos una reerencia a un mtodo en un delegado, el
disenador del lenguaje debe tomar una decisin: ,debe el delegado guardar tambin la reerencia a
un objeto en particular, o no C--, que no es un lenguaje puro`, opta por la a negatia. Los
lenguajes .NL1 siguen la a contraria: un delegado, en realidad, almacena una reerencia opcional a
un objeto, ademas de la reerencia al mtodo en s. La reerencia al objeto es opcional para que los
delegados se puedan usar tambin con mtodos estaticos, que como sabemos, no necesitan una
instancia para ser ejecutados.
\ hay mas complicaciones: los alores delegados pueden ormar cadenas de reerencias a distintos
mtodos. Ll siguiente diagrama muestra, al in, la estructura interna de una instancia de un tipo
delegado y el mecanismo usado para ormar las cadenas mencionadas:

Ln primer lugar, y como sugiere el diagrama, los delegados son tipos de reerencia: .NL1 los imple-
menta como un tipo muy particular de clases. Lse es el motio por el que haba que utilizar el
operador new en C
4
1.0 para obtener un delegado:
Funcion raizCuadrada = new Funcion(Math.Sqrt);
Lsto sigue siendo cierto en la ersin 2.0, pero el compilador ahora puede anadir por su cuenta la
llamada al constructor:
Funcion raizCuadrada = Math.Sqrt;
_target
_methodPtr
_prev
_target
_methodPtr
_prev
Tipos delegados
Copyright 2004-200S, Ian Marteens
5
57
Se asume que todos los tipos delegados descienden de la clase especial MulticastDelegate, que a su
ez deria de Delegate, la cual desciende directamente de Object. Al parecer, Microsot consider en
algn momento la posibilidad de orecer delegados simples y combinables, pero en la ersin inal
slo sobreiieron los delegados combinables que ahora conocemos.
Ll diagrama nos muestra tambin la existencia de tres campos ocultos dentro de una instancia de
delegado. Ll primero de ellos, bautizado _target en la ilustracin, almacena una reerencia a un ob-
jeto, en el caso de que el delegado se reiera a un mtodo estatico, se guarda un puntero nulo en
este campo. Ll campo _methodPtr contiene una reerencia al mtodo concreto al que el delegado
apunta. Sospecho que este alor debe interpretarse como una direccin dentro del segmento de
cdigo de la aplicacin. linalmente, el campo _prev apunta a otro delegado del mismo tipo, y es el
truco que se utiliza para implementar cadenas de delegados.
,Qu sentido tiene combinar delegados en una cadena Si se trata de unciones matematicas, como
la raz cuadrada o la exponencial, no tiene ningn sentido. Si actiamos el delegado, se ejecutaran
cada una de las unciones de la cadena, pero slo podremos usar el alor de retorno de la ltima
uncin que se ejecute. Algo muy dierente ocurre cuando trabajamos con mtodos que proocan
eectos secundarios. Veamos un ejemplo sencillo:
public delegate void Mensaje();
Lste delegado se puede conectar a mtodos sin parametros y sin alor de retorno. Deinamos dos
mtodos compatibles con este tipo. Para simpliicar, usaremos mtodos estaticos:
public static void BuenosDias()
{
Console.WriteLine("Buenos das");
}
public static void Adios()
{
Console.WriteLine("Adis");
}
Declaremos una ariable y construyamos una cadena de delegados:
Mensaje cadena = new Mensaje(BuenosDias) + new Mensaje(Adios);
Como e, podemos utilizar el operador de adicin para componer cadenas de delegados. Ln el
ondo, el operador ejecuta el mtodo estatico Combine de la clase Delegate. Ljecutemos ahora los
mtodos de la cadena:
cadena();
lemos usado la ariable como si uese el nombre de una uncin, y le hemos pasado los parame-
tros necesarios. Ln nuestro caso, no hay parametros, por supuesto.
L|amadas as|ncronas
Los tipos delegados en .NL1 nos deparan una agradable sorpresa: son la base de un mecanismo
potente y elegante de ejecucin asncrona. Lo mejor de todo es que la tcnica es segura en lo que
respecta al sistema de tipos y el traspaso de parametros.
Imagine que desea ejecutar el siguiente mtodo en paralelo con el hilo principal de su aplicacin:
bool HazAlgo(double d, ref string s, out int i);
Ahora nos da igual lo que haga el mtodo, pues lo que nos interesa realmente son sus parametros.
Ll primer paso es declarar un tipo delegado que se ajuste al prototipo del mtodo. Por ejemplo:
delegate bool HazAlgoDelegate(double d, ref string s, out int i);
De manera automatica, el nueo tipo HazAlgoDelegate implementa un par de mtodos con los si-
guientes prototipos:
Intuitive C
#

Copyright 2004-200S, Ian Marteens
58
5S
// Estos son mtodos automticamente declarados para HazAlgoDelegate
public IAsyncResult BeginInvoke(
double d, ref string s, out int i,
AsyncCallback callback, object state);
public bool EndInvoke(ref string s, out int i, IAsyncResult result);
Como puede comprobar, los parametros de los mtodos del delegado se basan en los del mtodo
original que queremos llamar. Las reglas son estas:
1 Ll tipo de retorno de BeginInvoke siempre es IAsyncResult.
2 1odos los parametros del mtodo original se copian en el prototipo de BeginInvoke.
3 Ademas, a BeginInvoke siempre se le anaden dos parametros adicionales, el primero del tipo
AsyncCallback, y el segundo de tipo object.
4 Ll tipo de retorno de EndInvoke debe coincidir con el del mtodo original.
5 Los parametros de EndInvoke se generan a partir de los parametros de salida y de reerencia del
mtodo original, y se le anade un parametro de tipo IAsyncResult.
Veamos ahora cmo implementar el modelo mas sencillo de llamada asncrona:
// Obtenemos un delegado que haga referencia al mtodo a llamar.
HazAlgoDelegate d = HazAlgo;
// Ejecutamos el mtodo BeginInvoke del delegado.
string s = "Valor inicial";
int i;
IAsyncResult result = d.BeginInvoke(3.14, ref s, out i, null, null);
// Ejecutamos algo mientras BeginInvoke termina.
OperacionAburrida();
// Ejecutamos EndInvoke para esperar por BeginInvoke
// y obtener los resultados.
bool b = d.EndInvoke(ref s, out i, result);
La llamada a BeginInvoke pide un hilo disponible a una cach interna de hilos que .NL1 mantiene
para cada proceso. La ejecucin de HazAlgo se inicia en ese hilo paralelo, mientras que el hilo princi-
pal sigue ejecutandose en paralelo al mtodo asncrono. Ln nuestro ejemplo, aproechamos para
ejecutar en paralelo una operacin larga y aburrida y, al terminar, esperamos que HazAlgo haya
terminado. Ln realidad, el mtodo asncrono puede o no haber terminado de ejecutarse, pero la
llamada a EndInvoke bloquea el hilo inicial si el segundo hilo an esta ejecutando cdigo. De paso,
aproechamos para recuperar los resultados de HazAlgo mediante los parametros y el alor de re-
torno del mtodo EndInvoke.
lay unas cuantas ariantes de esta tcnica. Por ejemplo, en ez de sentarnos a esperar pasiamente
a que la ejecucin asncrona haya concluido, podemos aproechar el tiempo de espera para otras
tareas:
// Otra forma de esperar por EndInvoke.
while (!result.Completed)
{
result.AsyncWaitHandle.WaitOne(1000, false);
OperacionNoMuyLarga();
}
// De todos modos, es necesario llamar a EndInvoke.
bool b = d.EndInvoke(ref s, out i, result);
Para la espera, hemos utilizado el alor de tipo IAsyncResult que nos ha deuelto BeginInvoke. La
propiedad Completed nos indica si la ejecucin asncrona ha terminado o no. Mientras no lo haya
hecho, llamamos al mtodo WaitOne sobre la propiedad AsyncWaitHandle del alor deuelto por
BeginInvoke. Lste mtodo bloquea el hilo que lo llama hasta que el hilo asncrono termina de ejecu-
tar el mtodo paralelo. o cuando se agota el tiempo de espera indicado en el primer parametro, y
que se da en milisegundos. Ln nuestro caso, esperamos un segundo por cada ez, y entre espera y
espera, aproechamos para ejecutar alguna otra tarea no demasiado compleja. Obsere que siempre
hay que llamar a EndInvoke, existan o no parametros de salida o alores de retorno.
Tipos delegados
Copyright 2004-200S, Ian Marteens
59
59
,Qu haramos en el caso en que no nos interesase saber el inal de la ejecucin del mtodo asn-
crono Ln ese caso, podramos pasar a BeginInvoke la direccin de un mtodo adicional, que se
ejecutara al inalizar la ejecucin del segundo hilo. Recuerde que BeginInvoke tiene dos parametros
adicionales, y que el tipo del primero de ellos es un tipo delegado:
public delegate void AsyncCallback(IAsyncResult ar);
Primero declararamos un mtodo auxiliar que se ajustase al prototipo anterior:
private void EjecucionTerminada(IAsyncResult ar)
{
string s = "";
int i;
bool b = ((HazAlgoDelegate)ar.AsyncDelegate).EndInvoke(
ref s, out i, ar);
}
Lste ejemplo no es quizas el mas adecuado para esta tcnica, pues estamos ignorando todos los
resultados de la ejecucin asncrona. Obsere, de todos modos, la conersin de tipos que realiza-
mos para obtener el delegado original a partir del parametro IAsyncResult.
linalmente, la direccin de este mtodo se pasara como parametro de BeginInvoke:
// Obtenemos un delegado que haga referencia al mtodo a llamar.
HazAlgoDelegate d = HazAlgo;
// Ejecutamos el mtodo BeginInvoke del delegado.
string s = "Valor inicial";
int i;
IAsyncResult result = d.BeginInvoke(3.14, ref s, out i,
EjecucionTerminada, null);
// Podemos seguir a nuestro aire
Cuando la ejecucin de HazAlgo termine, se ejecutara EjecucionTerminada, y es all donde se llamara
a EndInvoke, por esta ez.



Copyright 2004-200S, Ian Marteens
60
7 77 7
COMPONENTE8

4 \ JAVA SON LLNGUAJLS ORILN1ADOS a objetos. Para ser mas exactos, C
4
es un lenguaje
orientado a componentes. Jaa, sin embargo, no lo es. ,Qu signiica lo de orientado a
componentes` ,Por qu Jaa no cumple los requisitos
6omponentes versus c|ases
Ll paso de simples clases` a componentes es muy parecido al que se produjo en la industria
electrnica cuando entr en escena el circuito impreso. Antes de la popularizacin del circuito im-
preso, el ingeniero electrnico tena que amanarselas para soldar entre s alulas, transistores, dio-
dos, resistencias y condensadores. ,la isto alguna ez las entranas de un teleisor o de una radio
de principios de los 50s Si el aparato tena alulas, los zcalos de stas se ijaba al chasis metalico,
y sus terminales seran de puente o apoyo a las restantes piezas. Disenar un circuito sencillo reque-
ra una buena imaginacin graica para distribuir las piezas en el espacio sin que se cruzasen por
accidente. Los circuitos impresos dieron un uelco a la situacin, simpliicando las tareas de interco-
nexin de las partes implicadas.
Un componente de sotware es una clase a la que se le ha anadido la inraestructura necesaria para
conectarse con acilidad a otros componentes dentro de un sistema de desarrollo dado. 1omemos
una clase de .NL1 al azar, por ejemplo, la clase Hashtable. ,Ls Hashtable un componente No, no
lo es. Le alta la capacidad de crear instancias persistentes sobre las supericies de diseno de Visual
Studio. Una clase de la plataorma adquiere esa capacidad cuando implementa la interaz
IComponent, del espacio de nombres System.ComponentModel, ya sea de orma directa, o de orma
indirecta, usando la clase Component como ancestro. La interaz IComponent se declara as:
public interface IComponent : IDisposable
{
ISite Site { get; set; }
event EventHandler Disposed;
}
Un componente .NL1 se comunica con la supericie de diseno que lo aloja por medio de la propie-
dad Site. Al componente se le exige que permita su destruccin determinista ,p.2, y que nos aise
mediante un eento si es destruida. Lsto es necesario, por ejemplo, para eitar que otros
componentes hagan reerencia a un componente que ya ha sido eliminado de la supericie de di-
seno.
,Cmo se comunica la supericie de diseno con el componente La supericie contenedora asume
que el componente publica` propiedades y eentos. Con la lista de propiedades y eentos soporta-
dos por el componente actio, la supericie prepara el contenido que mostrara el inspector de
propiedades. linalmente, el programador utilizara el inspector de propiedades para establecer el
alor inicial de las propiedades de cada componente, y para enganchar mtodos a los eentos de
cada componente.
La pregunta del milln es: ,por qu se utilizan propiedades Una propiedad es una especie de
campo con superpoderes. ,Por qu, entonces, no se trabaja directamente con los campos La res-
puesta, eidentemente, tiene que er con los superpoderes` mencionados. Ll programador espera
que, cuando asigne una cadena en la propiedad Text de un botn, el cambio se muestre inmediata-
mente en el botn ubicado sobre la supericie de diseno. Si trabajasemos directamente con los cam-
C
Componentes
Copyright 2004-200S, Ian Marteens
61
61
pos del botn, tras asignar un alor en el campo correspondiente, habra que ejecutar automatica-
mente un mtodo para actualizar el aspecto del botn. La propiedad, por el contrario, encapsula
para beneicio nuestro, el campo mas el mtodo de actualizacin en los casos en que sea necesario.
Lsta claro que podemos idear otras estrategias para poder modiicar una instancia en tiempo de
diseno. pero todas las alternatias al uso de propiedades son peores. Lsto es lo que ocurre con
Jaa, que no soporta ni propiedades ni eentos. Para poder utilizar una clase en tiempo de diseno, el
autor de la clase tiene que suministrar inormacin adicional que, a la larga, simula la existencia de
propiedades de cara al entorno de desarrollo. Como consecuencia, es mucho mas complicado crear
un componente en Jaa que hacerlo con C
4
o con cualquier otro lenguaje que soporte propiedades y
eentos.
Prop|edades
Una propiedad imita el comportamiento de un campo mediante dos mtodos internos que deben
uncionar en sincrona. Si la propiedad es de slo lectura, slo es necesario un mtodo. No existen
propiedades de slo escritura, porque no tendran mucho sentido.
Las propiedades mas sencillas son aquellas que encapsulan un campo, como en el siguiente ejemplo:
private int edad;
public int Edad
{
get { return edad; }
set { edad = value; }
}
Como puede er, esta propiedad ha necesitado dos mtodos. Ll mtodo de lectura, identiicado
mediante get, no recibe parametros, y debe deoler un alor del mismo tipo que la propiedad. Ll
mtodo de escritura, identiicado con set, no deuele nada, pero debe recibir un alor del mismo
tipo que la propiedad. Ll parametro no se declara: el alor asignado a la propiedad puede obtenerse
mediante el identiicador value.
Podemos utilizar una propiedad de este tipo para eriicar que los alores asignados al campo sean
siempre correctos. La siguiente propiedad complica un poco su mtodo de escritura:
public int Edad
{
get { return edad; }
set
{
if (value < 0)
throw new Exception("Edad incorrecta.");
edad = value;
}
}
Si el alor que se quiere asignar a la propiedad no nos gusta, impedimos la asignacin lanzando una
excepcin.
Se pueden usar propiedades para sintetizar inormacin a partir de otros campos y propiedades. La
siguiente propiedad calcula la edad a partir de la echa de nacimiento, en ez de almacenar la edad
directamente en un campo:
private DateTime fechaNacimiento;
public int Edad
{
get
{
DateTime hoy = DateTime.Today;
if (hoy.DayOfYear >= fechaNacimiento.DayOfYear)
return hoy.Year - fechaNacimiento.Year;
Intuitive C
#

Copyright 2004-200S, Ian Marteens
62
62
else
return hoy.Year - fechaNacimiento.Year - 1;
}
}
Ln casos como ste, hay que preguntarse si debemos declarar Edad como una propiedad de slo
lectura o como una uncin sin parametros:
Si el algoritmo consume mucho tiempo, es preerible usar una uncin sin parametros. Ll
programador que accede a una propiedad espera que su lectura sea igual de rapida que la lectura
de un campo. Ln cambio, de una llamada a uncin se puede esperar cualquier cosa.
Si el algoritmo de lectura cambia el estado obserable de la instancia, es preerible usar una
uncin.
Ln las ersiones 1.0 y 1.1 de la plataorma, los mtodos de acceso tenan siempre la misma isibili-
dad que la propiedad que los declaraba. A partir de .NL1 2.0, es posible declarar propiedades con
mtodos de dierentes isibilidades. Por ejemplo:
public int NombreCompleto
{
get { }
internal set { }
}
Ln este caso, cualquier clase, en cualquier ensamblado, puede leer el alor de la propiedad. Por el
contrario, al declarar el mtodo set como internal, slo podran escribir sobre la propiedad las cla-
ses situadas dentro del mismo ensamblado que la clase a la cual pertenece la propiedad.
|ndexadores
Un indexador, o indexer, es un tipo muy especial de propiedad que sire para simular el comporta-
miento de los tipos de ectores ,arrays,. Imagine que estamos hablando de C, y que escribo en la
pizarra la siguiente expresin:
c[0]
Para simpliicar, supongamos que c es una ariable local. ,Qu podemos inerir sobre el tipo de esta
ariable Si el lenguaje es C, entonces la ariable es un ector de algn tipo de datos. ,\ si el len-
guaje es C-- labra que andar con mas cuidado: puede que c sea un ector, pero puede darse
tambin el caso que se trate de una ariable de tipo clase ,o estructura,, para la cual se haya
sobrecargado el signiicado del operador [].
Ln C
4
existe la sobrecarga o redeinicin de operadores, aunque es bastante dierente de la sobre-
carga en C--. Los autores de C
4
decidieron no permitir la sobrecarga de los corchetes, sustituyn-
dola por el uso de indexadores, que es una tcnica mas potente. De entrada, cuando se redeinen los
corchetes en C--, slo se puede usar un ndice o argumento, mientras que los indexadores admiten
cuantos argumentos o ndices deseemos dentro de los corchetes. Ademas, para que la sobrecarga de
corchetes en C-- permita la asignacin, el tipo de retorno del operador tiene que ser una referen-
cia, en el sentido de C--. Por el contrario, como los indexadores estan basados en dos mtodos de
acceso, como las restantes propiedades, tenemos mas libertad para deinir el signiicado de la
asignacin.
Ls un poco complicado encontrar un ejemplo de indexador que merezca la pena, y no es porque se
usen poco. La mayora de los ejemplos que tienen sentido son ejemplos en que slo se permiten las
lecturas, y los ejemplos con lecturas y escrituras terminan usando alguna estructura de datos priada
que esta basada, a su ez, en una clase con indexadores. lar el intento, de todas maneras:
Vamos a deinir una clase para representar ectores en tres dimensiones. Cada ector almacena tres
alores reales, uno por cada eje de coordenadas:
Componentes
Copyright 2004-200S, Ian Marteens
63
63
public class Vector
{
private double x, y, z;
public double X { get { return x; } set { x = value; } }
public double Y { get { return y; } set { y = value; } }
public double Z { get { return z; } set { z = value; } }

}
Ahora bien, en muchos algoritmos, es mas cmodo acceder a los componentes del ector por me-
dio de subndices. Ln ez de v.X, podramos preguntar por v[0], y en ez de v.Y, nos aldra
v[1]. Lste eecto lo podramos conseguir mediante un indexador dentro de la clase Vector:
public double this[int index]
{
get
{
switch (index)
{
case 0: return x;
case 1: return y;
case 2: return z;
}
throw new ArgumentOutOfRangeException("Indice incorrecto");
}
set
{
switch (index)
{
case 0: x = value; return;
case 1: y = value; return;
case 2: z = value; return;
}
throw new ArgumentOutOfRangeException("Indice incorrecto");
}
}
Un indexador se deine como una propiedad, excepto que:
Ll nombre siempre es la palabra clae this.
A la palabra clae this le sigue una lista de parametros encerrada entre corchetes. 1odos los
parametros deben pasarse por alor.
Al igual que ocurre con las otras propiedades, tenemos que deinir un mtodo de acceso de lectura
y, de manera opcional, un mtodo de acceso para escritura. Ambos mtodos reciben los parametros
declarados entre corchetes. Ll mtodo de lectura debe deoler un alor con el mismo tipo que el
de la propiedad, y el mtodo de escritura, aunque no deuele nada, recibe un parametro adicional
llamado siempre value, cuyo tipo es el de la propiedad.
Ln el ejemplo del ector, hay un solo parametro para el indexador, y es de tipo entero. Sin embargo,
slo consideramos correctos tres de los posibles alores que permite un entero. Si alguien intentar
pasar otro alor, respondemos con una excepcin.
C
4
nos permite declarar cuantos indexadores se nos antoje, siempre que se puedan distinguir entre
ellos por medio de sus argumentos. Por ejemplo, la clase DataRow, que representa una ila de una
tabla en memoria, deine seis indexadores dierentes. 1res de ellos tienen un solo parametro, y
permiten lecturas y escrituras:
public object this[DataColumn column] { get; set; }
public object this[int columnIndex] { get; set; }
public object this[string columnName] { get; set; }
Los tres restantes admiten dos parametros, pero slo permiten la lectura:
Intuitive C
#

Copyright 2004-200S, Ian Marteens
64
64
public object this[DataColumn column, DataRowVersion version] { get; }
public object this[int columnIndex, DataRowVersion version] { get; }
public object this[string columnName, DataRowVersion version] { get; }
Ln realidad, el Common Language Runtime permite mas libertades con los indexadores que las
soportadas por C
4
. Mientras que C
4
nos obliga a usar this para declarar un indexador, la propia
plataorma nos permite emplear cuantos nombres deseemos. y para cada uno de estos nombres,
podemos deinir todas las ersiones que queramos, siempre que se puedan distinguir entre s por
sus argumentos. VB.NL1 no sure esta restriccin. Los seis indexadores de DataRow realmente se
llaman Item, y con este nombre aparecen en la ayuda de la plataorma.
N
O
T
A

Desconozco la razn tras esta limitacin de C
#
. Quizas los autores encontraron ambigedades en la
interpretacin cuando se levanta la restriccin. En todo caso, he mencionado este asunto porque va
a tropezar con la dichosa y misteriosa propiedad Item con mucha frecuencia.

Prop|edades con |mp|ementac|n automt|ca
C
4
3 ha anadido una pequena, aunque largamente esperada, noedad. Se trata de propiedades con
implementacin automatica, o si lo preiere, implcita. La mayor parte de las propiedades, en la
practica, se declaran de manera preentia`: sabemos que es mala idea dejar un campo a la
intemperie, y nos curamos en salud encapsulando su acceso dentro de una propiedad. Por lo tanto,
y al menos en su orma inicial, la mayora de las propiedades terminan implementandose directa-
mente a tras de un campo.
Ahora podemos ahorrarnos algo de tiempo declarando propiedades de la siguiente manera:
public struct Vector
{
public double X { get; set; }
public double Y { get; set; }
public double Z { get; set; }

}
La sintaxis es casi idntica a la de las propiedades abstractas. pero alta el modiicador abstract.
Ll compilador declara internamente un campo del tipo apropiado, con isibilidad priada, e imple-
menta los mtodos de acceso correspondientes. Como el campo no tiene un nombre conocido,
slo podemos manejarlo indirectamente, a tras de la propiedad.
Obsere que es necesario declarar ambos mtodos de acceso. Si quisiramos una propiedad que no
pueda modiicarse uera de la clase que la declara, tendramos que incluir un modiicador de isibili-
dad al declarar el mtodo de escritura:
public double X { get; private set; }
La implementacin, sin embargo, no es la mejor posible. Si se accede a la propiedad desde la misma
clase que la declara y la propiedad no se ha declarado irtual, el compilador podra traducir auto-
maticamente las reerencias a la propiedad por reerencias al campo base. Pero, al menos en esta
ersin, el compilador insiste en utilizar los mtodos de acceso.
Otro problema tiene que er con la inicializacin del campo base de la propiedad: no podemos
utilizar un inicializador. Si la propiedad se deine dentro de una estructura, tendremos problemas
adicionales para darle un alor inicial a la propiedad que no sea el alor por omisin. Supongamos
que deseamos un constructor para la estructura Vector que hemos usado como ejemplo. Ll siguiente
cdigo sera lo mas natural. aunque el compilador no lo acepte:
// ERROR: el compilador se quejar porque Vector es una estructura.
public Vector(double x, double y, double z)
{
this.X = x;
this.Y = y;
this.Z = z;
}
Componentes
Copyright 2004-200S, Ian Marteens
65
65
La culpa la tiene la regla de asignacin deinida de C
4
, que exige en este caso que todos los campos
de una estructura estn explcitamente inicializados antes de llamar a cualquier mtodo. Como la
asignacin a una propiedad automatica se traduce en una llamada al mtodo de escritura, el
compilador protesta en la primera lnea del cdigo. Por ortuna, hay un truco para salir del paso:
public Vector(double x, double y, double z) : this()
{
this.X = x;
this.Y = y;
this.Z = z;
}
La llamada al constructor implcito resuele el problema de la asignacin deinida, aunque al precio
de inicializaciones innecesarias. Con un poco de suerte, el compilador JI1 podra eliminar las
asignaciones redundantes.
Eventos
,Por qu Microsot se tom tantas molestias para permitir la combinacin de delegados en cade-
nas La respuesta es que podemos usar los tipos delegados para declarar eventos dentro de una
clase. \a he presentado los eentos cuando eamos qu tipo de miembros puede contener una
declaracin de clase. Muchos programadores conunden al principio los dos conceptos, el de eento
y el de tipo delegado, pero basta con recordar un par de hechos para aclarar las ideas:
Un delegado es un tipo de datos.
Un eento es un tipo de miembro que es admitido por las clases, estructuras y tipos de interaz.
Ll eento se declara utilizando un tipo delegado.
Antes dije tambin que los eentos seran para implementar notiicaciones. Ln eecto: una clase
como Button puede declarar un eento llamado Click. Los usuarios de la clase pueden enganchar
delegados a la cadena de delegados que puede colgar de este eento. Lste acto se interpreta como
una suscripcin al eento. Cuando la clase Button detecta que alguien ha pulsado el control, actia
los mtodos enganchados a la cadena deinida por Click.
Un eento, en una primera aproximacin, se parece mucho a una propiedad de tipo delegado.
pero sera incorrecto. Para er la dierencia, usaremos el siguiente tipo delegado, deinido en System:
public delegate void EventHandler(object sender, EventArgs e);
No nos interesa ahora el papel de estos parametros, pero debe saber que, aunque potencialmente
podramos declarar eentos con tipos arbitrarios de delegados, .NL1 establece el conenio de que
un tipo delegado asociado a un eento debe tener siempre dos parametros. Ll primero de ellos debe
ser siempre de tipo object, y el segundo debe pertenecer a la clase EventArgs, o a una clase deriada
de la misma.
Ahora declaremos una propiedad y un eento dentro de una clase, usando el tipo EventHandler:
public class Clase01
{
private EventHandler campo;
public EventHandler Propiedad
{
get { return campo; }
set { campo = value; }
}
public event EventHandler Evento;
}
La primera dierencia es que una propiedad exige una implementacin, mientras que slo declara-
mos el eento y dejamos que el compilador proporcione una implementacin. Lsta no es una
dierencia undamental, sin embargo, entre otras razones porque podemos darle una implementa-
Intuitive C
#

Copyright 2004-200S, Ian Marteens
66
66
cin explcita al eento. La dierencia realmente importante se maniiesta durante el uso de la
propiedad y el eento. Para concretar, suponga que estamos dentro de una segunda clase:
public class Clase02
{
private void ManejadorCompatible(object sender, EventArgs e)
{
System.Console.WriteLine("yoo-hoo!");
}

public void Prueba(Clase01 cls01)
{
// Usemos la propiedad
cls01.Propiedad = new EventHandler(ManejadorCompatible);
// Usemos el evento
cls01.Evento += new EventHandler(ManejadorCompatible);
// Esto produce un error de compilacin!
cls01.Evento = new EventHandler(ManejadorCompatible);
}
}
Podemos asignar un alor a una propiedad, siempre que sta permita escrituras. Pero no podemos
asignar directamente un alor a un eento. No obstante, parece que podemos anadir, con el opera-
dor de asignacin y suma, y le aseguro que tambin podemos quitar` de un eento, con el imagina-
ble operador compuesto de asignacin y resta:
// Sera tambin correcto!
cls01.Evento -= new EventHandler(ManejadorCompatible);
La clae esta en las cadenas de delegados. Cuando exponemos una propiedad de tipo delegado,
estamos permitiendo a que un usuario de la clase que la contiene anada manejadores o quite
manejadores de la cadena de delegados a cuyo inicio apunta la propiedad. Pero tambin nos
arriesgamos a que un programador despistado asigne sin mas su propio manejador a la propiedad, y
se cargue de este modo una hipottica cadena ya existente. Lso es precisamente lo que se eita al
declarar un eento: el eento no puede ser asignado desde uera de la clase donde se deine, y slo
podemos usar los dos operadores mencionados para su manipulacin.
6|ases parc|a|es
la pasado mucho tiempo desde la inencin de Pascal por Niklaus \irth. Desde entonces, el hard-
ware ha mejorado espectacularmente. Ll sotware y las ideas relacionados con el sotware han cam-
biado con mucha mas lentitud. 1omemos como ejemplo los compiladores: en los anos 0, las
limitaciones de memoria dinamica obligaba a disenarlos como maquinas que transormaban un
lujo de datos en arias pasadas. Lste diseno sigui utilizandose cuando ya haban desaparecido las
limitaciones que lo haban motiado.
Por desgracia, esta caracterstica de la implementacin del compilador se propag al diseno de los
propios lenguajes de programacin. Un ejemplo clasico ocurre en Pascal. Suponga que necesitamos
dos procedimientos A y B. Ll procedimiento A llama al procedimiento B, y este necesita tambin
llamar al procedimiento A. Ln Pascal clasico`, tendramos que utilizar una declaracin forward
para permitir la recursiidad mutua:
{ Esto es Pascal }
procedure B; forward;
procedure A;
begin

end;
procedure B;
begin

end;
Componentes
Copyright 2004-200S, Ian Marteens
6
67
La declaracin que termina con forward es un anuncio de la erdadera declaracin, que tiene lugar
mas adelante. Sin ella, cuando compilasemos el mtodo A, no sabramos cual es el prototipo real del
mtodo B. Ln Delphi, la mayora de estas restricciones se han suaizado, pero todaa encontramos
requerimientos en el orden de declaracin que son consecuencia indirecta de las limitaciones de
hardware en la dcada de los 0s.
Ln este momento, la mayora de los compiladores para lenguajes modernos asumen que es posible
guardar una representacin completa del proyecto que compilan en la memoria dinamica del
ordenador. La ase de analisis sintactico crea una estructura de datos llamada rbol de Sintaxis Abs-
tracta, o AS1. Las restantes ases de la compilacin trabajan sobre esta estructura. Ln la plataorma
.NL1, esta tendencia se llea a sus ltimas consecuencias. Cuando se compila un proyecto, el resul-
tado debe ser el mismo que si mezclaramos todos los icheros de cdigo del proyecto en un nico y
gigantesco ichero uente.
Lste comportamiento sire para explicar un til recurso anadido a C
4
2.0: las clases parciales. Ln la
primera ersin, todo el cdigo correspondiente a una clase deba escribirse en el mismo ichero, de
manera continua. Ahora, por el contrario, podemos ragmentar la deinicin de una clase. Los rag-
mentos pueden escribirse en icheros dierentes, o incluso dentro de un mismo ichero. Por ejem-
plo, esto es alido ,aunque absurdo,:
public partial class A
{
public A() { }
public int Valor
{
get { return valor; }
set { valor = value; }
}
}
public class B
{

}
public partial class A
{
private int valor;
}
La clase A ha sido diidida en dos ragmentos, y ambos ragmentos deben utilizar el modiicador
partial para que el compilador acepte esta diisin. Para resaltar la diisin, he intercalado una
segunda clase, aunque no es necesario: los ragmentos podran incluso escribirse uno detras del
otro. Ln el primer ragmento de A he declarado un constructor pblico y una propiedad. Ll se-
gundo slo contiene la declaracin del campo priado que se utiliza para implementar la propiedad.
Lste reparto de declaraciones es completamente arbitrario: puedo distribuir las declaraciones en la
orma que me apetezca.
Ln el ejemplo anterior, la clase A heredaba directamente de System.Object, y no implementaba tipos
de interaz. Supongamos ahora que A debe implementar la interaz IDisposable ,p.3,:
public partial class A : IDisposable
{
public A() { }
}
partial class A
{
public void Dispose()
{

}
}
Intuitive C
#

Copyright 2004-200S, Ian Marteens
68
6S
Podamos haber repetido la mencin al tipo de interaz en ambos ragmentos, pero como muestra
el ejemplo, podemos incluir la mencin en slo uno de ellos. Ls mas, esta ez he omitido el
modiicador de isibilidad de la clase en el segundo ragmento. Ll mismo principio es aplicable a la
declaracin de una clase ancestro: podemos repetir la declaracin en cada ragmento, o podemos
omitirla en uno o mas de ellos.
A pesar de la aparente inocuidad de las clases parciales, Visual Studio 2005 le saca buen partido. Ln
la ersin 2003, Visual Studio ubicaba el cdigo generado automaticamente para ormularios,
conjuntos de datos y componentes en el mismo ichero donde el programador escriba su propio
cdigo. La siguiente imagen muestra los elementos de un proyecto de aplicacin recin creada en
Visual Studio 2003:

Al ichero Form1.cs, que es el que corresponde a la entana principal, se le asocia un ichero de
recursos, de extensin resx, usado internamente por Visual Studio. No obstante, como ya he dicho,
tanto el cdigo generado por el entorno de desarrollo como el cdigo escrito por el usuario, utilizan
el mismo ichero. con todos los peligros que puede imaginar. Lsta otra imagen muestra lo que
ocurre en Visual Studio 2005:

Por algn motio, ya no aparece el ichero resx, pero tenemos un nueo ichero de extensin cs. Ls
decir, tenemos un nueo ichero con cdigo en C
4
asociado al ormulario. Ln este nueo ichero,
Visual Studio genera las instrucciones necesarias para inicializar el ormulario y sus controles. Ll
ichero Form1.cs es ahora completamente nuestro. La clase Form1 se declara en Form1.Designer.cs
de esta manera:
partial class Form1
{

}
No se menciona ni la clase base ni la isibilidad del ormulario. Ll segundo ragmento de la clase
Form1, en el ichero Form1.cs, comienza as:
public partial class Form1: Form
{

}
Componentes
Copyright 2004-200S, Ian Marteens
69
69
Htodos parc|a|es
Visual Studio genera cdigo basado en clases parciales con bastante recuencia, pero hay un pro-
blema con las clases parciales que nos puede complicar la ida: las tcnicas de inicializacin de la
clase. Normalmente, parte de la inicializacin se realiza en las propias declaraciones de campos, y el
resto se encapsula dentro de los constructores de la clase. Las cosas se ponen eas cuando tenemos
una clase diidida en dos ragmentos, con los constructores declarados en el ragmento que no
podemos modiicar manualmente. Por ejemplo:
// "Nuestro" fragmento
public partial class A
{

}
// Fragmento administrado por Visual Studio.
partial class A
{
private int i;
public A()
{
this.i = 1111;
}
}
Si tuisemos que anadir un nueo campo dentro de nuestro ragmento, para uso propio, ya no
podramos inicializarlo en el constructor, pues no podemos tocar el constructor. Si para inicializar el
campo nos bastase una expresin de inicializacin, no habra mayor problema. 1enga en cuenta, no
obstante, que las expresiones de inicializacin de campos de instancia tienen unas cuantas limitacio-
nes. Por ejemplo, no se puede inicializar un campo de instancia mediante una llamada a un mtodo
de instancia de la misma clase.
La ersin 3 de C
4
orece una solucin a este problema: los mtodos parciales. Ls una solucin
parcial`, para las diicultades con la inicializacin, pues requiere colaboracin en los dos ragmen-
tos, pero hay que reconocer que su alcance es mucho mayor. Ademas, la colaboracin no implica un
coste adicional en tiempo de ejecucin, como eremos enseguida.
La idea es muy simple: igual que se puede declarar dos ragmentos de una clase mediante el
modiicador partial, podemos tambin declarar dos ragmentos de un mismo mtodo. La dieren-
cia es que slo podemos incluir una implementacin del mtodo en uno de los ragmentos:
// Fragmento administrado por Visual Studio.
partial class A
{
partial void OnCreate();
public event EventHandler Evento;
public A()
{
OnCreate();

}
}
// "Nuestro" fragmento
public partial class A
{
private void Respuesta(object sender, EventArgs e)
{

}
partial void OnCreate()
{
Intuitive C
#

Copyright 2004-200S, Ian Marteens
0
70
this.Evento += Respuesta;
}
}
Ln el ejemplo anterior, el primer ragmento anuncia la posible existencia de un mtodo llamado
OnCreate, cuyo cuerpo no se incluye en el anuncio. No obstante, el constructor de la clase se per-
mite el lujo de ejecutar dicho mtodo, que puede existir o no en otro ragmento. Si por esas
casualidades nadie diese ida a OnCreate, la llamada a OnCreate simplemente no generara cdigo
alguno. ,Adiina cual es una de las restricciones de los mtodos parciales Lectiamente: su tipo de
retorno debe ser void ,,por qu,. Ln este ejemplo, por el contrario, el segundo ragmento propor-
ciona un cuerpo a OnCreate, y dentro de ese cuerpo aproechamos para enganchar un manejador a
un eento. Lste es un tipo de inicializacin que, por cierto, no se podra conseguir con inicializado-
res de campos.
Naturalmente, podemos ejecutar mtodos parciales no slo desde un constructor, sino dentro de
cualquier otro cdigo de la misma clase. Obsere que el mtodo parcial OnCreate se declara sin el
modiicador de acceso: se trata de un asunto interno de la clase parcial, y se asume que el mtodo
parcial, si se llega a completar, sera priado y por lo tanto, inisible uera de la clase que lo declara y
utiliza.


Copyright 2004-200S, Ian Marteens
71

ADMN8TRACON DE MEMORA

NA CARAC1LRS1ICA COMN A .NL1 y la maquina irtual de Jaa es la existencia de un
recolector de basura ,garbage collector,, con el propsito de automatizar la liberacin de
memoria. Los algoritmos basicos de este tipo existen desde hace muchsimo tiempo. Ln la dcada
de los 80s se produjeron algunos aances importantes, como la tcnica llamada generation scaveng-
ing, o barrido generacional, que mejoraron los tiempos de ejecucin del algoritmo y lo hicieron mas
atractio uera de las areas especializadas donde se utilizaba. A inales de los 80s y principios de los
90s, el lenguaje Liel populariz la idea. Se trataba de un lenguaje orientado a objetos muy com-
pleto y elegante que incorporaba el manejo automatico de la memoria con un recolector de basura.
1anto Jaa como los lenguajes de la plataorma .NL1 aproechan las experiencias acumuladas en el
diseno y uso de Liel.
Recog|endo |a basura
Para ser sinceros, siempre he sentido recelos hacia este tipo de algoritmos. y sigo desconiando.
No obstante, es cierto que la recoleccin automatica de basura es una jugada obligada en el diseno
de .NL1, y la justiicacin principal es la necesidad de contar con tcnicas seguras y eicientes para
el manejo de memoria en entornos distribuidos. Puede parecer extrana esta justiicacin, pero es
completamente cierta. De modo que hay que acostumbrarse a que la basura se eapore misteriosa-
mente ante nuestras narices, aunque a algunos no nos entusiasme excesiamente la idea.
Debo reconocer que la tcnica de recoleccin de basura tiene cierto regusto Zen. Puede incluso que
al obispo Berkeley le hubiese agradado. La idea se resume as:
Si nadie te ve, no existes
O mas exactamente: si no existen reerencias a cierto objeto desde un objeto io, el objeto en
cuestin esta muerto, y su memoria puede ser liberada. De ez en cuando, se disparara un hilo de
ejecucin que se lleara todas estas almas muertas a Neerland. ,o era al \alhalla Como puede
er, todo se basa en deinir qu es lo que esta io y qu es lo que esta muerto, y al parecer, la
deinicin es recursia. De modo que pongamonos ormales:
1 Deinamos un conjunto de objetos vivos iniciales, a los que llamaremos races.
1odos los objetos a los que se pueda acceder a tras de un campo estatico de una clase, se
consideran races.
Ln un momento dado, congelemos la pila de llamadas de la aplicacin junto con sus corres-
pondientes zonas de ariables locales. Ln ese momento, cualquier objeto apuntado desde
cualquier ariable local actia, se considera tambin una raz.
2 1omemos todas las reerencias a otros objetos desde un objeto io: todos esos objetos
reerenciados estaran tambin vivos.
,Ls eiciente la recoleccin de basura de .NL1 ,Se nota cuando tiene lugar No se preocupe: ya no
estamos en los tiempos de los antiguos intrpretes de LISP y Prolog, y sobre todo, nuestros
ordenadores no son ya los de aquella lejana poca. le estado trabajando mucho tiempo en el
desarrollo de un compilador de lnea de comandos para un lenguaje compatible con .NL1. Durante
la ejecucin del compilador se crean multitud de pequenos objetos estrechamente relacionados
entre s. La compilacin de un proyecto de unos pocos miles de lnea no necesita ni siquiera un
O
Intuitive C
#

Copyright 2004-200S, Ian Marteens
2
72
segundo, y la mayor parte de este tiempo se consume en leer inormacin sobre los ensamblados
que se an a usar como reerencia. Ln una etapa posterior del proyecto intentaremos disminuir ese
tiempo probando un par de tcnicas y optimizaciones.
Pues bien, durante ese par de dcimas de segundos, el entorno de ejecucin puede llegar a actiar
dos o tres eces la recoleccin de basura. Como entendera, el tiempo de ejecucin de este algoritmo
no es notable, ni siquiera en este caso extremo.
F|na||zac|n y destrucc|n determ|n|sta
No obstante, la recoleccin automatica de basura tiene un lado muy oscuro: nadie sabe cuando a a
tener lugar, y esto astidia el uso de una de las tcnicas mas atractias de la programacin orientada
a objetos. Ln palabras de Bjarne Stroustrup, el creador de C--:
Object creation is resource allocation
1raducido al romance: la creacin de objetos es equialente a la resera de recursos. O con mayor
claridad: podemos encapsular recursos` dentro de clases, de modo que la creacin de una instancia
de estas clases implique la obtencin de una instancia de estos recursos`. Ln este contexto, re-
curso` signiica cualquier objeto o entidad potencialmente costosa, que exija de nosotros una opera-
cin explcita de peticin pareada con una operacin explcita de deolucin. Si cuesta, debemos
deolerlo, ,no Dentro de esta categora entran entidades tales como las transacciones en un sis-
tema de bases de datos, icheros abiertos, semaoros del sistema operatio, la aprobacin de uso de
uno de estos semaoros, la mayora de los objetos que se utilizan para dibujar en pantalla. y la lista
podra estirarse. Como curiosidad, obsere que la mayora de estas entidades son en realidad instan-
cias de objetos deinidos en un niel mas bajo del sistema.
Ll problema nunca se presenta al pedir uno de estos recursos, sino a la hora de deolerlos. Sobre
todo, porque debemos garantizar la deolucin de los mismos. Si no cerramos los icheros que
abrimos, podemos perder la capacidad de abrir mas icheros. Si no terminamos correctamente las
transacciones que iniciamos, podemos perder los cambios que hayamos hecho en el contexto de
estas, ademas de que estaramos inteririendo en el trabajo de los restantes usuarios de la base de
datos. Si seguimos los pasos de Stroustrup y asociamos la concesin de un recurso al estado de una
instancia, es normal que asociemos la deolucin del recurso a la destruccin de la instancia.
. y es aqu donde empezamos a tirarnos de los pelos, porque en un sistema con recoleccin
automatica de basura no podemos predecir cuando tendra lugar la siguiente limpieza de la memoria:
sera cuando el sistema lo estime oportuno. Puede que nunca. Ln cualquier caso, C
4
nos permite
escribir cdigo para que se ejecute cuando un objeto se conierta en basura y pase a mejor ida. La
sintaxis necesaria recuerda el uso de destructores en C--:
public class ClaseConRecurso
{
private Recurso recurso;
ClaseConRecurso()
{
// Este es el constructor
}
ClaseConRecurso()
{
// Parece un destructor, pero es un finalizador
if (recurso != null)
recurso.Liberar();
}
}
No se apresure a sacar conclusiones a partir de este ragmento de cdigo, para empezar, hay que
tener mucho cuidado con la clase Recurso. Si, por ejemplo, Recurso uese una clase CLR como File-
Stream, sera incorrecto, e incluso peligroso, usar la inalizacin! Luego explicar por qu. De mo-
mento, tenemos dos problemas con la inalizacin. \a hemos mencionado el primero de ellos:
Administracin de memoria
Copyright 2004-200S, Ian Marteens
3
73
nunca sabremos en qu momento se a a ejecutar. Ll segundo es casi tan grae: cuando el colector
de basura encuentra una instancia con cdigo de inalizacin, la muee a una cola de inalizacin, y
utiliza un hilo separado para ejecutar el cdigo de inalizacin de cada miembro de la cola. Lsto
puede llegar a ser muy costoso.
A la ista de todas estas diicultades, muchos optan por no aplicar la maxima de Stroustrup, y utili-
zar mtodos normales` para reserar recursos y liberarlos posteriormente. Pero se trata de una
cobarde rendicin: es muy acil olidar la llamada al mtodo que deuele el recurso. Ln todo caso,
hemos complicado las cosas anadiendo el sistema de recoleccin de basura para automatizar la
deolucin de uno de los recursos mas baratos: la memoria libre. Sin embargo, seguimos indeensos
rente al problema de la gestin automatica del resto de los tipos de recursos. Los demonios
aparentemente exorcizados uelen a asomar sus eos rostros.
No hay orma de eadirse de estos problemas. La plataorma .NL1 orece un remedio parcial: un
patrn de uso conocido como destruccin determinista. Ln ez de dejar que el autor de cada clase
se inente un nombre dierente para el mtodo de liberacin, .NL1 nos propone que lo llamemos
siempre Dispose. Mas an, nos aconseja que aisemos que nuestra clase tiene recursos que deben
ser liberados haciendo que la clase implemente el tipo de interaz IDisposable:
public interface IDisposable
{
void Dispose();
}
De modo que si encontramos una clase que implementa IDisposable, sabremos con certeza total
que debemos garantizar la ejecucin de su mtodo Dispose. Ahora bien, incluso este requisito es
demasiado para algunos programadores. Ll patrn de uso de una clase con destruccin determinista
debera parecerse a esto:
ClaseConRecurso objeto = new ClaseConRecurso();
try
{
// Aqu usamos la instancia creada
}
finally
{
IDisposable d = objeto as IDisposable;
if (d != null) d.Dispose();
}
La instruccin try,finally igila la generacin de excepciones, si se produce alguna mientras usa-
mos la instancia, nos garantiza que se ejecute el cdigo incluido en la clausula finally. Por desgracia,
hay muchos libros en los que el autor olida esta simple regla de higiene. Lstos pecados no reciben
su merecido castigo de orma inmediata. 1an solo sucede que terminamos con una aplicacin poco
robusta entre manos, a la espera de que un pequeno allo se ampliique hasta conertirse en una
erdadera catastroe.
Para ahorrarnos lneas de programa, mejorar la legibilidad y hacer eidentes nuestras intenciones,
los disenadores de C
4
idearon la instruccin using:
using (ClaseConRecurso objeto = new ClaseConRecurso())
{
// Aqu usamos la instancia creada
}
Lstas cuatro lneas hacen el mismo trabajo que las diez lneas originales. Para no complicarme
demasiado, he usado una instruccin new en la cabecera de la instruccin, pero es posible usar
cualquier instruccin que deuela un objeto perteneciente a una clase que soporte IDisposable:
using (SqlDataReader rd = sqlCommand.ExecuteReader())
while (rd.Read())
Console.WriteLine(rd[0]);
Intuitive C
#

Copyright 2004-200S, Ian Marteens
4
74
Como puede er en estos sencillos ejemplos, una de las entajas adicionales de la instruccin using
es que hace coincidir el tiempo de ida del objeto asignado a la ariable con el interalo en el que la
clase encapsula el recurso presuntamente alioso. Despus de ejecutar Dispose, el objeto se con-
ierte en un zombie, en un cascarn aco sin alma. Sera arriesgado poder seguir usando la instancia
despus de que sus recursos se hayan liberado.
N
O
T
A

El ejemplo que utiliza la clase SqlDataReader muestra una tcnica de uso frecuente en el acceso a
datos, especialmente en ASP.NET v1.1. A pesar de ello, los ejemplos equivalentes en la documenta-
cin oficial del SDK no utilizan una instruccin using para garantizar la liberacin de recursos. Ni
siquiera se utiliza tryffinally o se advierte del problema que puede ocasionar una excepcin fuera
de control.

Precauc|ones en un mundo fe||z
Coniene que resuma mi opinin sobre la recoleccin automatica de basura:
1 Un sistema orientado a objetos que soporte objetos distribuidos debe implementar algn tipo
de gestin automatica de la memoria. COM,COM- usaba contadores de reerencia, mientras
que .NL1 utiliza recoleccin de basura.
2 Ls muy probable que algunos de los disenadores de .NL1 estuiesen sinceramente entusiasma-
dos con la perspectia de usar garbage collection.
3 Creo, sin embargo, que esta tcnica es un mal menor. Ls cierto que resuele el problema de la
liberacin de la memoria ocupada por instancias dinamicas, pero deja sin resoler el problema
mucho mas grae de la liberacin de otros tipos de recursos aliosos.
4 Ls incluso posible que las primeras aplicaciones para .NL1 que salgan al mercado ean resen-
tida su estabilidad por los errores a los que puede inducir la alsa conianza que proporciona la
recoleccin de basura.
Por supuesto, mi ltima airmacin necesita ser demostrada: la recoleccin automatica de basura
puede proocar graes meteduras de pata. Volamos por un momento a uno de los ejemplos ya
mostrados:
public class ClaseConRecurso
{
private Recurso recurso;
ClaseConRecurso()
{
// Este es el constructor
//
}
ClaseConRecurso()
{
// Este es el finalizador
}
}
Cuando present esta clase, eit cuidadosamente dar demasiados detalles sobre el tipo Recurso.
Identiiquemos temporalmente la misteriosa clase con una clase cualquiera del CLR, como File-
Stream:
private FileStream recurso;
Pregunta capciosa: ,es correcto hacer lo que muestra el siguiente inalizador
ClaseConRecurso()
{
if (recurso != null)
recurso.Dispose();
}
Administracin de memoria
Copyright 2004-200S, Ian Marteens
5
75
Respuesta: es un peligroso disparate! Razonemos: ,por qu se esta ejecutando el inalizador Re-
sulta que el objeto de tipo ClaseConRecurso ha muerto porque nadie lo miraba. Si nadie lo miraba,
,habra alguien mirando al objeto enganchado a recurso Lo mas probable es que no, porque se
trata de un campo oculto dentro de la clase. Lntonces, el nico objeto que mira al objeto engan-
chado en recurso, es un cadaer! ,si, es ttrico,. Para lo que iene a continuacin es importante el
hecho de que FileStream es una clase perteneciente a .NL1. Lsto signiica que FileStream sigue los
conenios de la plataorma, y tiene un inalizador para cuando alguien olide cerrar el ichero antes
de perderlo de ista. Por lo tanto, el ichero recurso esta tambin en la cola de inalizacin del colec-
tor de basura. ,Cual objeto exprimira primero el colector, al de tipo ClaseConRecurso o al ichero
Ls imposible saberlo! Ls mas, es probable que la reerencia al ichero siga siendo alida, pero esto
es algo que depende de la implementacin concreta del colector de basura: despus de una pasada
del colector, se compacta la memoria dinamica. Ni siquiera se nos garantiza que el ichero siga ah!
Ls cierto que, descontando este problema con los inalizadores, la recoleccin de basura hace
imposible que utilicemos objetos ya liberados. Como compensacin, el nueo problema mas re-
cuente consiste en que objetos innecesarios sean mantenidos ios de orma artiicial, y sin que
seamos conscientes de ello. Una orma muy sencilla de proocar este error guarda relacin con los
manejadores de eentos. Para proocar el problema necesitamos un objeto de data duracin, o
incluso una clase con estado estatico como Application. Lste objeto lanza ciertas notiicaciones me-
diante un eento. Ln el caso de Application, este eento podra ser Idle o incluso ThreadException.
Un objeto con tiempo de ida mas corto se interesa por estas notiicaciones y anade un manejador
a la correspondiente cadena de delegados.

Ll diagrama muestra el solapamiento de los tiempos de ida de los dos objetos inolucrados. lay
tres puntos marcados en la lnea del tiempo. Ln el primero, se crea el objeto emero. Ln el segundo
instante, el objeto emero se suscribe a un eento disparado por el objeto longeo. Por ltimo, el
programador cumple con sus deberes y ejecuta, directa o indirectamente, el mtodo Dispose del
objeto temporal, para que muera. Pero ha olidado desconectar el manejador de eentos. Como el
delegado contiene un puntero oculto al objeto manejador, el objeto que deba haber muerto sigue
estando en el directorio telenico del objeto de larga duracin. Si, por desgracia, este dispara el
eento, tendremos problemas con casi total seguridad, porque al llamar a Dispose el objeto se ha
conertido en un cascarn aco.
Objeto longevo
Objeto efimero
lnea del tiempo t0 t1 t2

Copyright 2004-200S, Ian Marteens
76
S SS S
GENERCDAD

I LN BIOLOGA ABUNDAN LOS LSCARABAJOS, en Inormatica lorecen listas de todo tipo:
listas en las que es rapido insertar y algo mas lento buscar, listas en las que la bsqueda a
como un relampago y las inserciones an como las bajadas de impuestos, listas para amar, listas para
odiar. Lo mejor, o lo peor, es que todas estas ariedades de listas se subdiiden a su ez en listas
de enteros, listas de cadenas, listas de alores reales, listas de personas, listas de echas y as hasta la
nausea.
T|pos genr|cos
Las dierencias entre una lista de echas y una de personas son mnimas. Sin embargo, en lenguajes
populares como Delphi y Jaa, y en la primera ersin del propio C
4
, si quisiramos listas de perso-
nas y de echas, tendramos que duplicar el trabajo, programando dos clases independientes.
La solucin a este problema son los tipos genricos, y se conoce desde hace tiempo gracias a lengua-
jes como CLU y Ada. Como en muchos otros casos, la idea se populariz gracias a C--, que
implementa una ariante sui generis de la idea bajo el nombre de templates o plantillas. Ll meca-
nismo de C-- es muy potente, pero esta plagado de problemas:
1 Ln el ondo, es un mecanismo de macro con eriicacin sintactica. Muchos problemas no se
detectan durante la deinicin del tipo genrico, sino cuando intentamos crear un tipo concreto
a partir del tipo genrico. Cuesta bastante localizar y arreglar estos problemas.
2 Como ocurre con muchas otras caractersticas de C--, el ormato OBJ no almacena inorma-
cin sobre tipos genricos. Lsto exige que, para reutilizar un tipo genrico, haya que depender
de los icheros de cabecera.
Jaa ha anadido recientemente tipos genricos a su repertorio, y su implementacin, aunque es mas
consistente que la de C--, sigue teniendo limitaciones: como la maquina irtual es un etiche
intocable para Sun, la implementacin corre a cargo del compilador. Ln tiempo de ejecucin se
pierde toda inormacin sobre genericidad, y para rematar, el cdigo generado esta plagado de
conersiones de tipos en tiempo de ejecucin.
Por el contrario, la implementacin de este recurso en la plataorma .NL1 es una joyita digna de
enmarcar. Ls la propia plataorma la que ha sido modiicada para que soporte la genericidad. Con
esto, se logran arios objetios:
1 Ln principio, todos los lenguajes que se ejecuten sobre la plataorma pueden implementar tipos
genricos, pues mas de la mitad del trabajo la tienen ya adelantada. Ln casos extremos, en los
que no merezca la pena complicar el lenguaje o el compilador, un lenguaje puede limitarse a
usar tipos genricos deinidos en otros lenguajes, sin permitir deinir nueos tipos.
2 Al contrario de lo que ocurre en Jaa, los nueos tipos genricos son ciudadanos de primera
clase de la plataorma. 1oda la inormacin relacionada con estos tipos esta disponible en
tiempo de ejecucin, por medio del API de relexin.
3 Lsto, a su ez, permite generar cdigo mas eiciente: al contrario de lo que ocurre en Jaa, no
hace alta anadir conersiones ocultas de tipos.
Resumiendo: si no le bastaban las razones para preerir .NL1 a Jaa, ahora tiene una mas.
S
Genericidad
Copyright 2004-200S, Ian Marteens

77
6||entes y proveedores de t|pos genr|cos
Pongamonos de acuerdo con la terminologa: llamaremos cliente de un tipo genrico al cdigo que
utilice un tipo genrico deinido en alguna otra parte, y llamaremos proveedor al cdigo que deine
un tipo genrico. Ls posible ser, al mismo tiempo, cliente y proeedor de tipos genricos. Lsta
distincin es til para explicar el uso habitual de la genericidad:
Ls muy probable que usted sea cliente de tipos genricos deinidos en otra parte, principal-
mente, por la propia plataorma.
Ls poco probable que usted deine tipos genricos propios ,en circunstancias normales,.
La causa de esto es, en gran medida, el propio espritu de la programacin orientada a objetos:
escribir una ez, usar muchas eces, que en este caso llega casi al extremo. Lste es tambin el mo-
tio por el que utilizar un ejemplo bastante isto para mostrarle cmo se deine una clase genrica:
public class Pila<T>
{
private T[] elementos;
private int cantidad;
public Pila(int capacidad)
{
this.cantidad = 0;
this.elementos = new T[capacidad];
}
public void Aadir(T valor)
{
elementos[cantidad++] = valor;
}
public T Extraer()
{
return elementos[--cantidad];
}
public bool EstaVacia
{
get
{
return cantidad == 0;
}
}
}
Al declarar la clase Pila, hemos anadido un parametro a la declaracin y lo hemos llamado T. Ln el
resto de la declaracin, he resaltado las reerencias a dicho parametro. Ln todos los casos, T se uti-
liza como si uese un tipo de datos cualquiera: declaramos un ector de elementos de tipo T, iniciali-
zamos el ector con el operador new, asignamos un parametro de este tipo en un elemento de ec-
tor del mismo tipo, y deolemos alores de tipo T. No importa cual sea el tipo exacto de T, pues
todos los tipos soportados por .NL1 permiten sin excepcin las operaciones mencionadas.
A la hora de usar el tipo Pila, lo mas recuente es darle un tipo concreto al parametro T:
Pila<int> p = new Pila<int>(128);
p.Aadir(1000); p.Aadir(2000); p.Aadir(3000);
Console.WriteLine(p.Extraer());
Como puede imaginar, no podemos anadir una cadena a una pila de enteros, y las pilas de dierentes
tipos bases no son compatibles.
Antes he dicho lo mas recuente` porque podemos aproechar la clase Pila postergando la asigna-
cin de un tipo concreto. Lso es lo que ocurrira si usaramos la pila dentro de la declaracin de otra
clase genrica. Por ejemplo:
Intuitive C
#

Copyright 2004-200S, Ian Marteens
8
7S
public class Compilador<TipoNodo>
{
private Pila<TipoNodo> pila;

}
Ln este caso, tenemos un parametro de tipo al que hemos llamado TipoNodo ,para demostrar que
los nombres de parametros pueden tener mas de una letra!, en la clase que se deine. Como un
parametro de tipo puede usarse en casi cualquier parte donde pueda usarse un tipo normal, esta ez
lo utilizamos para instanciar un caso concreto del tipo genrico Pila.
Cener|c|dad y restr|cc|ones
Si nos limitamos al comportamiento comn a todos los tipos de datos posibles, poca cosa podre-
mos hacer con los tipos genricos. Para poder ir mas lejos, necesitamos exigir condiciones adiciona-
les para los parametros de tipos. Suponga, por ejemplo, que necesitamos aeriguar cual es el alor
mnimo de los elementos almacenados en una pila. Para calcular el alor mnimo necesitamos
comparar. ,Soportan la comparacin todos los tipos en .NL1 No, porque las comparaciones son
odiosas. Si se tratase de comparar para aeriguar igual o desigualdad, podramos aproechar el m-
todo Equals que es soportado por todos los tipos en .NL1. Pero no es este tipo de comparacin la
que necesitamos para hallar el mnimo de una coleccin.
,La solucin Lxijamos que la clase genrica Pila slo se instancie con tipos que soporten la
comparacin. ,Qu signiica soportar comparaciones` Signiica que los tipos admitidos tendran
que implementar ciertos mtodos. ,No es eso mismo lo que garantizan los tipos de interaz Ob-
sere entonces la declaracin inicial de la nuea ersin del tipo Pila:
public class Pila<T> where T : IComparable
{

}
Ll tipo IComparable se deine as:
public interface IComparable
{
int CompareTo(object obj);
}
Ll mtodo CompareTo compara dos objetos y debe deoler un alor negatio, si el primer objeto
es el menor, un alor positio si el primero es el mayor, y cero si ambos objetos son iguales. Si los
objetos pertenecen a dierentes clases, el mtodo puede quejarse disparando una excepcin. Con
estas reglas del juego, podemos anadir el siguiente mtodo a la pila:
public T Menor()
{
if (cantidad == 0)
throw new Exception("Pila vaca!");
T menor = elementos[0];
for (int i = 1; i < cantidad; i++)
if (elementos[i].CompareTo(menor) < 0)
menor = elementos[i];
return menor;
}
A pesar de que sabemos que cualquier tipo que pasemos en T soportara IComparable, no por ello
podemos usar directamente el operador <. 1enemos que llamar explcitamente a CompareTo. ,Qu
tipos implementan dicha interaz Somos aortunados, porque la lista es grande. Por ejemplo, el tipo
System.Int32, el amoso int, implementa IComparable. De esta manera, podramos declarar pilas de
enteros, de reales, de tipos lgicos y en general, de todos los tipos que implementen la mencionada
interaz.
Genericidad
Copyright 2004-200S, Ian Marteens
9
79
Otro detalle: si la pila esta aca, Menor lanza una excepcin. Lsto es lo correcto, pues no existe un
alor menor` dentro de un conjunto aco.
Incluso podemos mejorar la implementacin. Ll mtodo CompareTo de IComparable recibe un
parametro de tipo object, y eso signiica que nos pueden pasar casi cualquier cosa en dicho parame-
tro, cuando realmente nos interesara limitar las comparaciones a tipos idnticos o compatibles. \
hay mas: si creamos una pila de tipos enteros, por ejemplo, para cada comparacin tendramos que
conertir el entero, que es una estructura, en un puntero a objeto. ,Cmo se llama esta operacin
Repita conmigo en oz alta: boxing! \ ya sabemos que el boxing es una operacin costosa que debe-
mos eitar a toda costa.
ICompare es una interaz heredada de la ersin 1.1, antes de la llegada de los tipos genricos. Ln
.NL1 2.0 existe una nuea interaz genrica, que es la que realmente tenemos que usar:
public interface IComparable<T>
{
int CompareTo(T obj);
}
1ome nota de cmo modiicamos la restriccin de tipo en la declaracin de la pila:
public class Pila<T> where T : IComparable<T>
{

}
Aunque parezca complicado, no hay nada ilegal en este uso de los parametros de tipos. Por ejemplo,
el tipo int esta realmente declarado de este modo:
public struct Int32 : IComparable, IFormattable, IConvertible,
IComparable<int>, IEquatable<int>
{

}
Ll tipo entero sigue soportando IComparable a secas por compatibilidad con .NL1 1.1, pero tam-
bin soporta la ersin genrica, que en muchos casos es mas eiciente.
N
O
T
A

De paso, observe que .NET permite que dos clases tengan el mismo nombre basico si tienen dife-
rente cantidad de parametros genricos. Compruebe tambin que la implementacin del mtodo
Menor no cambia al pasar de IComparable a IComparable<T>.

\ este es slo la orma mas simple de restriccin. Podramos exigir que el parametro de tipo imple-
mentase mas de un tipo de interaz:
public class Pila<T> where T : IComparable, Comparable<T>
Podramos pedir tambin que el tipo uese descendiente de alguna clase:
public class Pila<T> where T : TipoBase
Naturalmente, TipoBase no podra ser una clase sellada, porque entonces la genericidad no tendra
mucho sentido. 1enemos otros tres tipos de restricciones:
Restriccin Significado
where T: struct Ll parametro de tipo debe ser un tipo de estructura.
where T: class Ll parametro de tipo debe ser un tipo de reerencia.
where T: new Ll parametro de tipo debe incluir un constructor pblico sin parametros.

Obsere que si usamos la primera de estas tres restricciones, la ltima se conierte en redundante,
pues todos los tipos de estructura tienen un constructor pblico sin parametros.
Intuitive C
#

Copyright 2004-200S, Ian Marteens
80
S0
Htodos genr|cos
Ln un mundo perecto, donde no uese necesario corregir el rumbo en plena marcha, puede que la
tcnica que oy a explicar ahora no uese necesaria. Ademas de declarar tipos genricos, .NL1 nos
permite programar mtodos genricos. Lsto es: mtodos genricos dentro de una clase o estructura
que no necesariamente tendra que ser genrica. Pongamos por caso que estamos programando una
aplicacin con \indows lorms que utiliza pilas genricas. Si aciasemos pilas con cierta recuencia,
nos interesara declarar, en una clase nuestra, un mtodo como el siguiente:
public static void VaciarPila<T>(Pila<X> pila) where X : IComparable<X>
{
while (!pila.EstaVacia)
pila.Extraer();
}
Lo mejor sera que uese la propia clase Pila<T> la que implementase Vaciar ,sin la coletilla de Pila,.
Pero, como he dicho antes, este mundo no es perecto. Ll mtodo se ejecutara de la siguiente ma-
nera:
Pila<int> p = new Pila<int>(128);
p. Aadir(1000); p.Aadir(2000); p.Aadir(3000);

VaciarPila<int>(p);
Lo interesante, sin embargo, es que esta orma de llamada tambin es aceptada por el compilador:
VaciarPila(p);
Ln este caso, el compilador pone en marcha la inferencia de tipos: por el tipo de los parametros, es
posible deducir los tipos que hay que suministrar para los parametros genricos de un mtodo.
6mo func|ona |a |nferenc|a de t|pos
La inerencia de tipos en las llamadas a mtodos genricos es un pequeno regalo que nos hace el
compilador. Como ocurre con casi todo regalo, el compilador inierte un esuerzo razonable en esta
tarea, pero no garantiza que siempre sea posible culminarla exitosamente. lay dos motios para que
la inerencia de tipos no se realice con toda la soisticacin imaginable:
1 Ll natural equilibrio entre esuerzos y resultados.
2 Un lenguaje en el que se llea la inerencia de tipos a sus ltimas consecuencias puede ser un
lenguaje dicil de dominar, y las aplicaciones desarrolladas con l seran diciles de mantener.
Para justiicar lo anterior, debo explicarle primero cmo unciona la inerencia de tipos en C
4
. Para
simpliicar la presentacin, utilizar un ejemplo hipottico. Lxiste una clase estatica Math deinida
dentro del espacio de nombres System. Lsta clase orece una amilia o grupo de mtodos llamados
todos Min, que siren para calcular el mnimo entre dos alores del mismo tipo. Slo mencionar un
par de ellos:
public static decimal Min(decimal d1, decimal d2);
public static long Min(long l1, long l2);
Supongamos que la genericidad hubiese estado disponible desde la primera ersin de la plata-
orma. Ln tal caso, es mas que probable que la clase Math tuiese un nico mtodo llamado Min,
aunque se tratara de un mtodo genrico:
public static T Min<T>(T v1, T v2) where T: IComparable<T>
{
if (v1.CompareTo(v2) <= 0)
return v1;
else
return v2;
}
Genericidad
Copyright 2004-200S, Ian Marteens
81
S1
Supongamos que el compilador tropieza con esta llamada:
// No olvide que Math no contiene mtodos genricos!
Console.WriteLine(Math.Min(1, -1));
Olidmonos, de momento, de la existencia de tipos como byte o sbyte. La inerencia tiene lugar
considerando cada argumento por separado: el primer argumento se considera como de tipo entero.
Como el primer argumento del mtodo genrico Min<T> es de tipo T, eso signiica que T debe ser
el tipo int. Ll segundo argumento permite deducir lo mismo. Mezclamos todas las deducciones y
emos si existe alguna incoherencia. No la hay. Comprobamos si podemos entonces aplicar el algo-
ritmo tradicional de resolucin de sobrecargas: como hay un nico mtodo candidato, hemos termi-
nado exitosamente.
Pongamos ahora un ejemplo ligeramente mas complicado:
// La inferencia de tipos falla en ejemplos como ste:
Console.WriteLine(Math.Min(1.0, -1));
Ll actual compilador de C
4
se quejara y con razn, pues la especiicacin del lenguaje tambin lo
hace. Ln el ejemplo anterior, el primer argumento sire para deducir que T es el tipo double, pero
el segundo argumento exige un tipo entero, y a continuacin, el compilador considerara que se
trata de tipos incompatibles, ignorando la existencia de una conersin implcita desde el tipo en-
tero al real. Puede parecernos una limitacin tonta, pero ,imagina el caos de inerencias que podra
proocar la inclusin de conersiones, en general, en el algoritmo descrito Lo mas importante es
que tenemos una salida para casos como ste. Nos bastara con decir claramente lo que queremos
decir:
// Esta es la nica salida:
Console.WriteLine(Math.Min<double>(1.0, -1));
Lste ha sido un ejemplo sencillo, pero el algoritmo de inerencia detecta parametros genricos
enterrados dentro de ectores y tipos genricos cerrados. Por ejemplo, la clase Array tiene el si-
guiente mtodo estatico:
public static int IndexOf<T>(T[] array, T value);
Ll siguiente ragmento realiza una llamada con inerencia de tipos al mtodo anterior:
int[] vector = new int[] { 1, 2, 3, 4 };
Console.WriteLine(Array.IndexOf(vector, 1));
Ls cierto que, en este ejemplo concreto, el segundo parametro basta para deducir que T debe ser el
tipo entero. Pero lo que nos importa es que la misma deduccin tiene lugar a tras del primer para-
metro. Ln este caso, hay que ejecutar un algoritmo de unificacin de tipos.
Nud|smo y covar|anza
Lxiste un tipo de restriccin en los parametros de un tipo genrico, algo extrana a simple ista, que
en ingls se conoce como naked constraint, o restriccin desnuda. Consiste en pedir que un parame-
tro de tipo, llammosle U, sea descendiente de otro parametro genrico, al que llamaremos T. Lste
sera el patrn general de una restriccin desnuda:
class MiClase<T, U>
where U: T
Obsere que las restricciones desnudas pueden poner en un brete al compilador y al API de re-
lexin de tipos genricos. Por ejemplo, el tipo T podra ser lo mismo una clase que un tipo de inter-
az. Mas intrigante an es saber por qu se permite este tipo de restriccin, y para qu se utiliza.
Veamos: es posible que necesitemos una clase genrica con dos parametros de tipos. Digamos que
estos parametros se utilizan para declarar sendos campos en la clase. Con una restriccin desnuda,
podemos hacer que el segundo campo almacene reerencias de clases compatibles con la clase que
utilicemos en el primer parametro. ,Se da cuenta de que esto ya lo garantiza la regla de la asigna-
cin polimrica
Intuitive C
#

Copyright 2004-200S, Ian Marteens
82
S2
La clae nos la da la gua del lenguaje de la propia Microsot. Aunque por uniormidad, podemos
usar restricciones desnudas en una clase genrica, el recurso es erdaderamente til cuando se uti-
liza con mtodos genricos. Por ejemplo, podemos usarlo para realizar asignaciones covariantes
entre contenedores genricos. Analice el siguiente ragmento de cdigo:
string[] strArray = new string[] { "Haba", "una", "vez" };
object[] objArray = strArray;
foreach (object obj in objArray)
Console.WriteLine(obj);
Obsere, sobre todo, la segunda instruccin: C
4
nos permite asignar un ector de cadenas a un
ector de objetos. Mas en general, podemos asignar un ector de una clase deriada a un ector de
una clase base, siempre que base y deriada sean tipos de reerencia.
Ll truco, no obstante, introduce algunos problemillas por la puerta trasera,
string[] strArray = new string[] { "Haba", "una", "vez" };
object[] objArray = strArray;
// Esto no debera permitirse
objArray[0] = new Buttton();
// para evitar desastres como el siguiente!
Console.WriteLine(strArray[0].Length);
La tercera instruccin es una receta para el desastre: hemos asignado un objeto donde debera ir una
cadena. Sin embargo, ,se le ocurre alguna razn por la que el compilador debera rechazar la tercera
instruccin Recuerde que la eriicacin de tipos es una operacin basicamente local. Si permiti-
mos que se ejecute impunemente la tercera instruccin, el desastre llegara al ejecutarse la cuarta:
intentamos aplicar la propiedad Length sobre un objeto que no la soporta.
lay lenguajes, como Liel, que intentan resoler problemas como el anterior extendiendo el al-
cance de la eriicacin de tipos mas alla del ambito meramente local. Si el CLR adoptase esta pol-
tica, el analisis tendra que ejecutarse en tiempo de carga, pues la adicin dinamica de cualquier
ensamblado podra inalidar la seguridad del proceso. La solucin adoptada por Microsot, por
consiguiente, ha sido anadir una eriicacin en tiempo de ejecucin para las asignaciones a los
elementos de un ector. La tercera instruccin sera aceptada por el compilador, pero proocara un
error en tiempo de ejecucin.
N
O
T
A

El prefijo de instruccin readonly, del Lenguaje !ntermedio (!L), sirve para marcar referencias a
elementos de vectores como de slo lectura. De esta manera, el compilador J!T puede evitar la
inclusin de la verificacin en tiempo de ejecucin en muchos casos, que de otra manera serian
complicados de detectar durante la traduccin a cdigo nativo.

,No sera mas sencillo prohibir la compatibilidad coariante entre ectores de tipos de reerencia
No me lo parece, pero ademas, al CLR no le queda otra opcin, pues Jaa permite ese tipo de
asignaciones, con una implementacin muy parecida.
Ln principio, se puede imaginar una regla de compatibilidad parecida para los tipos genricos. Ima-
gine que, en ez de ectores, quisiramos usar listas genricas:
List<string> strList = new List<string>(
new string[] { "Haba", "una", "vez" });
// Esto no se permite!
List<object> objList = strList;
A pesar de que la asignacin coariante tiene sentido` ,Liel la permite,, C
4
la prohbe. Lo intere-
sante es que el CLR permite, al menos en teora, este tipo de compatibilidad, mediante opciones
disponibles al deinir el tipo de datos.
. y en este punto, regresamos al tema de la seccin: ,qu ocurre si tenemos una lista de cadenas y
necesitamos manejarla como una lista de objetos Si la operacin no se ejecuta con demasiada
recuencia, quizas podramos crear una nuea lista con el tipo adecuado y transerir a ella los
elementos almacenados en la lista original. Como los elementos perteneceran a tipos de reerencia,
en realidad slo estaramos duplicando la memoria del contenedor, no la de los elementos. La
Genericidad
Copyright 2004-200S, Ian Marteens
83
S3
diicultad estara en eitar la repeticin innecesaria del cdigo de copia. \ es aqu donde nos pueden
ser tiles las restricciones desnudas que mencionabamos al inicio de la seccin. Analice el siguiente
ragmento de cdigo:
public static class CovariantConverter
{
public static List<T> Convert<S, T>(List<S> source)
where S: T
{
List<T> result = new List<T>(source.Count);
foreach (S value in source)
result.Add(value);
return result;
}
}
La clase estatica CovariantConverter deine un mtodo genrico Convert, con dos parametros de
tipos. Ll primero de ellos indica el tipo de elemento de la lista original, y el segundo corresponde al
tipo de elemento de la lista resultado. Obsere que anadimos una restriccin desnuda para relacio-
nar ambos parametros. ,Para qu la necesitamos La respuesta esta en la llamada al mtodo Add
sobre la lista de destino. De no ser por la restriccin, dicha llamada no sera aceptada por el
compilador. Con la ayuda del mtodo genrico anterior, podemos remedar el uncionamiento de
una asignacin coariante entre listas:
List<string> strList = new List<string>(
new string[] { "Haba", "una", "vez" });
List<object> objList =
CovariantConverter.Convert<string, object>(strList);
Note que no podemos usar inerencia de tipos para omitir los argumentos de tipo de la llamada a
nuestro mtodo Convert. Parte de la inormacin necesaria no se encuentra en la propia llamada,
sino en el lado izquierdo de la asignacin.
N
O
T
A

La especificacin formal de C# 2 se incluy en el libro The C# Programming Language, de Hejls-
berg, Wiltamuth y Golde, mucho antes del estreno oficial de la versin 2.0 de la plataforma. Cuando
se produjo el estreno, las extensiones al lenguaje habian cambiado bastante. Por ejemplo, en la sec-
cin sobre restricciones genricas, se prohibia explicitamente el uso de restricciones desnudas, que
si son admitidas por la versin final. Naturalmente, no hay motivo para alarmarse por ancdotas
como sta. Slo la menciono porque es una de esas ocasiones en las que vemos lo que ocurre antes
del estreno, tras las cortinas del escenario.

Cener|c|dad y t|pos an|dados
Como dije al presentar la genericidad, en la practica es mucho mas recuente que utilicemos tipos
genricos ya deinidos, que tengamos que deinir nuestras propias clases y estructuras genricas. Por
este motio, he utilizado una pila, uno de los tipos mas manoseados, como ejemplo de clase gen-
rica. .NL1, en realidad, ha hecho bien los deberes, y nos orece una amplia gama de clases genricas
para las estructuras de datos mas solicitadas. Lsta es una de mis aoritas:
public class Dictionary<TKey,TValue> :
IDictionary<TKey,TValue>,
ICollection<KeyValuePair<TKey,TValue>>,
IEnumerable<KeyValuePair<TKey,TValue>>,
IDictionary, ICollection, IEnumerable,
ISerializable, IDeserializationCallback
No se asuste por la larga lista de interaces implementadas. aunque tampoco es dicil descirar de
qu se trata con un poco de paciencia. le trado la clase Dictionary a colacin no slo por su
innegable utilidad, sino tambin para mostrarle una declaracin con dos parametros genricos.
De todos modos, quiero mostrarle la implementacin de alguna clase genrica un poco mas com-
pleja que las tontas pilas. ,Qu tal si implementamos un arbol binario ordenado La noedad del
arbol es que, en realidad, necesitaremos dos clases dierentes: una para representar los nodos, y otra
Intuitive C
#

Copyright 2004-200S, Ian Marteens
84
S4
para encapsular el conjunto de nodos en el arbol, propiamente hablando. Vamos a declarar la clase
de los nodos, a la que llamaremos Node, dentro de la clase del arbol, al que bautizaremos BinaryTree:
namespace IntSight.DataStructures
{
public class BinaryTree<T> where T : IComparable<T>
{
protected class Node
{

}

}
}
le situado la clase del arbol dentro de un espacio de nombres hipottico para poder presentar los
nombres completos de las clases, que en este caso seran:
IntSight.DataStructures.BinaryTree[T]
IntSight.DataStructures.BinaryTree[T].Node
Obsere que la clase de los nodos hereda` el parametro genrico de la clase contenedora. La pala-
bra herencia` no es muy apropiada en este contexto, pero espero que transmita la idea correcta-
mente. Un error recuente sera, por ejemplo, deinir un segundo parametro genrico para la clase
Node.
Ln realidad, esta idea de transmisin de los parametros genricos es ruto de las manipulaciones del
compilador de C
4
. Las clases reales deinidas de cara al Common Language Runtime seran:
IntSight.DataStructures.BinaryTree`1
IntSight.DataStructures.BinaryTree`1+Node
Aqu puede er el conenio usado por la plataorma para las clases genricas: al nombre de la clase
se anade el acento grae o inerso, y luego, el nmero de parametros genricos de la clase. Ln el
caso de la clase anidada, el nombre de la misma se separa del nombre de la clase contenedora por
un caracter -. Ll compilador es el encargado de adecentar este conenio, utilizando puntos tanto
para separar espacios de nombres como clases.
La implementacin de la clase Node discurra por estos cauces:
protected class Node
{
public T value;
public Node left;
public Node right;
public Node(T value)
{
this.value = value;
}
public Node Contains(T value)
{
int result = value.CompareTo(this.value);
if (result == 0)
return this;
else if (result < 0)
return left == null ? null : left.Contains(value);
else
return right == null ? null : right.Contains(value);
}
}
Al tratarse de una clase anidada no pblica, no hay que preocuparse excesiamente por encapsular
cada uno de sus campos. le preerido dar acceso directo a los tres campos que existiran en cada
Genericidad
Copyright 2004-200S, Ian Marteens
85
S5
nodo: un alor, declarado mediante el parametro de tipo, y reerencias a los arboles a la izquierda y a
la derecha del nodo. Obsere estas declaraciones con cuidado:
public Node left;
public Node right;
No hace alta incluir parametros de tipo en esta declaracin, porque se trata de una abreiatura de la
declaracin completa:
public BinaryTree<T>.Node left, right;
EJERCICIO PROPUESTO
Para no complicarme demasiado, la implementacin del mtodo Contains en la clase Node es recursiva. Puede ex-
perimentar transformando esta busqueda en una operacin iterativa.
La clase BinaryTree contendra un puntero al nodo raz del arbol, que sera nulo cuando el arbol est
aco. La bsqueda se implementara delegando en el mtodo Contains de Node, y la parte mas
complicada, al menos relatiamente, sera la adicin de nueos alores al arbol:
public class BinaryTree<T> where T : IComparable<T>
{
// Aqu se declarara la clase anidada Node
protected Node root;
public void Add(T value)
{
Node parent = null, current = root;
while (current != null && current.value.CompareTo(value) != 0)
{
parent = current;
current = value.CompareTo(current.value) < 0 ?
current.left : current.right;
}
if (current == null)
{
current = new Node(value);
if (parent == null)
root = current;
else if (value.CompareTo(parent.value) < 0)
parent.left = current;
else
parent.right = current;
}
}
public void Add(params T[] values)
{
foreach (T value in values)
Add(value);
}
public bool Contains(T value)
{
return root != null && root.Contains(value) != null;
}
}
Obsere que tenemos dos ersiones sobrecargadas de Add. La segunda de ellas nos permitira anadir
arios nodos en una sola operacin. No pierda esta clase de ista, porque la necesitaremos un par
de eces mas en esta introduccin. A partir de este punto, dejar de resaltar en amarillo los parame-
tros de tipo, para no abusar de su ista y de su paciencia.
T|pos anu|ab|es
La adicin de tipos genricos a la plataorma .NL1 ha hecho posible la aparicin de los tipos anula-
bles, o nullable types: un recurso que puede sernos muy til para trabajar con los alores nulos
Intuitive C
#

Copyright 2004-200S, Ian Marteens
86
S6
caractersticos de las bases de datos relacionales. Lsta aplicacin, sin embargo, no es la nica posible
para estos tipos.
Imagine que tenemos una tabla en SQL Serer, con una columna de tipo byte. ,Cuantos alores
dierentes podemos representar mediante dicha columna Si la columna no admite alores nulos,
son 256 alores, en caso contrario, hay que sumar un nueo alor, el nulo, con lo que llegaramos a
25 alores dierentes. Si quisiramos leer el alor de esa columna y almacenarlo en un campo de
una clase escrita en C
4
no podramos usar simplemente un campo de tipo byte. 1endramos que
usar dos campos dierentes: uno, probablemente de tipo bool, para indicar si hemos ledo un alor
nulo de SQL o no, y el campo de tipo byte ya mencionado.
Manejar dos campos para cada columna puede complicar el cdigo uente y diicultar su manteni-
miento, pero los problemas gordos slo acaban de llegar. Los alores nulos de SQL se interpretan
como inormacin desconocida`. Lxisten reglas muy claras que establecen lo que debe ocurrir si
intentamos usar un alor nulo en una expresin. Por ejemplo, si sumamos x a un alor nulo, el
resultado debe ser tambin nulo, sin importar el alor concreto de la x. Ln eecto, si depositamos un
euro en una cuenta bancaria cuyo saldo desconocemos, ,cual sera el nueo saldo Desconocido,
eidentemente.
Volamos a la aplicacin escrita en C
4
, y supongamos que tenemos que sumar el alor de dos cam-
pos, a los que llamaremos x e y, cuyos alores han sido extrados de una base de datos:
private decimal x, y;
Supongamos tambin que las correspondientes columnas admitan alores nulos, por lo que hemos
asociado un par de campos lgicos a los campos ya mencionados:
private bool nx, ny;
Ll alor de nx sera erdadero cuando la columna asociada al campo x contenga un nulo, y algo
parecido ocurrira con el par ormado por ny e y. La pregunta del millar de liras alsas: ,cmo puedo
implementar la suma de estos dos pares de campos, de manera que tengan en cuenta los alores
nulos Lsta sera la respuesta:
private bool nz; // Nos dir si el resultado es nulo o no.
private decimal z; // La suma, si el resultado no es nulo.
if (nx || ny)
nz = true;
else
{
nz = false;
z = x + y;
}
No es tan complicado como la Mecanica Cuantica. pero es pesado de escribir y complicado de
mantener. ,\ si le pidiese que calculase las races de una ecuacin de segundo grado, teniendo en
mente la posibilidad de que los coeicientes de la ecuacin contengan nulos
Los tipos anulables resuelen la mayor parte de estas diicultades. La limitacin de nuestro amigo, el
tipo byte, era que slo poda almacenar 256 mseros alores, ,no Le hacemos beber batido de
kriptonita y lo conertimos en un tipo anulable:
private byte clark; // Superman sin capa y con gafas.
private byte? superman; // Clark Kent con capa y sin gafas.
Ll signo de interrogacin tras el nombre del tipo coniere superpoderes a ste. Ln realidad, se trata
de una notacin abreiada para esta declaracin:
private System.Nullable<byte> superman;
1ras los presuntos superpoderes, se esconda un tipo genrico: la estructura System.Nullable<T>,
cuya declaracin se parece a esto:
Genericidad
Copyright 2004-200S, Ian Marteens
8
S7
public struct Nullable<T> where T : struct
{
private bool hasValue;
internal T value;

}
Como puede er, Nullable encapsula un alor del tipo necesario mas un campo lgico que indica si
se trata de un alor nulo o no. lay otros dos detalles muy importantes en el ragmento anterior:
1 Nullable slo admite tipos con semantica de alor para su parametro genrico. No podemos
declarar Nullable<String>, o su equialente String, porque System.String es una clase.
2 Ll propio tipo Nullable es una estructura.
\ hay que destacar otro detalle, aunque esta ez la declaracin no nos da pistas:
3 Ln realidad, el parametro genrico de Nullable rechaza tambin los tipos anulables, aunque sean
tipos de alor. Lsto es, aunque se permite Byte?, no se permite Byte??.
,Por qu no se admiten tipos de reerencia La respuesta es que no necesitan kriptonita. La kripto-
nita anada un nueo alor al dominio original del tipo, ,no es as Pues bien, los tipos de reerencia
ya cuentan con un alor especial adicional: el alor null, es decir, el puntero nulo! Si necesitamos
leer una columna de tipo cadena en un campo o ariable de C
4
, podemos adoptar el conenio de
representar los alores nulos de SQL como punteros nulos en C
4
. Slo los tipos de alor necesitan
el mdulo de expansin` que implementa System.Nullable.
,Por qu Nullable es una estructura lacil: si Nullable uese una clase, en ez de una estructura, ten-
dramos que reserar memoria dinamica para sus instancias. Como los tipos bases admitidos por
Nullable no requieren memoria dinamica por ser tipos de alor, Nullable introducira un coste
innecesario en memoria y en tiempo de ejecucin.
No me pregunte, en cambio, por qu no se puede aplicar Nullable a un tipo Nullable. Supongo que
la deinicin e implementacin de las operaciones con estos tipos se complicara en caso de que
uese posible. Pero slo es una conjetura personal.
0perac|ones con t|pos anu|ab|es
Veamos qu podemos hacer con los tipos anulables. Comencemos por las asignaciones:
int? i = 1234;
int? j = null;
A una ariable de un tipo anulable, podemos asignarle un alor del tipo original, lo cual no es ex-
trano, pero tambin permiten la asignacin de un puntero aco, lo cual si resulta raro. Ln ambos
casos, el compilador traduce los alores a la derecha de la asignacin en llamadas a los constructores
de la clase Nullable:
Nullable<int> i = new Nullable<int>(1234);
Nullable<int> j = new Nullable<int>();
Lo que no podemos hacer es asignar directamente un alor anulable en una ariable del tipo origi-
nal:
int? i = 1234;
int j = i; // Esto no es aceptado por el compilador!
La orma mas acil de extraer la inormacin almacenada en una entidad anulable es usar estas dos
propiedades, deinidas en la clase Nullable<T>:
public bool HasValue { get; }
public T Value { get; }
Por lo tanto, la secuencia de asignaciones que rechazaba el compilador puede escribirse de esta ma-
nera:
Intuitive C
#

Copyright 2004-200S, Ian Marteens
88
SS
int? i = 1234;

int j = i.Value;
Ln este ejemplo, estamos conencidos de que la ariable i contiene un alor no nulo. ,\ si no uese
as Ln tal caso, la lectura de la propiedad Value proocara una excepcin. Ln general, si queremos
eitar excepciones, tendramos que combinar el uso de Value con HasValue, como en este rag-
mento:
int? i = 1234;

if (i.HasValue)
{
int j = i.Value;

}
Ll ejemplo anterior tiene un pequeno problema de eiciencia: la implementacin de la propiedad
Value eriica si hay algn alor asignado en la ariable. pero se tratara de una eriicacin por
duplicado, al menos en este ejemplo. Por este motio, existe una uncin adicional en Nullable, lla-
mada GetValueOrDefault, que no realiza esta eriicacin:
int? i = 1234;

if (i.HasValue)
{
int j = i.GetValueOrDefault();

}
La implementacin de GetValueOrDefault es muy simple:
public T GetValueOrDefault()
{
return this.value;
}
Si llamasemos a GetValueOrDefault sin comprobar antes el contenido de HasValue, y si tuisemos
la mala suerte de que la instancia contuiese un alor nulo. de todos modos obtendramos un
resultado bastante sensato: el alor por omisin correspondiente al tipo original. Por ejemplo, si
estamos trabajando con enteros, obtendramos un cero, si se trata de un tipo lgico, obtendramos
el alor also. Ln general, obtendramos el resultado de asignar ceros en cada uno de los bytes de la
instancia.
Lxiste una segunda ariante de GetValueOrDefault, que nos permite elegir el alor por omisin:
public T GetValueOrDefault(T valor_por_omisin);
Con esta uncin podramos escribir cdigo como el siguiente:
int? i = 1234;

int j = i.GetValueOrDefault(-1);
Pararaseando la ltima instruccin: dame el alor almacenado en i, si es que existe, para asignarlo
en j, y si el alor es nulo, asignemos -1. Sin embargo, existe una alternatia mejor al uso de esta a-
riante de GetValueOrDefault: el llamado operador de fusin ,coalescence operator,, representado me-
diante dos signos de interrogacin consecutios:
int j = i ?? -1;
Se trata de un operador binario que deuele el alor de su primer operando si ste resulta ser no
nulo. Ln caso contrario, deuele el alor del segundo operando. Ll segundo operando puede ser de
tipo anulable o no. Ln el ejemplo anterior, el segundo operando es una constante entera, por lo que
el resultado del operador siempre sera un tipo entero no anulable.
Genericidad
Copyright 2004-200S, Ian Marteens
89
S9
,Por qu es preerible el operador de usin al mtodo GetValueOrDefault La entaja del operador
de usin es que no esta obligado a ealuar su segundo operando si encuentra que el primero no
deuele un nulo. GetValueOrDefault, como todo mtodo con parametros, exige que se eale cada
uno de sus parametros antes de ser ejecutado. Ln nuestro ejemplo, el segundo operando era una
constante, pero tenga presente que podramos escribir una expresin compleja en su lugar.
N
O
T
A

El operador de fusin puede usarse tambin con tipos de referencia. Se evaluaria el primer ope-
rando, y si el resultado no es un puntero nulo, se devuelve este valor. En caso contrario, se devolve-
ria el resultado de la evaluacin del segundo operando.

Promoc|n de operadores
Las noedades relacionadas con los tipos anulables no se detienen aqu. ,Recuerda que nos quejaba-
mos de lo dicil que resulta imitar el comportamiento de las expresiones que contienen nulos en
SQL Los tipos anulables tienen tambin presente este problema, y nos echan una mano alterando
la semantica de los operadores habituales de C
4
. La documentacin se reiere a los operadores que
trabajan con operandos anulables con el nombre de lifted operators, que podramos traducir mas o
menos como operadores promovidos. Las reglas son sencillas, pues son las mismas que en SQL: en
una expresin, si alguno de sus operandos es nulo, toda la expresin se anula.
int? ni = 1000;
int? nn = null;
ni = ni + 1;
Console.WriteLine(i);
ni = ni * nn;
Console.WriteLine(i);
La suma del ejemplo anterior inolucra a un tipo anulable y una constante entera. Como el ope-
rando anulable no contiene un nulo, la operacin unciona normalmente y se asigna 1001 en la
ariable ni. Ln la multiplicacin, por el contrario, el segundo operando contiene un nulo, y se asigna
un nulo en ni. Un detalle: las llamadas a WriteLine proocan la ealuacin del mtodo ToString de la
clase Nullable<T>. Cuando ToString se ejecuta sobre un alor no nulo, deuele el alor original
como cadena. Si aplicamos ToString sobre un alor nulo, obtendremos una cadena aca.
Ln el ejemplo, en ez de multiplicar directamente por la constante null, utilic una ariable que
contena null. Ll compilador permitira esta asignacin:
ni = ni * null;
Pero se dara cuenta de que, independientemente del contenido de ni, la expresin siempre se
ealuara a null. La expresin se simpliicara de manera automatica, y recibiramos una adertencia
por parte del compilador.
1enga muy presente que, con estas noedades, C
4
intenta duplicar la semantica de expresiones de
SQL. pero eso no quiere decir que la semantica de expresiones de SQL sea digna de un premio
1uring. ,Qu ocurrira si ejecutasemos el siguiente ragmento de cdigo
int? ni = 1000;
int? nn = null;
if (ni > nn)
Console.WriteLine("Mayor");
else if (ni < nn)
Console.WriteLine("Menor");
else if (ni == nn)
Console.WriteLine("Igual");
else
Console.WriteLine("Este mundo loco, loco, loco...");
Si su respuesta ha sido que algo apesta en Dinamarca, ha acertado. Ls una lastima que el concurso
no oreciese premios.


Copyright 2004-200S, Ian Marteens
90
El Conocimiento Secreto
Recuerde que puede dejar que el compilador iniera los argumentos de tipos en llamadas
a mtodos genricos.
De todos modos, debo adertirle que el abuso de esta caracterstica puede oler ilegible
su cdigo. Ademas, existe el peligro de que el compilador iniera un tipo deriado cuando
lo que usted pretenda era usar un ancestro del tipo deducido.

Copyright 2004-200S, Ian Marteens
91
JU JU JU JU
TERACON

UALQUILR APLICACIN MLDIANAMLN1L compleja utiliza contenedores de ariado pelaje con
generosidad. Puede tratarse de pilas, listas ordenadas y sin ordenar, arboles de todos los colo-
res e incluso conjuntos de datos o simples ectores. Para todas estas estructuras, es crucial la opera-
cin que permite recuperar cada uno de sus elementos de manera secuencial. Aunque una adecuada
encapsulacin ayuda bastante, C
4
orece una instruccin especial, foreach, que simpliica el reco-
rrido sobre los elementos de cualquier contenedor debidamente preparado.
La otra cara de la moneda es cmo preparar un contenedor para que soporte la instruccin mencio-
nada. Ln la primera ersin de la plataorma, era una tarea dicil, pues casi siempre haba que
implementar una maquina de estados. A partir de la segunda ersin, es el propio compilador quien
se hace cargo de los detalles sucios, gracias a la adicin de iteradores al lenguaje.
E| patrn de enumerac|n
La instruccin using que he presentado antes corresponde a una decisin de diseno nada usual,
este autor no conoce nada parecido, al menos en los lenguajes mas populares. La singularidad de
using consiste en que se trata de una adaptacin sintactica para simpliicar el uso de un recurso de
la biblioteca de clases. Ls normal concebir el diseno de un lenguaje de programacin mediante ca-
pas:
1 Ln el niel inerior, la deinicin lexical: qu es un identiicador, cmo se orman las cadenas de
caracteres, qu importancia se le da a los cambios de lneas.
2 A partir de los elementos lexicales, se deine la sintaxis del lenguaje: una instruccin if esta
ormada por una condicin, una parte then obligatoria y una parte else uncional. A su ez, la
parte then esta ormada por. Creo que la idea queda clara, ,no
3 Ll lenguaje que hemos deinido puede ejecutarse sobre dierentes plataormas. Para cada plata-
orma se espera que, mas alla de una coleccin pequena de clases o unciones comunes, surjan
bibliotecas de clases o unciones dierentes.
De acuerdo a esta isin estratiicada del lenguaje, no sera buena idea incular entidades del niel
dos con entidades del niel tres. ,O s Conieso que he hecho trampas, omitiendo un estrato que se
ubicara entre el segundo y el tercero.
2 Una ez deinida la sintaxis, casi siempre mediante una gramatica libre de contexto, la
especiicacin del lenguaje debe completarse asignando atributos a las distintas construcciones
gramaticales, y exigiendo que se cumplan determinadas reglas relacionadas con estos atributos.
Por ejemplo, la expresin que sigue a if en una instruccin condicional debe ser de tipo
Boolean.
,Ln qu se dierencia la exigencia de que la condicin de un if o de un while sea de tipo Boolean de
requerir que el tipo de la ariable en una instruccin using implemente la interaz IDisposable La
nica dierencia es que el tipo lgico es un tipo predeinido`. pero este concepto de tipo predei-
nido es muy lexible. C
4
ha cruzado una lnea muy delgada, y creo que el paso esta justiicado.
Una ez abierta la eda, es lgico esperar mas instrucciones inculadas a determinados tipos de
datos. Probablemente, la mas popular de ellas sea la instruccin foreach, para recorrer todos los
elementos de una coleccin.
C
Intuitive C
#

Copyright 2004-200S, Ian Marteens
92
92
foreach (Tipo variable in expresin)
instruccin
Oldese por el momento del tipo de la ariable declarada por la instruccin. Lo mas importante es
determinar qu tipo de expresiones se admiten a la derecha de la palabra reserada in. lay dos
posibilidades:
1 Que el tipo de la expresin descienda o implemente el tipo de interaz IEnumerable, deinido en
el espacio de nombres System.Collections. 1odos los ectores, con independencia de su tipo
base, descienden ormalmente de la clase System.Array, y sta implementa IEnumerable, por lo
que la expresin puede ser un ector de tipo arbitrario.
2 Si uela como un pato, nada como un pato, anda patosamente ,como un pato, y grazna como
un pato. ,qu mas da si no es un pato Quiero decir, que si la expresin no implementa la
interaz IEnumerable, pero tiene mtodos pblicos cuyos prototipos coinciden con los de
IEnumerable, C
4
acepta pato por liebre ,,o se trataba de un gato,.
Veamos entonces la declaracin del tipo IEnumerable:
public interface IEnumerable
{
IEnumerator GetEnumerator();
}
Ls decir, IEnumerable slo orece un mtodo que sire para obtener un puntero a otro tipo de
interaz, IEnumerator, tambin declarado en System.Collections:
public interface IEnumerator
{
bool MoveNext();
object Current { get; }
void Reset();
}
Supongamos que la expresin usada en la instruccin foreach implementa IEnumerable. Ln tal
caso, la instruccin se implementa, a grandes rasgos, de la siguiente manera:
IEnumerator enumerador = expresin.GetEnumerator();
while (enumerador.MoveNext())
{
Tipo variable = (Tipo)enumerador.Current;
instruccin;
}
Ln realidad, la implementacin es un poco mas compleja, porque hay que comprobar si la expre-
sin implementa, ademas de IEnumerable, el tipo IDisposable. Pero por ahora podemos arreglarnos-
las con esta ersin simpliicada.
,Por qu se deinen dos tipos de interaz dierentes para deinir un recorrido Gracias a este diseno,
es posible realizar arios recorridos simultaneamente sobre una misma coleccin, porque las
estructuras de datos que controlan la iteracin se pueden situar en la implementacin de
IEnumerator. Ni siquiera es necesario anidar las iteraciones: cada alor IEnumerator que obtengamos
ejecutando GetEnumerator debe ser independiente de los demas:

lLnumeab1e
lLnumeafo
lLnumeafo
GefLnumeafo{}
GefLnumeafo{}
Iteracin
Copyright 2004-200S, Ian Marteens
93
93
lay otro detalle importante: la propiedad Current de IEnumerator esta declarada con el tipo object,
y en consecuencia, para asignar su alor en la ariable del bucle es necesario realizar una conersin
de tipo explcita. Como puede imaginar, esta conersin tiene un coste. Lsto se resuele en .NL1
2.0, como eremos mas tarde, mediante los nueos tipos genricos, pero se trata de un lujo que no
esta disponible en la ersin 1.1. ,Ls posible librarnos de esta conersin
Para resolerlo tendramos que usar un pato. es decir, el tipo de la expresin en foreach debe
deinir un mtodo pblico llamado GetEnumerator. No es necesario que este GetEnumerator de-
uela un alor de tipo IEnumerator. Llamemos T al tipo deuelto por GetEnumerator. Lste tipo
debe declarar un mtodo pblico y una propiedad pblica:
public bool MoveNext();
public X Current { get; }
La dierencia respecto al IEnumerator ortodoxo` esta en el tipo de la propiedad Current, que ahora
no tiene que ser necesariamente object, sino el tipo que el autor de la clase considere apropiado. Si
el compilador, al traducir una sentencia foreach, descubre este patrn y encuentra, ademas, que el
tipo declarado para la propiedad Current coincide con el tipo de la ariable del bucle, puede aho-
rrarse la temida conersin de tipos.
Una pregunta recuente es si este mecanismo no es demasiado pesado` para recorrer los elemen-
tos de un ector. Pero ocurre que el compilador de C
4
maneja este caso de orma separada, y tra-
duce una instruccin foreach que recorre un ector de la orma mas eiciente posible. Ls mas: hay
insistentes rumores de que, al conertir inalmente un bucle sobre un ector en cdigo natio
ejecutable, el compilador JI1 introduce unas cuantas optimizaciones para no tener que realizar la
eriicacin de lmites de la ariable de control. Le adierto que no lo he comprobado en persona,
pero si non vero, ben trovato.
|teradores
La otra gran noedad en C
4
ersin 2.0 son los mtodos iteradores. La terminologa se las trae: en
realidad, la documentacin de C
4
habla de bloques de iteracin`. Un bloque de iteracin es un
bloque que contiene al menos una instruccin yield return o yield break. Para que estas nueas
instrucciones puedan usarse, el mtodo debe deoler un alor de tipo IEnumerable, IEnumerator o
de los primos genricos de estos dos tipos. Resumiendo, que a primera ista cuesta trabajo distin-
guir estos engendros.
1odo esto es, en gran parte, culpa de la ieja sintaxis heredada de los tiempos de C. Ln lreya, cuya
sintaxis es pascaloide, un iterador es un mtodo que se declara mediante la palabra clae iterator: de
igual manera que un constructor se declara con la palabra clae constructor:
BinaryTree = class[X]

public
iterator Nodes: X;
end;
Ln C-- y C
4
, por el contrario, para distinguir entre un constructor y un mtodo normal` hay que
obserar si el mtodo indica un tipo de retorno. aunque este sea void. \ un iterador como el
anterior tendra que declararse as:
public IEnumerable<X> Nodes() { }
Ls decir, sin conocer el contenido de Nodes, es imposible saber si se trata de un mtodo regular o
de un mtodo con bloques de iteracin en su interior.
,Cmo se usan los bloques de iteracin Lste es un ejemplo simple, aunque un poco extremo:
public class Iteradores
{
public static IEnumerable<int> Digitos()
{
Intuitive C
#

Copyright 2004-200S, Ian Marteens
94
94
yield return 0; yield return 1; yield return 2;
yield return 3; yield return 4; yield return 5;
yield return 6; yield return 7;
yield return 8; yield return 9;
}
}
le declarado un mtodo Digitos estatico, para eitar tener que crear una instancia para usarlo, que
contiene un bloque de iteracin, cuyo alor de retorno es uno de los adecuados. Podemos usar
Digitos de esta manera:
foreach (int i in Iteradores.Digitos())
Console.WriteLine(i);
Lste cdigo imprimira los dgitos decimales, del 0 al 9, uno por lnea. Conceptualmente, se puede
representar el uncionamiento de la instruccin yield return de esta manera:

Para simpliicar, he supuesto que el iterador se llama como parte de un bucle foreach, luego ere-
mos que no es indispensable. Ll bucle cedera el control al iterador, y ste ejecutara sus instruccio-
nes hasta llegar a un yield return. Ll control pasara temporalmente al cuerpo del bucle. Cuando el
bucle pidiese el siguiente elemento, el control regresara a la instruccin siguiente al yield return
que interrumpi la ejecucin. lablando en jerga inormatica, el iterador uncionara como una
corrutina.
Por supuesto, usted y yo sabemos cmo unciona la pila de llamadas de un microprocesador y nadie
nos conencera a las buenas de que los iteradores uncionan exactamente as. Lo que realmente su-
cede es que el compilador desmenuza el cdigo del iterador y, a partir de los trozos, monta un m-
todo MoveNext de una clase creada para uso interno. Sin entrar en demasiados detalles tcnicos, la
tcnica comienza sustituyendo cada yield return por la siguiente secuencia equialente:
yield return x;

_estado =
i

_current = x;
return true;

i
:
Lstas instrucciones almacenan el estado interno del iterador en un campo de la clase generada, y
asignan el alor de retorno en otro campo, para inmediatamente terminar la ejecucin del iterador.
Luego, el compilador anade una instruccin switch al inicio del iterador, que lee en qu estado se
encuentra el iterador para decidir adnde saltar. Lsta es slo la idea basica. Ademas, el compilador
debe determinar cuales campos de la clase son utilizados dentro del iterador para conertirlos en
campos de la clase interna generada. 1ambin hay que transormar las ariables locales del iterador
en campos de la clase interna, para que no se pierdan sus alores en cada paso del bucle.
Veamos ahora un ejemplo mas real de iterador. Lsta ez se trata de un iterador que deolera la
secuencia de los nmeros de libonacci:
public static IEnumerable<int> Fibonacci()
{
yield return 0;
int i = 0, j = 1;
while (true)
{
yield return j;
int temp = i;
i = j;
yield return x;
foreach ()
{



}
Iteracin
Copyright 2004-200S, Ian Marteens
95
95
j += temp;
}
}
Como e, este iterador ejecuta un bucle ininito! Lsto, en s, no es grae. siempre que adirtamos
al cliente del iterador sobre esta caracterstica, pues el responsable de detener la generacin de
nmeros en algn momento sera el cliente. Por ejemplo:
foreach (int i in Iteradores.Fibonacci())
if (i > 1000)
break;
else
Console.WriteLine(i);
EJERCICIO PROPUESTO
!ntente generar una clase que implemente IEnumerable a partir del cdigo del iterador Fibonacci. Luego, programe
el iterador en una aplicacin y compruebe con Reflector o con !LDASN el cdigo que ha generado el compilador.
Comparelo con su propia implementacin.
,Qu tal si implementamos un iterador para nuestra clase BinaryTree Voy a mostrarle una
implementacin que recorre los nodos en orden simtrico: para cada nodo, primero se recorre el
arbol de la izquierda, a continuacin se isita el alor del propio nodo, y luego se pasa al arbol de la
derecha. Como se trata de un arbol binario ordenado, este recorrido deuele los alores almacena-
dos en su orden correcto.
Lsta es la implementacin del iterador:
public IEnumerable<T> Nodes()
{
Node current = root;
Stack<Node> stack = new Stack<Node>();
while (true)
if (current != null)
{
stack.Push(current);
current = current.left;
}
else if (stack.Count > 0)
{
current = stack.Pop();
yield return current.value;
current = current.right;
}
else
yield break;
}
Ln realidad, hay dos ormas de implementar un iterador de este tipo: o usamos recursiidad, o
utilizamos una pila auxiliar. Ll mtodo anterior muestra el uso de una pila. Ln este caso, para la pila
he usado la clase Stack, que orece .NL1 de serie.
6ompos|c|n de |teradores
Para explicar por qu es preerible usar una pila explcita, tenemos que er cmo sera la
implementacin recursia:
public IEnumerable<T> NodosRecursivos()
{
if (left != null)
foreach (T valor in left.NodosRecursivos())
yield return valor;
yield return this.value;
Intuitive C
#

Copyright 2004-200S, Ian Marteens
96
96
if (right != null)
foreach (T valor in right.NodosRecursivos())
yield return valor;
}
,Ve el problema Cada ez que se ejecute una instruccin foreach, se creara una instancia de la
clase interna sintetizada por el compilador. Ll gasto en memoria sera excesio, y habra que contar
tambin con el coste adicional de las llamadas recursias. La entaja de la implementacin recursia,
no obstante, es su mayor simplicidad. Ls muy acil equiocarse al programar la iteracin sobre un
arbol mediante una pila auxiliar. Si no me cree, intente programar iteradores que isiten los nodos
en pre-orden y en post-orden.
Htodos ann|mos
Ln los lenguajes que no soportan iteradores ,la gran mayora, es comn utilizar otra tcnica para
recorrer estructuras complejas, como nuestros arboles binarios ordenados. La tcnica consiste en
deinir el iterador como un mtodo que recibe como parametro un puntero a mtodo. Ln C
4
, natu-
ralmente, puntero a mtodo` debe traducirse como tipo delegado`. Suponga que tenemos una
clase que esconde un ector en sus tripas, como la amosa pila:
public class Pila<T>
{
private T[] elementos;
private int cantidad;

}
Podramos deinir un iterador que recorriese todos los elementos de la pila mediante un mtodo
como el siguiente:
public void Recorrer(Action<T> accion)
{
for (int i = cantidad; i > 0;)
accion(elementos[--i]);
}
Ll tipo genrico Action, utilizado en el parametro de Recorrer, esta declarado en System:
// Delegado predefinido en System
public delegate void Action<T>(T obj);
A este tipo de mtodos se les llama iteradores cerrados. Por lo general, resulta mas sencillo progra-
mar iteradores cerrados que iteradores abiertos`: lo comprobaremos enseguida, cuando le muestre
cmo programar un iterador cerrado para BinaryTree. La mala noticia es que los iteradores cerrados
no son tan lexibles como los abiertos:
1 Ls mas complicado abortar un recorrido con un iterador cerrado. De entrada, tendramos que
ampliar el prototipo del parametro de accin para que el cliente del iterador aise si desea conti-
nuar o no. Ll delegado Action tendra que deinirse de orma parecida a sta:
public delegate void Action1<T>(T obj, ref bool abortar);
Ln teora, podramos haber hecho que Action1, en ez de tener un parametro adicional, deol-
iese un alor de tipo lgico. Pero sera una mala decisin: estaramos obligando al cliente del
iterador a deoler siempre algo` para terminar la accin. Con un parametro pasado por
reerencia, podramos inicializar abortar a true. Ll cliente no tendra que tocar este parametro a
menos que realmente desease interrumpir el recorrido.
2 La limitacin mas importante concierne a las ormas en las que podramos usar el iterador.
Imagine que tenemos dos listas ordenadas, y que queremos mezclar sus elementos en una
nuea lista que consere el orden. Ll algoritmo de mezcla ordenada es un clasico de la
Inormatica: pedimos un elemento de cada lista para compararlos entre s. Ll elemento menor
se inserta en el resultado, y la lista de donde procede aanza un paso en el recorrido. Lste tipo
Iteracin
Copyright 2004-200S, Ian Marteens
9
97
de recorrido se puede implementar con los iteradores abiertos. aunque no con la instruccin
foreach! Pero no podemos usar un iterador cerrado en un algoritmo de mezcla ordenada.
Ahora que ya conoce las limitaciones, eamos cmo programara un recorrido en orden simtrico
para un arbol binario ordenado:
public class BinaryTree<T> where T : IComparable<T>
{
public void ForEach(Action<T> action)
{
if (root != null) root.ForEach(action);
}
}
Para mantener la terminologa usada por C
4
, he llamado ForEach al iterador cerrado. Su
implementacin comprueba si la raz es nula o no, para delegar el recorrido en un mtodo de la
clase Node:
public class BinaryTree<T> where T : IComparable<T>
{
protected class Node
{

public void ForEach(Action<T> action)
{
if (left != null) left.ForEach(action);
action(value);
if (right != null) right.ForEach(action);
}
}
}
,la isto qu acil ha sido Para mostrar cmo se utilizara ForEach necesitamos una accin`: un
mtodo que reciba un alor entero y que no tenga alor de retorno. Algo como esto nos serira:
private void ImprimirNumero(int i)
{
Console.WriteLine(i);
}
Ln realidad, podramos usar directamente Console.WriteLine, pero supondremos que lo que
necesitamos hacer con cada nodo no es tan simple como imprimir su alor en pantalla. Para ejecu-
tar el bucle con la ayuda de ForEach haramos lo siguiente:

BinaryTree<int> tree = new BinaryTree<int>();
tree.Add(0, 9, 1, 8, 7, 2, 3, 6, 4, 5);
tree.ForEach(ImprimirNumero);

Aqu ya podemos er el problema: tenemos, por un lado, una llamada a ForEach que hace reerencia
a un mtodo llamado ImprimirNumero. ,Dnde esta deinido este mtodo Por lo general, estara en
el quinto pino, uera de nuestro alcance. Como resultado, analizar este sencillo ragmento de cdigo
puede ser bastante pesado.
A partir de su segunda ersin, C
4
aliia nuestros surimientos permitindonos escribir mtodos
annimos. Obsere:
BinaryTree<int> tree = new BinaryTree<int>();
tree.Add(0, 9, 1, 8, 7, 2, 3, 6, 4, 5);
tree.ForEach(delegate(int value) { Console.WriteLine(value); });
La zona resaltada en amarillo corresponde a la deinicin de un mtodo en toda regla. Ll mtodo
no tiene nombre, y de ah lo de annimo`. Solamente declaramos los parametros, porque el tipo
de retorno puede ser inerido por el compilador, en este caso, el mtodo annimo no deuele
Intuitive C
#

Copyright 2004-200S, Ian Marteens
98
9S
alor alguno. La gran entaja es que la llamada a ForEach y la accin que ejecutamos para cada
nodo estan juntas sicamente, y es mas sencillo comprender qu hace el ragmento de cdigo.
Como imaginara, el compilador de C
4
genera mtodos internos para implementar los mtodos
annimos. Ll compilador debe detectar si el mtodo utiliza ariables locales accesibles desde el
punto donde se declara el mtodo. Ln el ejemplo anterior, el mtodo annimo slo utiliza el
parametro del propio mtodo. Pero podemos complicarlo un poco:
BinaryTree<int> tree = new BinaryTree<int>();
tree.Add(0, 9, 1, 8, 7, 2, 3, 6, 4, 5);
int total = 0;
tree.ForEach(delegate(int value) { total += value; });
Console.WriteLine(total);
Lsta ez, el mtodo annimo lee y escribe el alor de una ariable local del mtodo donde es decla-
rado. Ll compilador lo resuele creando una clase auxiliar, moiendo la ariable local a dicha clase,
como un campo, y deiniendo el mtodo oculto dentro de esta clase.
Antes mencion que el compilador puede inerir el alor de retorno del mtodo annimo. Ln la
practica, puede hacer mucho mas:
int count = 0;
tree.ForEach(delegate { count++; });
Ll compilador acepta que omitamos toda la lista de parametros si no la amos a utilizar. Obsere
que no he puesto parntesis acos despus de la palabra delegate. Si los pusiramos, el compilador
protestara, porque interpretara que el mtodo annimo no debe recibir parametros.
N
O
T
A

Tanto los mtodos annimos como los bloques de iteracin son recursos implementados por los
compiladores, y no necesitan ayuda alguna por parte de la plataforma. Los tipos genricos, por el
contrario, son responsabilidad del entorno de ejecucin.

Expres|ones |ambda
Los mtodos annimos cobran mayor importancia en la siguiente ersin de C
4
, pues son la base
de las expresiones lambda, que son a su ez undamentales para LINQ, el lenguaje de consulta que
implementara la ersin 3 de C
4
. Ll nombre de expresin lambda esta tomado del calculo uncional
y de los lenguajes de programacin uncionales, y es una senal del inters de los disenadores de C
4

por aproechar ciertas caractersticas de estos lenguajes.
Antes de explicar en qu consisten estas expresiones, eamos otro ejemplo en el que interendran
mtodos annimos. La clase System.Array, que es la clase base de todos los tipos de ectores en C
4
,
no es una clase genrica, pero en ella se deinen arios mtodos estaticos genricos como el si-
guiente:
public static T[] FindAll<T>(T[] vector, Predicate<T> condicion);
FindAll recibe un ector y una condicin, y deuele un nueo ector con todos los elementos del
ector original que satisacen la condicin. La condicin se representa mediante un delegado
compatible con el siguiente tipo:
public delegate bool Predicate<T>(T obj);
Lsto es: los mtodos compatibles con el delegado Predicate reciben una instancia y deben deoler
un alor lgico, indicando si la instancia cumple con la condicin deseada o no. Por ejemplo, puede
que nos interese seleccionar los nmeros impares presentes en un ector de enteros:
int[] result = Array.FindAll<int>(
new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 },
delegate(int x) { return x % 2 != 0; });
foreach (int i in result)
Console.WriteLine(i);
Iteracin
Copyright 2004-200S, Ian Marteens
99
99
Como ya hemos isto, en estos casos coniene usar un mtodo annimo, para que la llamada a
FindAll y la condicin concreta que exigimos estn cercanas espacialmente.
Una expresin lambda es simplemente una nuea manera de escribir mtodos annimos. Gracias a
ellas, la llamada a FindAll en el ejemplo anterior se podra simpliicar de esta manera:
int[] result = Array.FindAll(
new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }, x => x % 2 != 0);
le resaltado en amarillo la expresin lambda. Por una parte, tenemos que teclear menos, y eso es
siempre de agradecer. Pero lo principal es que ahora es mas claro el objetio de la llamada. o eso
me parece a m.
Para entender cmo ha sido posible tal simpliicacin, eamos los pasos por los que ha eolucio-
nado la expresin de nuestro ejemplo:
1 delegate(int x) { return x % 2 != 0; }
2 (int x) => { return x % 2 != 0; }
3 (int x) => x % 2 != 0
4 (x) => x % 2 != 0
5 x => x % 2 != 0
La primera expresin corresponde a un mtodo annimo, completamente legal desde el punto de
ista de C
4
ersin 2.0. La primera mutacin es puramente sintactica, y su resultado es la segunda
expresin. lemos cambiado la palabra clae delegate por el smbolo => y luego hemos intercam-
biado la posicin de este smbolo con la de la lista de parametros del mtodo annimo.
Ll siguiente cambio es igual de radical, y aecta al bloque de instrucciones. Cuando se trabaja con
mtodos annimos, es muy recuente que el bloque de instrucciones slo contenga una instruccin,
y que sta instruccin sea un return seguido de una expresin. Ll cambio ha consistido en eliminar
las llaes que delimitaban el bloque, porque al existir una sola instruccin se uelen superluas, a la
ez que eliminamos la palabra clae return y el terminador de instrucciones, es decir, el punto y
coma. Ll resultado es la tercera expresin de la secuencia.
La tercera transormacin a mas alla de lo meramente sintactico: el compilador de C
4
3.0 permite
inerir los tipos de parametros de una expresin lambda. siempre que sea posible, claro. Ln nues-
tro caso, lo es. Ll parametro de tipo con el que instanciamos el mtodo genrico FindAll tiene que
ser obligatoriamente el tipo entero, pues el primer parametro de FindAll es un ector de alores
enteros. Seamos sinceros: al presentar el ejemplo, hemos establecido explcitamente el parametro de
tipo en FindAll, pero esto es innecesario incluso en C
4
2.0. Comprubelo!
Ln consecuencia, podemos eliminar el tipo en la declaracin de parametros de la expresin lambda.
Como slo tenemos un parametro en esa declaracin, y su tipo es inerido por el compilador, pode-
mos dar el ltimo paso de la metamorosis y eliminar los parntesis alrededor de la x:
x => x % 2 != 0
Podemos leer` la expresin anterior de la siguiente manera:
luncin que recibe un parametro x, y deuele true si x es un nmero impar.
Al presentar la orma inal de la llamada a FindAll, apliqu otras dos simpliicaciones. \a he mencio-
nado una de ellas: podemos dejar que el compilador iniera el parametro de tipo de la llamada a
FindAll, pues la llamada tiene lugar sobre un ector de enteros. La segunda simpliicacin corres-
ponde a un nueo truco de C
4
3.0 relacionado con las expresiones de creacin de ectores:
new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 } // Antes
new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 } // Ahora
Ln la nuea ersin del lenguaje, no hace alta indicar que se trata de un ector de enteros, pues
todos los elementos del ector pertenecen, sin lugar a dudas, a este tipo.
Intuitive C
#

Copyright 2004-200S, Ian Marteens
100
100
Equ|va|enc|a estructura|?
,Cual es el tipo de datos de una expresin lambda Lidentemente, se trata de un tipo delegado
pero ,cual tipo, en concreto Por suerte, existe bastante lexibilidad en la atribucin de tipos delega-
dos a expresiones. Imagine que deine estos dos tipos, aparentemente equialentes:
public delegate void MyHandler1(object sender, EventArgs e);
public delegate void MyHandler2(object sender, EventArgs e);
Necesitamos un mtodo con un prototipo compatible, aunque sea triial:
public void AHandler(object sender, EventArgs e)
{
}
Con las piezas presentadas, podemos ejecutar estas dos asignaciones sin mayor problema:
MyHandler1 eh1 = AHandler;
MyHandler2 eh2 = AHandler;
Sin embargo, el compilador se queja si intentamos lo siguiente:
eh1 = eh2;
Ni siquiera con una conersin explcita de tipos, el compilador se mostrara dispuesto a pasar por
el aro. \ no intente el truco sucio de conertir primero al tipo comn Delegate para luego transor-
mar el delegado en el tipo del lado izquierdo: el compilador le mirara de reojo y no dira nada, pero
sera el entorno de ejecucin el que protestara ruidosamente.
Lo que ocurre es que AHandler, sin los parntesis necesarios para su ejecucin, no es una erdadera
expresin, con todos sus derechos y deberes, sino un truco sintactico, que se atiene a otras reglas.
Lsta instruccin, por ejemplo, sera incorrecta:
AHandler.BeginInvoke(null, null, null, null);
Ln contraste, esta instruccin s es aceptada:
((MyHandler1)AHandler).BeginInvoke(null, null, null, null);
Si lo preiere, puede considerar que AHandler, a secas, pertenece a un tipo de datos cocido slo a
medias. Mientras no termine su coccin, es compatible con cualquier tipo delegado con un proto-
tipo compatible. Pero eso no signiica que exista equialencia estructural entre los tipos delega-
dos. o si lo preiere, entre tipos delegados debidamente cocinados.
Voliendo a las expresiones lambda, es acil er que stas pertenecen a tipos medio cocidos, y que
son compatibles con cualquier tipo delegado con prototipo compatible. Ln particular, .NL1 deine
toda una amilia de tipos delegados genricos en el espacio System:
public delegate TResult Func<TResult>();
public delegate TResult Func<T1, TResult>(T1 arg1);
public delegate TResult Func<T1, T2, TResult>(T1 arg1, T2 arg2);
. y as sucesiamente, hasta llegar a cuatro parametros de entrada. Si necesita guardar una reeren-
cia a una expresin lambda, puede echar mano de estos tipos predeinidos:
Func<int, bool> esPar = x => x % 2 == 0;
Ln este ejemplo, la expresin recibe un entero, y deuele un alor lgico que indica si el entero es
o no un nmero par.
Arbo|es de expres|ones
Ll tipo Func es, al in y al cabo, un tipo delegado. ,Cree posible transmitir un delegado, que no es
mas que un puntero a un mtodo, a tras de una conexin remota Si quiere programar aplicacio-
nes de bases de datos en arias capas, la transmisin remota de condiciones, de una manera u otra,
Iteracin
Copyright 2004-200S, Ian Marteens
101
101
es indispensable. ,Sera posible guardar una copia de un delegado de una sesin a otra, utilizando
persistencia Se me ocurren arias ideas, pero todas son algo barrocas. y poco estables. ,\ si
necesitamos sintetizar` una uncin nuea en tiempo de ejecucin Ls cierto que podemos utilizar
el API de emisin de cdigo, pero es una tcnica de muy bajo niel.
Lxiste una alternatia a guardar una reerencia a una expresin lambda en un delegado: conertir la
expresin en un arbol de expresiones. Lsta ez, el tipo que necesitamos es una clase genrica, dei-
nida en el espacio System.Linq.Expressions:
public class Expression<TDelegado> { }
Ln teora, el lenguaje no orece ningn recurso general para conertir un tipo delegado en una clase
arbitraria, como Expression. Ls el compilador el encargado de tratar estas asignaciones especiales de
manera dierente. le aqu un sencillo ejemplo:
Expression<Func<int, bool>> esPar = x => x % 2 == 0;
Func<int, bool> fnPar = esPar.Compile();
if (fnPar(2) && !fnPar(3))
Console.WriteLine("El cielo NO se va a caer.");
Ll uego de artillera se concentra en la primera instruccin, pues el compilador traduce esa simple
lnea en cdigo equialente al siguiente:
ParameterExpression px =
Expression.Parameter(typeof(int), "x");
Expression body = Expression.Equal(
Expression.Modulo(px, Expression.Constant(2, typeof(int))),
Expression.Constant(0, typeof(int)));
Expression<Func<int, bool>> esPar =
Expression.Lambda<Func<int, bool>>(body, px);
Como e, el compilador construye un arbol a partir de la expresin lambda. Los nodos del arbol
son acilmente reconocibles en la expresin original.
Una ez construido el arbol, para ejecutarlo debemos llamar al mtodo Compile, que nos deuele
un alor de tipo delegado:
Func<int, bool> fnPar = esPar.Compile();
Obsere que el tipo exacto del delegado corresponde al parametro de tipo especiicado al declarar
la expresin.


Copyright 2004-200S, Ian Marteens
102
JJ JJ JJ JJ
REFLEXON

S OPININ GLNLRAL LN1RL QUILNLS se dedican a la Inteligencia Artiicial que la consciencia
humana esta relacionada, de algn modo, con la capacidad autorreerencial. Lsto es, simplii-
cando un poco, que todo sistema consciente debe poder emitir opiniones o diagnsticos sobre su
propio uncionamiento. La autorreerencialidad sera una caracterstica necesaria, aunque no sui-
ciente, para la inteligencia.
8|stemas h|pocondr|acos
Por este motio, desde que surgieron los primeros lenguajes de programacin se intent que algu-
nos de ellos pudiesen analizarse a s mismos. Uno de los mas exitosos ue, o mas bien es, LISP. Se
trata de un lenguaje uncional con buen soporte para el manejo de listas. \ da la casualidad` de
que los programas LISP son, precisamente, listas. Otro ejemplo de este tipo es Prolog, el lenguaje
de programacin lgica. Prolog tambin manejaba listas con soltura, y en las ersiones mas moder-
nas, ampliaban bastante la lista de estructura de datos soportadas.
Los lenguajes antes mencionados no implementan el llamado paradigma procedimental. Puede que
el pionero en anadir relexin a un lenguaje de este tipo haya sido Smalltalk: no lo puedo airmar
con seguridad porque nunca me atrajo la idea de programar con una mano atada a la espalda. Ln
cualquier caso, el hecho es que, cuando entra Jaa en escena, ya trae consigo un API de reflexin
respetable. ,la descubierto ya el actor comn Lectiamente: todos los mencionados son lengua-
jes que comenzaron su existencia como lenguajes interpretados. Ls cierto que todos ellos termina-
ron con compiladores natios, pero la moraleja es la siguiente: hasta hace muy poco, era consenso
general que anadir inormacin disponible en tiempo de ejecucin sobre un programa, era un
desperdicio. a no ser que el programa tuiese que ser interpretado.
,Cmo demostrar que mi airmacin no es calumniosa Ah tiene el caso de C--. Los autores
queran que uese el lenguaje mas eiciente del mundo. Consideraron, por ejemplo, que anadir cua-
tro bytes en cada instancia de objeto para almacenar un puntero a una tabla de mtodos irtuales,
era un derroche inaceptable. 1enga en cuenta que estamos hablando de los ordenadores del princi-
pio y mitad de los 80s. Como resultado, no se permiti que existiese un ancestro comn a todas las
clases ,como s lo hay en Delphi, muy similar a C--, pero disenado en los 90s,. A principios de los
90s, C-- comenz a incluir lo que entonces se llamaba Runtime Type Information ,R11I,, pero slo
como opcin, y en cualquier caso, con muchas limitaciones en la inormacin disponible. Delphi
marc una nuea etapa: no slo oreca mucha mas R11I de serie que C--, sino que sta era uno
de los pilares del API de persistencia. \ Delphi nunca ue un lenguaje interpretado.
A no ser que haya empezado a leer el libro en esta pagina, ya imaginara lo que oy a decir a
continuacin: que el API de relexin de C
4
y de todos los lenguajes de .NL1, es mucho mas
grande, mas grueso y ademas unciona mejor que el de Jaa. Bueno, ,para qu negar la realidad
La plataorma .NL1 pone a disposicin del interesado, en tiempo de ejecucin, toda la inormacin
que quepa imaginar, superando ampliamente la oerta y el alcance de lo que orece la maquina ir-
tual de Jaa. Ls mas: esta dierencia es parte de los principios de diseno. Cuando present los tipos
genricos, mostr cmo se maniestaba la dierente ilosoa de diseno de ambas plataormas: mien-
tras que Jaa implementa los genricos mediante trucos del compilador, en .NL1 los tipos genri-
cos son ciudadanos de primera y participan plenamente en la implementacin del API de relexin.
|
Reflexin
Copyright 2004-200S, Ian Marteens
103
103
Ln la plataorma .NL1, la relexin es indispensable para los sistemas de persistencia, de programa-
cin remota, de seguridad e incluso para la implementacin de la recoleccin de basura. La re-
lexin en .NL1 no se limita a aeriguar inormacin sobre un ensamblado, mdulo o aplicacin ya
existente, sino que orece tambin arios esquemas para la generacin de cdigo ejecutable. Ll de
mas bajo niel, y por lo tanto mas potente, se conoce con el nombre del espacio de nombres donde
se alojan la mayora de sus clases: Reflection.Emit.
Por ltimo, el API de relexin nos permite acceder a una tcnica noedosa disponible en todos los
lenguajes .NL1: el uso de atributos.
|nformac|n sobre t|pos
Cuando utilizamos cualquiera de las tcnicas de relexin, tropezamos con la clase System.Type,
cuyas instancias representan descriptores de tipos. Se trata de una clase abstracta: las clases deria-
das concretas distinguen, por ejemplo, entre descriptores de tipos ya existentes en un ensamblado o
descriptores de tipos creados sobre la marcha mediante generacin de cdigo. Ln concreto, cuando
pedimos el descriptor de un tipo normal`, ya compilado, obtenemos en realidad una instancia de la
clase System.RuntimeType. Ahora bien, esta es una clase interna, por lo que debemos manejar la
instancia que recibimos mediante los miembros heredados de la clase base, esto es, a tras de la
clase System.Type.
lay unas cuantas ormas de obtener un descriptor de tipo. Si quiere el descriptor de un tipo con-
creto, del que no tiene una instancia en la mano, debe usar el operador typeof de C
4
. Imagine que
se encuentra en el mtodo de inicio de una aplicacin de consola:
class Program
{
static void Main(string args[])
{
System.Type t = typeof(Program);
Console.WriteLine(t.FullName);
}
}
La propiedad FullName de la clase Type deuele el nombre completo de la clase asociada al
descriptor, que incluye el espacio de nombres al que pertenece la clase. Si la clase Program del ejem-
plo reside dentro del espacio ConsoleApplication1, la aplicacin escribira en la consola:
Conso1eApp11caf1on1.Pogam
Si tenemos una instancia y queremos obtener el descriptor correspondiente a la clase de la instancia,
debemos usar el mtodo GetType, que se declara en System.Object. ,Qu tal si le administramos a
Type una dosis de su propia medicina
class Program
{
static void Main(string args[])
{
System.Type t = typeof(Program);
Console.WriteLine(t.FullName);
t = t.GetType();
Console.WriteLine(t.FullName);
}
}
EJERCICIO PROPUESTO
cQu debe escribir el ultimo ejemplo en la consola? No necesita ejecutar el ejemplo para responder.
Lstas dos tcnicas son slo las mas directas. Una ez que tenga una instancia de System.Type en la
mano, podra naegar por las relaciones de herencia e implementacin de tipos de interaz que aec-
tan a la clase representada por el descriptor. La clase base se obtiene mediante una propiedad:
public Type BaseType { get; }
Intuitive C
#

Copyright 2004-200S, Ian Marteens
104
104
La lista de interaces implementadas o heredadas se obtiene mediante una uncin:
public Type[] GetInterfaces();
Ln este ltimo caso, la uncin deuele un ector de descriptores de tipos. Si el tipo no imple-
menta ni hereda interaces, GetInterfaces deuele un ector de tamano cero, en lugar de un puntero
nulo. Podemos mezclar estas llamadas para aeriguar la genealoga de un tipo de datos:
static void Main(string[] args)
{
Type t = typeof(int);
while (t != null)
{
Type[] intfs = t.GetInterfaces();
Console.Write(t.FullName);
if (intfs.Length > 0)
{
Console.Write(" implementa");
foreach (Type intf in intfs)
{
Console.Write(" ");
Console.Write(intf.Name);
}
}
Console.WriteLine();
t = t.BaseType;
}
}
Cuando la indagacin parte del tipo int, como en el ragmento anterior, se obtiene lo siguiente:
5ysfem.lnf32 1mp1emenfa lCompaab1e lIomaffab1e lConvef1b1e
lCompaab1e1 lLquafab1e1
5ysfem.va1ue1ype
5ysfem.Obecf
Obsere que para escribir el nombre de los tipos de interaz he usado la propiedad Name en ez de
FullName. La clase int implementa dos tipos de interaz genricos, y el nombre completo de estos
dos tipos es muy largo, pues contiene el nombre completo del ensamblado donde se deine el pro-
pio tipo entero. Si siente curiosidad, cambie Name por FullName y compare los resultados.
Ref|ex|n y ensamb|ados
La plataorma .NL1 es muy puntillosa, y con toda la razn, sobre la identidad de los tipos de datos.
Dado el niel extraordinario de dinamismo que permite, es muy importante que eriique, por ejem-
plo, que la clase SqlConnection que ha cargado en una aplicacin es la SqlConnection de erdad`,
made in Microsoft, por as decirlo, y no una clase pirata que delega todos los mtodos en la erda-
dera SqlConnection a la ez que nos mantiene inormados de la actiidad de una maquina zombi. Ln
principio, no hay nada que me impida declarar una clase llamada System.Data.Sql-
Cliente.SqlConnection en un ensamblado mo. Lo que .NL1 no me permitira es dar el cambiazo a la
DLL que contiene la clase erdadera.
Por esta razn, las coordenadas` completas de un tipo de datos deben indicar tambin en cual
ensamblado ha sido deinido. Ln consecuencia, nuestro paseo por el API de relexin debera haber
comenzado por los ensamblados y mdulos. La clase que representa un ensamblado cargado en el
espacio de memoria de un proceso se llama Assembly, y se declarada en System.Reflection. lay unas
cuantas maneras de obtener una instancia de esta clase. Por ejemplo, si ya tenemos un descriptor de
tipos a nuestra disposicin, podemos obtener el descriptor del ensamblado donde se declara el tipo
por medio de la propiedad Assembly:
Console.WriteLine(typeof(int).Assembly.FullName);
La instruccin anterior mostrara la siguiente cadena en la consola:
Reflexin
Copyright 2004-200S, Ian Marteens
105
105
msco11b, ves1on=2.0.0.0, Cu1fue=neufa1,
Pub11ckey1oken=b77a5c561934e09
Lsto se conoce como el display name, o nombre para mostrar, del ensamblado. Si en ez de utilizar
el tipo predeinido int hubisemos partido de la clase Program de nuestra aplicacin de consola,
obtendramos algo parecido a lo siguiente:
Conso1eApp11caf1on1, ves1on=1.0.0.0, Cu1fue=neufa1,
Pub11ckey1oken=nu11
Ln ambos casos, se muestran los cuatro elementos que participan en la identidad de un ensam-
blado:
1 Ll nombre simblico del ensamblado.
2 La ersin. Recuerde que .NL1 permite la presencia y ejecucin simultanea de distintas ersio-
nes de un mismo ensamblado, segn las necesidades de otros componentes.
3 La cultura. que casi siempre se traduce en el idioma o dialecto.
4 Por ltimo, la clae pblica del abricante, si el ensamblado ha sido irmado.
N
O
T
A

El ensamblado mscorlib tiene caracteristicas especiales. Es el ensamblado donde se definen los tipos
basicos de .NET, y por lo general todos los restantes ensamblados hacen uso de l. Tericamente, el
compilador de C
#
permite compilar nuevos ensamblados que no hagan referencia automaticamente
a mscorlib. pero hasta el momento, no he tenido necesidad de hacer tal cosa.

1ambin se puede acceder a determinados ensamblados a tras de mtodos estaticos de la propia
clase Assembly:
public static Assembly GetExecutingAssembly();
public static Assembly GetCallingAssembly();
public static Assembly GetEntryAssembly();
Ll primer mtodo deuele el ensamblado donde se deine el mtodo desde donde se ejecuta la
llamada. Ll segundo nos indica el ensamblado donde se deine el mtodo que nos ha llamado. Ll
ltimo mtodo de la lista nos indica en qu ensamblado se deini el mtodo Main que se ha utili-
zado como punto de entrada de la ejecucin de la aplicacin en curso.
Obsere que todas estas tcnicas permiten obtener descriptores de ensamblados que ya se han
cargado en el proceso. Muchas aplicaciones, sin embargo, necesitan cargar ensamblados en tiempo
de ejecucin. Lste sera el caso, por ejemplo, de una aplicacin extensible mediante plug-ins. La
orma preerida para cargar un ensamblado en memoria es usar el mtodo estatico Load, de la clase
Assembly, pasandole como parametro el nombre completo del ensamblado:
System.Reflection.Assembly asm = System.Reflection.Assembly.Load(
"System.Data, Version=2.0.0.0, Culture=neutral, " +
"PublicKeyToken=b77a5c561934e089");
No se deje amedrentar por la aparente complejidad de la cadena: esta identiicacin es un inariante,
que no cambia de maquina en maquina. Si usted quiere cargar un ensamblado que ya conoce, slo
tiene que aeriguar cual es su nombre completo. Basta con abrir el ensamblado con Relector y
seleccionar el nodo raz, con cualquier lenguaje excepto IL en el combo de seleccin del lenguaje:

Intuitive C
#

Copyright 2004-200S, Ian Marteens
106
106
,\ si el ensamblado no existe en el momento en que compilamos la aplicacin Lsta es la situacin
tpica cuando una aplicacin carga plug-ins de terceros. Ln tal caso, hay tambin alternatias:
1 Que el usuario que suministra el plug-in nos suministre por su cuenta el nombre completo del
mismo. No es una opcin muy realista.
2 Que el usuario nos suministre la ubicacin sica del ichero. Lntonces podemos usar el mtodo
estatico LoadFrom, tambin de la clase Assembly, para cargar el ensamblado:
System.Reflection.Assembly asm = System.Reflection.Assembly.LoadFrom(
@"C:\Windows\Microsoft.net\Framework\v2.0.50727\System.Data.dll");
Le adierto que LoadFrom tiene sus inconenientes, porque no incluimos inormacin completa
sobre la identidad del ensamblado.
Suponga que inalmente hemos cargado un ensamblado que nos dicen que contiene clases para
extender nuestra aplicacin. 1enemos ya una ariable de tipo Assembly, digamos que se llama asm.
,Qu podemos hacer con el descriptor del ensamblado La principal operacin sera localizar un
tipo por su nombre, o simplemente, podemos pedir los descriptores de todos los tipos declarados
dentro del ensamblado:
foreach (Type t in asm.GetTypes())
if (t.IsPublic)
Console.WriteLine(t.FullName);
Para no hacer demasiado largo el listado de tipos, el ragmento anterior declara los tipos internos
mediante la propiedad IsPublic del descriptor de tipo. Lsto quiere decir que GetTypes deuele to-
dos los tipos del ensamblado, incluyendo los tipos presuntamente internos!
Si le escandaliza este comportamiento, medtelo un poco: recuerde que la encapsulacin tiene como
objetio la reduccin de dependencias entre mdulos de sotware. Su objetio no es hacer de guar-
dian de la propiedad intelectual. Si realmente necesitamos proteger nuestro cdigo de ojos
irrespetuosos, lo que debemos hacer es ofuscar el ensamblado.
Hanejo d|nm|co de |nstanc|as
Una ez que tenemos un descriptor de tipo en la mano, el API de relexin nos permite crear
instancias del tipo, y acceder con total libertad a los miembros del tipo, incluso si no tenamos
conocimiento del tipo durante la compilacin. De hecho, es este ltimo escenario el que nos inter-
esa mas, pues corresponde a la manera en que tendra que trabajar una aplicacin extensible me-
diante plug-ins.
Si quiere jugar un poco con la tcnica, cree una solucin en Visual Studio y anadale primero un
proyecto de tipo Biblioteca de clases. Luego, anada dos ormularios de \indows lorms a la biblio-
teca. Para distinguirlos entre s, puede cambiar la propiedad BackColor en los dos ormularios.
Guarde entonces el proyecto y llamelo Ventanas:

Simularemos que esta biblioteca es una extensin de la erdadera aplicacin, que amos a crear a
continuacin. Lsta ez anadiremos un proyecto Aplicacin para Windows a la solucin. Marquela
como proyecto de inicio, para que al pulsar el botn de ejecucin, sea el ejecutable generado por
este proyecto el que se ejecute. Lcharemos mano de un truco: nos interesa, para simpliicar el c-
digo uente, que al ejecutarse el nueo proyecto, la DLL del otro proyecto est disponible en el
Reflexin
Copyright 2004-200S, Ian Marteens
10
107
mismo directorio que el ejecutable. lay una orma muy sencilla de lograrlo: hacer que el nueo
proyecto haga reerencia al proyecto anterior. Para establecer esta dependencia, seleccione el nodo
Referencias del proyecto ejecutable y ejecute el comando Agregar referencia del men local. Ln el
dialogo que aparece, actie la tercera pagina y seleccione el proyecto de biblioteca:

Recuerde que slo necesitamos este paso para eitar moer manualmente la DLL al directorio
donde nos interesa que est. Por este motio, ingiremos que el ensamblado Ventanas no se carga
automaticamente dentro del proceso ejecutable.
Intercepte el eento Load del ormulario principal del proyecto de aplicacin:
private void Form1_Load(object sender, EventArgs e)
{
string filename = Path.Combine(
Application.StartupPath, "ventanas.dll");
if (File.Exists(filename))
{
Assembly asm = Assembly.LoadFrom(filename);
foreach (Type t in asm.GetTypes())
if (t.IsPublic && t.IsSubclassOf(typeof(Form)))
CrearVentana(t);
}
}
Primero, preparamos una cadena con la ruta completa hasta la carpeta donde suponemos que estara
el ichero Ventanas.dll. Ll mtodo estatico Combine, de la clase Path, concatena la ruta con el nom-
bre del ichero, teniendo cuidado de que haya exactamente una barra de separacin entre la ruta y el
nombre de ichero. Una ez que comprobamos que existe el ichero con las extensiones, cargamos
el ensamblado en nuestro proceso con la ayuda del mtodo LoadFrom.
N
O
T
A

En honor a la verdad, hay que recordar que el ensamblado ya ha sido cargado junto con el ejecuta-
ble. Podriamos obtener el descriptor del ensamblado a travs del descriptor del tipo Form2 del
ensamblado Ventanas, o incluso a travs de Form1. siempre que tuvisemos cuidado al cualificar
correctamente la clase. Si tiene tiempo, compruebe que en ambos casos se nos entrega el mismo
descriptor de ensamblado.

Lsta ez, cuando recorremos todos los tipos pblicos del ensamblado con las extensiones, separa-
mos aquellos tipos que descienden, directa o indirectamente, del tipo Form. Ll mtodo que hemos
usado, IsSubclassOf, slo unciona con la herencia de clases. Cuando hay tipos de interaz por me-
dio, preiero usar el mtodo IsAssignableFrom, aunque hay que tener cuidado con este ltimo pues
inierte el papel de los objetos.
Si el tipo es pblico y es un ormulario, llamamos al mtodo CrearVentana, para que cree una instan-
cia de esta clase y la muestre en pantalla:
private void CrearVentana(Type t)
{
Form f = (Form)t.InvokeMember(null,
BindingFlags.CreateInstance | BindingFlags.Instance |
BindingFlags.Public, null, null, new object[0]);
f.Show();
}
Intuitive C
#

Copyright 2004-200S, Ian Marteens
108
10S
Sabemos que los ormularios creados por Visual Studio tienen un constructor pblico sin parame-
tros. Vamos a aproechar este conocimiento para eitarnos la bsqueda de un constructor adecuado
dentro del descriptor del tipo. CrearVentana delega casi todo el trabajo al siguiente mtodo de la
clase System.Type:
public object InvokeMember(
string name, BindingFlags invokeAttr, Binder binder,
object target, object[] args);
InvokeMember es un erdadero campen. La anterior es solamente una de sus tres ariantes. Gracias
a l, podemos poner en uncionamiento cualquiera de los miembros del tipo de datos cuyo descrip-
tor tenemos a mano. Da igual si se trata de leer un campo, ejecutar una lectura sobre una propiedad,
modiicar una propiedad, ejecutar un mtodo de instancia o estatico, o incluso ejecutar un construc-
tor: InvokeMember es lo que necesitamos.
Lsta ersatilidad se paga con un prototipo complicado. La ersin que hemos usado pide primera-
mente el nombre del miembro del tipo al que amos a acceder. Como se trata de un constructor,
podemos pasar un puntero nulo, porque el mtodo a a ignorarlo. Ls en el segundo parametro
donde decimos que amos a ejecutar el constructor. BindingFlags es un enumeratio cuyos alores
pueden combinarse entre s. CreateInstance pide un constructor, Public, que ste sea pblico, e In-
stance. bueno, hay que poner Instance por alguna razn que escapa a mi comprensin. Ll parame-
tro de tipo Binder sire para suministrar un componente auxiliar por si hay que elegir entre
alternatias sobrecargadas del mtodo, y si es necesario, para conertir algn que otro parametro.
Ln nuestro caso, no lo necesitamos. Pasamos entonces la instancia del objeto, que al tratarse de un
constructor, no existe, y inalmente, la lista de parametros, para la que suministramos un ector de
longitud cero.
InvokeMember se ocupa de todos los detalles sucios, crea el ormulario y nos lo deuele con una
etiqueta donde se lee Esto es un objeto, y no una pipa. Nosotros, que sabemos que se trata de un
ormulario, aplicamos una conersin de tipo para quedarnos con un puntero decente sobre la
criatura. Una ez que tenemos un ormulario en la mano, podemos hacer con l todas las cosas que
se le hacen a un ormulario. Ln este caso, mostrarlo en pantalla ejecutando su mtodo Show.
Ln principio, podramos haber simpliicado bastante la implementacin de CrearVentana, e incluso
eliminarla, sustituyendo la llamada a InvokeMember por una llamada al mtodo CreateInstance de la
clase Assembly:
foreach (Type t in asm.GetTypes())
if (t.IsPublic && t.IsSubclassOf(typeof(Form)))
((Form)asm.CreateInstance(t.FullName)).Show();
La desentaja de CreateInstance es que repite la bsqueda del tipo dado su nombre, y por supuesto,
que es un mtodo menos lexible que InvokeMember.
Atr|butos
Ll ejemplo que acabamos de er es una demostracin de uerza bruta: localizamos todos los
ormularios dentro de un ensamblado y los creamos todos de sopetn, porque nos da la gana. Ln la
ida real, necesitaramos que cada tipo encontrado nos diese mas inormacin. Por ejemplo, lo l-
gico es que no creasemos la entana de manera inmediata, sino que creasemos un comando de
men para que el usuario crease la entana cuando le apeteciera. ,Qu texto debera mostrar el
comando de men Ls muy importante que comprenda que necesitamos ese texto antes de que
hayamos tenido tiempo y oportunidad para crear la primera instancia de la clase.
La respuesta a este problema, y a otros cuantos, esta en una tcnica noedosa popularizada por
.NL1. Lste ragmento de cdigo muestra una tpica declaracin de propiedad en Delphi:
// Esto es Delphi:
property Edad: Integer read FEdad write SetEdad default 18;
Reflexin
Copyright 2004-200S, Ian Marteens
109
109
La parte en la que debe reparar es la clausula default. Como sugiere su nombre, sire para indicar
cual es el alor por omisin de la propiedad. Slo lo indica, sin embargo: la clausula default no
inicializa la propiedad. Su presencia no modiica en absoluto el cdigo de inicializacin de la clase.
De hecho, si no hacemos algo, el campo FEdad se inicializa automaticamente a cero. Ll alor indi-
cado por la clausula se copia en una zona especial del descriptor de la clase en tiempo de ejecucin.
Si hemos utilizado una clausula como la anterior, somos responsables de inicializar correctamente el
campo durante la construccin de las instancias de la clase.
,Qu sentido tiene entonces Ls el subsistema de persistencia de Delphi el que aproecha esta
inormacin, en el momento en que tiene que guardar el estado de una instancia de la clase. Su-
ponga que esta trabajando con el disenador de ormularios de Delphi. la trado una instancia de la
clase que contiene la propiedad Edad, y ha eectuado un par de cambios en otras propiedades.
Cuando guardamos el estado de la instancia, Delphi comprueba que el alor de Edad sigue siendo el
alor especiicado en la clausula default asociada a la propiedad. e iniere que no hace alta guar-
dar el alor de la propiedad.
Resumamos, caminando de espaldas sobre nuestros pasos:
1 Ll subsistema de persistencia de Delphi intenta ahorrar espacio ,y en algunos casos, tiempo de
ejecucin, guardando solamente el alor de las propiedades que han sido modiicadas expresa-
mente por el programador.
2 Para ello, necesita saber cual es el alor por omisin de cada propiedad. Por desgracia, es muy
complicado aeriguarlo analizando el cdigo uente del constructor de la clase. Ademas, el alor
se necesita en tiempo de ejecucin.
3 La solucin consiste en indicar explcitamente el alor por omisin de las propiedades, aunque
esto introduzca redundancia en la declaracin de la clase.
4 Para que podamos indicar el alor por omisin de una propiedad, los autores de Delphi preie-
ron una extensin sintactica especial para la declaracin de propiedades.
La lgica de los tres primeros puntos es irreprochable. Ll cuarto punto es un disparate. No pode-
mos modiicar la sintaxis de un lenguaje de programacin cada dos por tres. Ahora son los alores
por omisin, pero manana quin sabe.
Cuando Anders lejlsberg disen C
4
, ya traa la leccin aprendida. Ln ez de complicar la sintaxis
del lenguaje con clausulas ad hoc, recurri a una tcnica mas potente: el uso de atributos. Veamos
cmo se declara el alor por omisin de una propiedad en C
4
:
[DefaultValue(18)]
public int Edad
{
get { }
set { }
}
Un atributo es inormacin que se asocia a una declaracin, de manera que est disponible para la
aplicacin en tiempo de ejecucin. La declaracin puede ser de propiedad, de campo, de mtodo, de
parametro e incluso de mdulo o de ensamblado. Por ejemplo, en la siguiente declaracin he aso-
ciado un atributo a la clase y otro a una de sus propiedades:
[Obsolete("Utilice la clase Cliente")]
public class Persona
{
[DefaultValue(18)]
public int Edad { get { } set { } }

}
Ll atributo Obsolete se reiere en realidad a una clase llamada ObsoleteAttribute, que desciende de la
clase predeinida Attribute: se trata de un conenio para abreiar el nombre las clases de atributos.
Intuitive C
#

Copyright 2004-200S, Ian Marteens
110
110
Lsta clase ObsoleteAttribute orece un constructor pblico que recibe una cadena como parametro.
Por lo tanto, al asociar el atributo a la clase Persona, el compilador crea una instancia de Obsolete-
Attribute y la engancha al descriptor interno de la clase Persona:

Aunque hayamos creado mil y una instancias de la clase Persona, slo hay una instancia del atri-
buto en memoria, como maximo.
Aunque todaa no hayamos creado una sola instancia de Persona, de todos modos podemos
recuperar sus atributos a tras del descriptor de la clase.
Algo parecido ocurre con la propiedad Edad y el atributo DefaultValue: se crea internamente una
instancia de la clase DefaultValueAttribute con el correspondiente constructor, y se asocia al descrip-
tor de la propiedad, que existe a su ez dentro del descriptor de la clase Persona.
,Quin hace uso de esta inormacin aportada por los atributos Depende. Ln el caso de Default-
Value, como he dicho, el subsistema de persistencia aproecha esta inormacin para generar cdigo
de inicializacin de componentes. Ln el caso de Obsolete, es el propio compilador quien comprueba
si las clases mencionadas en un proyecto contienen este atributo, para quejarse en caso airmatio.
Ln el caso del atributo WebServiceAttribute, que se puede aplicar a una clase, es la parte del motor de
ASP.NL1 que implementa sericios \eb la que busca este atributo y, cuando lo encuentra en una
clase, prepara la inraestructura de la clase para permitir llamadas remotas a sus mtodos:
[WebService(
Description="Mi servicio",
Namespace="http://intsight.com/")]
public class MiServicioWeb
{

}
Ln el caso de WebService, he utilizado una sintaxis dierente para crear el atributo: en ez de pasar
parametros posicionales, he utilizado dos parametros con nombres. No se deje enganar por la
terminologa: Description y Namespace no son erdaderos parametros de un constructor de la clase
WebServiceAttribute, sino nombres de propiedades de esta clase. Para crear el objeto correspon-
diente al atributo, primero se crea la instancia llamando a un constructor sin parametros, y luego se
le asignan alores a las dos propiedades mencionadas.
N
O
T
A

Recuerde: un atributo es simplemente una forma de anadir anotaciones al cdigo fuente, con la
peculiaridad de que podemos recuperar luego estas anotaciones en tiempo de ejecucin. Un atri-
buto, en si, no tiene otros efectos en la aplicacin o biblioteca, aunque el propio compilador y el en-
torno de desarrollo utilizan atributos para sus fines particulares.

0|seo de atr|butos
Volamos a nuestro ejemplo inicial: una aplicacin extensible mediante plug-ins. \a sabemos cmo
abrir un ensamblado, recorrer sus tipos e identiicar los ormularios. Pero necesitamos mas control:
necesitamos marcar aquellos ormularios que realmente queremos que se utilicen para extender la
aplicacin nodriza. Ademas, si queremos crear comandos en un men para crear entanas, tendre-
mos que indicar el texto que deberan mostrar esos comandos, al menos. Ln una situacin ideal,
PERSONA
Nombre
Edad
Apellidos
DEFAULTvALUE
18
OBSOLETE
"Utilice la clase."
DESCRIPTOR DE CLASE
Reflexin
Copyright 2004-200S, Ian Marteens
111
111
deberamos indicar tambin el icono asociado al comando, y el orden relatio respecto a otras
extensiones de entanas. Pero de momento, nos conormaremos con el texto. Marcaremos los
ormularios disponibles como extensin mediante un atributo, y dicho atributo nos indicara el texto
del comando de men que crearemos.
Vamos a anadir una nuea clase en el proyecto DLL, en Ventanas:
[AttributeUsage(AttributeTargets.Class, AllowMultiple=false)]
public class MenuItemAttribute : Attribute
{
private string menuText;
public MenuItemAttribute()
{
this.menuText = String.Empty;
}
public MenuItemAttribute(string menuText)
{
this.menuText = menuText ?? String.Empty;
}
public string MenuText
{
get { return menuText; }
set { menuText = value ?? String.Empty; }
}
}
EJERCICIO PROPUESTO
Uno de los constructores y el mtodo de acceso para escritura de una propiedad de esta clase utilizan el operador
de fusin (p.88). cPor qu, o para qu?
Nuestra clase se llama MenuItemAttribute, cumpliendo con el requisito de las clases de atributos, que
deben contener Attribute como suijo, y desciende de la clase System.Attribute. La propia clase ha
sido marcada con un atributo predeinido:
[AttributeUsage(AttributeTargets.Class, AllowMultiple=false)]
Lste atributo indica que MenuItem slo podra usarse con clases ,esto excluye las estructuras!,, y que
para cada clase, el atributo slo podra mencionarse una sola ez, como maximo. lay dos
constructores pblicos en la clase: uno que no admite parametros, y otro que recibe un parametro
de tipo cadena. La cadena se utiliza para inicializar la propiedad pblica MenuText. Lsto signiica que
estos dos usos de la clase son equialentes:
[MenuItem("Texto para el comando")]
[MenuItem(MenuText = "Texto para el comando")]
Ln el primer caso, inicializamos la propiedad indirectamente, usando el constructor que recibe una
cadena de caracteres. Ln el segundo caso, la asignacin se ejecuta directamente sobre la propiedad,
como si tuisemos un parametro con nombre.
A continuacin, utilizaremos el nueo atributo para marcar todos los ormularios del proyecto
Ventanas en este plan:
[MenuItem("Primera ventana")]
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
}
Ln estos casos, hay que tener en cuenta si deseamos que un atributo sea heredado junto con una
clase. No olide que en Visual Studio podemos hacer que una clase de ormulario tenga como clase
Intuitive C
#

Copyright 2004-200S, Ian Marteens
112
112
base otro ormulario deinido por nosotros mismos. Ln nuestro caso, no nos interesa, pues cada
ormulario disponible debe tener su propio comando de men, con texto propio. 1enemos dos
opciones: asignar false en la propiedad Inherited de AttributeUsage, o excluir atributos heredados en
el cdigo que lee los atributos posteriormente. Nosotros utilizaremos la segunda a
Ref|ex|n sobre atr|butos
Lstamos ahora en la aplicacin nodriza. Lo primero que haremos es anadir un esqueleto de men a
su ormulario principal. Slo tenemos que arrastrar un componente MenuStrip al ormulario. Luego,
crearemos un comando directamente sobre la barra, como muestra la siguiente imagen:

Muy importante: llamaremos miVentanas al objeto de comando que corresponde al men desple-
gable Ventanas.
1enemos que modiicar la respuesta al mtodo Load del ormulario. Antes, recorramos los tipos
publicados por el ensamblado de extensin, y creabamos inmediatamente cada entana encontrada.
Ahora, crearemos un comando de men para cada tipo que descienda de Form y que enga deco-
rado por un atributo MenuItem:
private void Form1_Load(object sender, EventArgs e)
{
string filename = Path.Combine(
Application.StartupPath, "ventanas.dll");
if (File.Exists(filename))
{
Assembly asm = Assembly.LoadFrom(filename);
foreach (Type t in asm.GetTypes())
if (t.IsPublic && t.IsSubclassOf(typeof(Form)))
CrearMenu(t);
}
}
CrearMenu es el mtodo que se encargara de crear el comando de men a partir de la inormacin
que extraiga del atributo MenuItem. Para obtener los atributos asociados a un tipo de datos determi-
nado, utilizaremos el siguiente mtodo de la clase Type:
public object[] GetCustomAttributes(Type attributeType, bool inherit);
Ln esta ersin del mtodo, pasamos el descriptor de tipo del atributo en que estamos interesados.
Ll segundo parametro indica si aceptaremos atributos deinidos en un ancestro: en nuestro caso, no.
Obsere que GetCustomAttributes deuele un ector de objetos, al menos nominalmente. Lsto
signiica que tendremos que realizar una conersin de tipos sobre el alor deuelto por este m-
todo.
Lsta es, inalmente, nuestra implementacin de CrearMenu:
private void CrearMenu(Type t)
{
Ventanas.MenuItemAttribute[] attrs =
(Ventanas.MenuItemAttribute[])t.GetCustomAttributes(
typeof(Ventanas.MenuItemAttribute), false);
if (attrs.Length > 0)
{
string menuText = attrs[0].MenuText;
ToolStripItem item = miVentanas.DropDownItems.Add(menuText);
item.Tag = t;
Reflexin
Copyright 2004-200S, Ian Marteens
113
113
item.Click += new EventHandler(item_Click);
}
}
Como e, la mayor complicacin es la conersin de tipos. Se trata de una conersin entre tipos de
ectores, y slo es posible porque Object y MenuItemAttribute son, ambos, tipos de reerencia. Si
tuisemos que repetir esta llamada muchas eces, puede que nos interesase encapsular la coner-
sin dentro de un mtodo genrico como el siguiente:
public static T[] GetAttrs<T>(Type t, bool inherit) where T : Attribute
{
return (T[])t.GetCustomAttributes(typeof(T), inherit);
}
La limitacin de GetAttrs es que, al menos en .NL1 2.0, no puede beneiciarse de la inerencia de
tipos, y nos obliga a repetir el tipo del atributo dos eces, en ez de tres.
Ln CrearMenu he aproechado un truco: en ningn caso, GetCustomAttributes deuele un puntero
nulo. Si no se encuentran atributos del tipo indicado, el mtodo deuele un ector de longitud
cero. Lsto nos eita comprobaciones innecesarias sobre el alor de retorno. Ll resto de CrearMenu
es muy sencillo: si encontramos el atributo en el descriptor del tipo, creamos un comando de men,
anadindolo dentro de la lista de comandos del men desplegable Ventanas. Aproechamos la
propiedad Tag del objeto de comando para recordar a qu tipo de ormulario esta asociado el co-
mando, y inalmente enlazamos el eento Click del comando con un mtodo de respuesta a eentos
que hemos llamado item_Click:
private void item_Click(object sender, EventArgs e)
{
ToolStripItem item = sender as ToolStripItem;
if (item != null)
{
Type t = item.Tag as Type;
if (t != null)
CrearVentana(t);
}
}
Ll mtodo CrearVentana al que hace reerencia item_Click es nuestro iejo amigo, cuya implementa-
cin repito aqu para mayor comodidad:
private void CrearVentana(Type t)
{
Form f = (Form)t.InvokeMember(null,
BindingFlags.CreateInstance | BindingFlags.Instance |
BindingFlags.Public, null, null, new object[0]);
f.Show();
}
La siguiente imagen corresponde a la aplicacin en uncionamiento:

Note que el orden en que se descubren los tipos de ormularios y se crean los comandos, es arbitra-
rio. Piense en algn mecanismo que nos orezca algo mas de control sobre el orden de los coman-
dos.


Copyright 2004-200S, Ian Marteens
115
NDCE ALFABETCO

A
AppDomain 7
Application
Idle 75
ThreadApplication 75
rboles de expresiones 100
Array 11
assembly Ver ensamblados
Assembly
clase 104
CreateInstance 108
GetTypes 106
Load 105
LoadFrom 106, 107
Type 104
AsyncCallback 59
atributos 108
B
base 28
BeginInvoke 58
BindingFlags 108
boxing 50
C
clases
componentes 60
parciales 67
CLR
boxing 50
constructores 28
vectores 11
Combine
Delegate 57
Path 107
constructor 28
esttico 31
operador new 25
struct 46
covarianza 82
CreateInstance 108
D
DataRow 63
DefaultValue 109
delegados 55, 96
cadenas de 56
expresiones lambda 98
mtodos annimos 96
Delegate
Combine 57
Dispose 73
E
EndInvoke 58
ensamblados 4
Equals
ValueType 48
EventArgs 65
EventHandler 65
eventos 65
expresiones
rboles de 100
lambda 98
F
foreach 91
Form
Load 107, 112
Show 108
Func 100
G
genericidad 76
inferencia de tipos 80, 99
mtodos genricos 80
restricciones 78
tipos anidados 83
GetCallingAssembly 105
GetCustomAttributes 112
GetEntryAssembly 105
GetExecutingAssembly 105
GetHashCode 48
GetTypes
Assembly 106
GetValueOrDefault 88

IAsyncResult 58
IComponent 60
IDisposable 73
Idle 75
IEnumerable 92
IEnumerator 92
indexadores 62
inicializacin
campos 29
clases 28
struct 46
inlining 4
interfaces 35
implementacin explcita 36
implementacin implcita 35
internal 27, 48
InvokeMember 108
IsAssignableFrom 107
IsPublic
Type 106
Intuitive C
#

Copyright 2004-200S, Ian Marteens
116
116
IsSubclassOf 107
iteracin 91
iteradores 93
iteradores cerrados 96
L
Load
Assembly 105
Form 107, 112
LoadFrom 106, 107
M
mtodos 25
annimos 96
de acceso 61, 63
de extensin 39
estticos 26, 28, 31
genricos 80
parciales 69
virtuales 30
N
Nullable 86
GetValueOrDefault 88
HasValue 87
Value 87
O
ObsoleteAttribute 109
operador
de fusin 88
promovido 89
P
partial 67, 69
Path
Combine 107
private 27, 48
propiedades 61
automticas 64
indexadores 62
valor por omisin 109
protected 27
public 27, 48
R
restricciones
desnudas 81
8
Show 108
static 26
struct 43
constructores 46
T
ThreadException 75
tipos
anidados 83
anulables 85
de interfaz Ver interfaces
genricos 76
inferencia de 80, 99
ToString 9, 49
Type 103
Assembly 104
GetCustomAttributes 112
InvokeMember 108
IsAssignableFrom 107
IsPublic 106
IsSubclassOf 107
U
unboxing 51
using
instruccin 73
V
ValueType 47, 51
Equals 48
visibilidad 27
W
WaitOne 58





Intuitive C
#

Copyright 2004-2008, by Ian Marteens
Prohibida la reproduccin total o parcial de esta obra,
por cualquier medio, sin autorizacin escrita del autor.
Madrid, Espaa, 2008

You might also like