Sie sind auf Seite 1von 154

El lenguaje de programacin C# ndice El lenguaje de programacin C# ndice

TEMA 5: CLASES 56
INTRODUCCIN A LA OBRA 8
DEFINICIN DE CLASES 56
REQUISITOS PREVIOS RECOMENDADOS 8 CONCEPTOS DE CLASE Y OBJETO 56
ESTRUCTURA DE LA OBRA 8 SINTAXIS DE DEFINICIN DE CLASES 56
CONVENIOS DE NOTACIN 8 CREACIN DE OBJETOS 59
OPERADOR NEW 59
CONSTRUCTOR POR DEFECTO 61
TEMA 1: INTRODUCCIN A MICROSOFT.NET 10 REFERENCIA AL OBJETO ACTUAL CON THIS 61
HERENCIA Y MTODOS VIRTUALES 62
MICROSOFT.NET 10 CONCEPTO DE HERENCIA 62
COMMON LANGUAGE RUNTIME (CLR) 10 LLAMADAS POR DEFECTO AL CONSTRUCTOR BASE 64
MICROSOFT INTERMEDIATE LANGUAGE (MSIL) 13 MTODOS VIRTUALES 64
METADATOS 15 CLASES ABSTRACTAS 67
ENSAMBLADOS 16 LA CLASE PRIMEGENIA: SYSTEM.OBJECT 68
LIBRERA DE CLASE BASE (BCL) 19 POLIMORFISMO 71
COMMON TYPE SYSTEM (CTS) 20 CONCEPTO DE POLIMORFISMO 71
COMMON LANGUAGE SPECIFICATION (CLS) 20 MTODOS GENRICOS 72
DETERMINACIN DE TIPO. OPERADOR IS 73
TEMA 2: INTRODUCCIN A C# 22 ACCESO A LA CLASE BASE 73
DOWNCASTING 75
CLASES Y MTODOS SELLADOS 75
ORIGEN Y NECESIDAD DE UN NUEVO LENGUAJE 22
OCULTACIN DE MIEMBROS 76
CARACTERSTICAS DE C# 22
MIEMBROS DE TIPO 82
ESCRITURA DE APLICACIONES 27
ENCAPSULACIN 82
APLICACIN BSICA HOLA MUNDO! 27
PUNTOS DE ENTRADA 29
COMPILACIN EN LNEA DE COMANDOS 29 TEMA 6: ESPACIOS DE NOMBRES 87
COMPILACIN CON VISUAL STUDIO.NET 32
CONCEPTO DE ESPACIO DE NOMBRES 87
TEMA 3: EL PREPROCESADOR 36 DEFINICIN DE ESPACIOS DE NOMBRES 87
IMPORTACIN DE ESPACIOS DE NOMBRES 88
SENTENCIA USING 88
CONCEPTO DE PREPROCESADOR 36
ESPECIFICACIN DE ALIAS 90
DIRECTIVAS DE PREPROCESADO 36
ESPACIO DE NOMBRES DISTRIBUIDOS 91
CONCEPTO DE DIRECTIVA. SINTAXIS 36
DEFINICIN DE IDENTIFICADORES DE PREPROCESADO 37
ELIMINACIN DE IDENTIFICADORES DE PREPROCESADO 38 TEMA 7: VARIABLES Y TIPOS DE DATOS 93
COMPILACIN CONDICIONAL 38
GENERACIN DE AVISOS Y ERRORES 41 DEFINICIN DE VARIABLES 93
CAMBIOS EN LA NUMERACIN DE LNEAS 41 TIPOS DE DATOS BSICOS 94
MARCADO DE REGIONES DE CDIGO 42 TABLAS 96
TABLAS UNIDIMENSIONALES 96
TEMA 4: ASPECTOS LXICOS 44 TABLAS DENTADAS 98
TABLAS MULTIDIMENSIONALES 99
TABLAS MIXTAS 101
COMENTARIOS 44
COVARIANZA DE TABLAS 101
IDENTIFICADORES 45
LA CLASE SYSTEM.ARRAY 101
PALABRAS RESERVADAS 45
CADENAS DE TEXTO 102
LITERALES 47
CONSTANTES 107
OPERADORES 49
VARIABLES DE SLO LECTURA 108

Jos Antonio Gonzlez Seco Pgina 1 Jos Antonio Gonzlez Seco Pgina 2
El lenguaje de programacin C# ndice El lenguaje de programacin C# ndice

ORDEN DE INICIALIZACIN DE VARIABLES 109 CONCEPTO DE DELEGADO 143


DEFINICIN DE DELEGADOS 143
TEMA 8: MTODOS 111 MANIPULACIN DE OBJETOS DELEGADOS 145
LA CLASE SYSTEM.MULTICASTDELEGATE 148
LLAMADAS ASNCRONAS 149
CONCEPTO DE MTODO 111 IMPLEMENTACIN INTERNA DE LOS DELEGADOS 152
DEFINICIN DE MTODOS 111 EVENTOS 154
LLAMADA A MTODOS 112 CONCEPTO DE EVENTO 154
TIPOS DE PARMETROS. SINTAXIS DE DEFINICIN 112 SINTAXIS BSICA DE DEFINICIN DE EVENTOS 154
PARMETROS DE ENTRADA 113 SINTAXIS COMPLETA DE DEFINICIN DE EVENTOS 154
PARMETROS DE SALIDA 114
PARMETROS POR REFERENCIA 115
PARMETROS DE NMERO INDEFINIDO 115 TEMA 13: ESTRUCTURAS 157
SOBRECARGA DE TIPOS DE PARMETROS 116
MTODOS EXTERNOS 116 CONCEPTO DE ESTRUCTURA 157
CONSTRUCTORES 117 DIFERENCIAS ENTRE CLASES Y ESTRUCTURAS 157
CONCEPTO DE CONSTRUCTORES 117 BOXING Y UNBOXING 158
DEFINICIN DE CONSTRUCTORES 118 CONSTRUCTORES 160
LLAMADA AL CONSTRUCTOR 118
LLAMADAS ENTRE CONSTRUCTORES 118 TEMA 14: ENUMERACIONES 163
CONSTRUCTOR POR DEFECTO 120
LLAMADAS POLIMRFICAS EN CONSTRUCTORES 121
CONSTRUCTOR DE TIPO 122 CONCEPTO DE ENUMERACIN 163
DESTRUCTORES 123 DEFINICIN DE ENUMERACIONES 164
USO DE ENUMERACIONES 165
LA CLASE SYSTEM.ENUM 166
TEMA 9: PROPIEDADES 127 ENUMERACIONES DE FLAGS 168

CONCEPTO DE PROPIEDAD 127 TEMA 15: INTERFACES 171


DEFINICIN DE PROPIEDADES 127
ACCESO A PROPIEDADES 128
IMPLEMENTACIN INTERNA DE PROPIEDADES 129 CONCEPTO DE INTERFAZ 171
DEFINICIN DE INTERFACES 171
IMPLEMENTACIN DE INTERFACES 173
TEMA 10: INDIZADORES 130 ACCESO A MIEMBROS DE UNA INTERFAZ 176
ACCESO A MIEMBROS DE INTERFACES Y BOXING 178
CONCEPTO DE INDIZADOR 130
DEFINICIN DE INDIZADOR 130 TEMA 16: INSTRUCCIONES 180
ACCESO A INDIZADORES 131
IMPLEMENTACIN INTERNA DE INDIZADORES 132
CONCEPTO DE INSTRUCCIN 180
INSTRUCCIONES BSICAS 180
TEMA 11: REDEFINICIN DE OPERADORES 133 DEFINICIONES DE VARIABLES LOCALES 180
ASIGNACIONES 180
CONCEPTO DE REDEFINICIN DE OPERADOR 133 LLAMADAS A MTODOS 181
DEFINICIN DE REDEFINICIONES DE OPERADORES 134 INSTRUCCIN NULA 181
SINTAXIS GENERAL DE REDEFINICIN DE OPERADOR 134 INSTRUCCIONES CONDICIONALES 181
REDEFINICIN DE OPERADORES UNARIOS 136 INSTRUCCIN IF 181
REDEFINICIN DE OPERADORES BINARIOS 137 INSTRUCCIN SWITCH 182
REDEFINICIONES DE OPERADORES DE CONVERSIN 138 INSTRUCCIONES ITERATIVAS 184
INSTRUCCIN WHILE 184
TEMA 12: DELEGADOS Y EVENTOS 143 INSTRUCCIN DO...WHILE 185

Jos Antonio Gonzlez Seco Pgina 3 Jos Antonio Gonzlez Seco Pgina 4
El lenguaje de programacin C# ndice El lenguaje de programacin C# ndice

INSTRUCCIN FOR 185 OPERADOR SIZEOF. OBTENCIN DE TAMAO DE TIPO 225


INSTRUCCIN FOREACH 186 OPERADOR STACKALLOC. CREACIN DE TABLAS EN PILA. 226
INSTRUCCIONES DE EXCEPCIONES 190 FIJACIN DE VARIABLES APUNTADAS 227
CONCEPTO DE EXCEPCIN. 190
LA CLASE SYSTEM.EXCEPTION 191 TEMA 19: DOCUMENTACIN XML 230
EXCEPCIONES PREDEFINIDAS COMUNES 192
LANZAMIENTO DE EXCEPCIONES. INSTRUCCIN THROW 193
CAPTURA DE EXCEPCIONES. INSTRUCCIN TRY 193 CONCEPTO Y UTILIDAD DE LA DOCUMENTACIN XML 230
INSTRUCCIONES DE SALTO 198 INTRODUCCIN A XML 231
INSTRUCCIN BREAK 198 COMENTARIOS DE DOCUMENTACIN XML 232
INSTRUCCIN CONTINUE 199 SINTAXIS GENERAL 232
INSTRUCCIN RETURN 199 EL ATRIBUTO CREF 233
INSTRUCCIN GOTO 200 ETIQUETAS RECOMENDADAS PARA DOCUMENTACIN XML 235
INSTRUCCIN THROW 201 ETIQUETAS DE USO GENRICO 235
OTRAS INSTRUCCIONES 201 ETIQUETAS RELATIVAS A MTODOS 236
INSTRUCCIONES CHECKED Y UNCHECKED 201 ETIQUETAS RELATIVAS A PROPIEDADES 237
INSTRUCCIN LOCK 202 ETIQUETAS RELATIVAS A EXCEPCIONES 237
INSTRUCCIN USING 203 ETIQUETAS RELATIVAS A FORMATO 238
INSTRUCCIN FIXED 205 GENERACIN DE DOCUMENTACIN XML 240
GENERACIN A TRAVS DEL COMPILADOR EN LNEA DE COMANDOS 240
GENERACIN A TRAVS DE VISUAL STUDIO.NET 241
TEMA 17: ATRIBUTOS 206 ESTRUCTURA DE LA DOCUMENTACIN XML 242
SEPARACIN ENTRE DOCUMENTACIN XML Y CDIGO FUENTE 245
CONCEPTO DE ATRIBUTO 206
UTILIZACIN DE ATRIBUTOS 206 TEMA 20: EL COMPILADOR DE C# DE MICROSOFT 247
DEFINICIN DE NUEVOS ATRIBUTOS 208
ESPECIFICACIN DEL NOMBRE DEL ATRIBUTO 208
ESPECIFICACIN DEL USO DE UN ATRIBUTO 208 INTRODUCCIN 247
ESPECIFICACIN DE PARMETROS VLIDOS 210 SINTAXIS GENERAL DE USO DEL COMPILADOR 247
LECTURA DE ATRIBUTOS EN TIEMPO DE EJECUCIN 210 OPCIONES DE COMPILACIN 249
ATRIBUTOS DE COMPILACIN 214 OPCIONES BSICAS 249
ATRIBUTO SYSTEM.ATTRIBUTEUSAGE 214 MANIPULACIN DE RECURSOS 252
ATRIBUTO SYSTEM.OBSOLETE 214 CONFIGURACIN DE MENSAJES DE AVISOS Y ERRORES 253
ATRIBUTO SYSTEM.DIAGNOSTICS.CONDITIONAL 215 FICHEROS DE RESPUESTA 255
ATRIBUTO SYSTEM.CLSCOMPLIANT 216 OPCIONES DE DEPURACIN 257
PSEUDOATRIBUTOS 216 COMPILACIN INCREMENTAL 258
OPCIONES RELATIVAS AL LENGUAJE 259
OTRAS OPCIONES 260
TEMA 18: CDIGO INSEGURO 218 ACCESO AL COMPILADOR DESDE VISUAL STUDIO.NET 262

CONCEPTO DE CDIGO INSEGURO 218 TEMA 21: NOVEDADES DE C# 2.0 265


COMPILACIN DE CDIGOS INSEGUROS 218
MARCADO DE CDIGOS INSEGUROS 219
DEFINICIN DE PUNTEROS 220 INTRODUCCIN 265
MANIPULACIN DE PUNTEROS 221 GENRICOS 265
OBTENCIN DE DIRECCIN DE MEMORIA. OPERADOR & 221 CONCEPTO 265
ACCESO A CONTENIDO DE PUNTERO. OPERADOR * 222 UTILIDADES ERROR! MARCADOR NO DEFINIDO.
ACCESO A MIEMBRO DE CONTENIDO DE PUNTERO. OPERADOR -> 222 SINTAXIS 268
CONVERSIONES DE PUNTEROS 223 LIMITACIONES 269
ARITMTICA DE PUNTEROS 224 RESTRICCIONES 271
OPERADORES RELACIONADOS CON CDIGO INSEGURO 225 VALORES POR DEFECTO 276

Jos Antonio Gonzlez Seco Pgina 5 Jos Antonio Gonzlez Seco Pgina 6
El lenguaje de programacin C# ndice El lenguaje de programacin C# Introduccin a la obra

AMBIGEDADES 277
TIPOS PARCIALES 277 Introduccin a la obra
ITERADORES 279
MEJORAS EN LA MANIPULACIN DE DELEGADOS 282
INFERENCIA DE DELEGADOS 282
Requisitos previos recomendados
MTODOS ANNIMOS 283
COVARIANZA Y CONTRAVARIANZA DE DELEGADOS 286
TIPOS ANULABLES 287 En principio, para entender con facilidad esta obra es recomendable estar familiarizado
CONCEPTO 287 con los conceptos bsicos de programacin orientada a objetos, en particular con los
SINTAXIS 288 lenguajes de programacin C++ o Java, de los que C# deriva.
CONVERSIONES 289
OPERACIONES CON NULOS 290 Sin embargo, estos no son requisitos fundamentales para entenderla ya que cada vez que
OPERADOR DE FUSIN (??) 291 en ella se introduce algn elemento del lenguaje se definen y explican los conceptos
MODIFICADORES DE VISIBILIDAD DE BLOQUES GET Y SET 292 bsicos que permiten entenderlo. An as, sigue siendo recomendable disponer de los
CLASES ESTTICAS 292 requisitos antes mencionados para poder moverse con mayor soltura por el libro y
REFERENCIAS A ESPACIOS DE NOMBRES 293 aprovecharlo al mximo.
ALIAS GLOBAL Y CALIFICADOR :: 293
ALIAS EXTERNOS 295
SUPRESIN TEMPORAL DE AVISOS 296 Estructura de la obra
ATRIBUTOS CONDICIONALES 297
INCRUSTACIN DE TABLAS EN ESTRUCTURAS 297 Bsicamente el eje central de la obra es el lenguaje de programacin C#, del que no slo
MODIFICACIONES EN EL COMPILADOR 298 se describe su sintaxis sino que tambin se intenta explicar cules son las razones que
CONTROL DE LA VERSIN DEL LENGUAJE 298 justifican las decisiones tomadas en su diseo y cules son los errores ms difciles de
CONTROL DE LA PLATAFORMA DE DESTINO 299 detectar que pueden producirse al desarrollar de aplicaciones con l. Sin embargo, los
ENVO AUTOMTICO DE ERRORES A MICROSOFT 299 20 temas utilizados para ello pueden descomponerse en tres grandes bloques:
CONCRETIZACIN DE AVISOS A TRATAR COMO ERRORES 301
VISIBILIDAD DE LOS RECURSOS 302 Bloque 1: Introduccin a C# y .NET: Antes de empezar a describir el lenguaje
FIRMA DE ENSAMBLADOS 302 es obligatorio explicar el porqu de su existencia, y para ello es necesario antes
introducir la plataforma .NET de Microsoft con la que est muy ligado. Ese es el
DOCUMENTACIN DE REFERENCIA 304 objetivo de los temas 1 y 2, donde se explican las caractersticas y conceptos
bsicos de C# y .NET, las novedosas aportaciones de ambos y se introduce la
programacin y compilacin de aplicaciones en C# con el tpico Hola Mundo!
BIBLIOGRAFA 304
INFORMACIN EN INTERNET SOBRE C# 304
Bloque 2: Descripcin del lenguaje: Este bloque constituye el grueso de la
PORTALES 305
obra y est formado por los temas comprendidos entre el 3 y el 19. En ellos se
GRUPOS DE NOTICIAS Y LISTAS DE CORREO 305
describen pormenorizadamente los aspectos del lenguaje mostrando ejemplos de
su uso, explicando su porqu y avisando de cules son los problemas ms
difciles de detectar que pueden surgir al utilizarlos y cmo evitarlos.

Bloque 3: Descripcin del compilador: Este ltimo bloque, formado solamente


por el tema 20, describe cmo se utiliza el compilador de C# tanto desde la
ventana de consola como desde la herramienta Visual Studio.NET. Como al
describir el lenguaje, tambin se intenta dar una explicacin lo ms exhaustiva,
til y fcil de entender posible del significado, porqu y aplicabilidad de las
opciones de compilacin que ofrece.

Convenios de notacin

Jos Antonio Gonzlez Seco Pgina 7 Jos Antonio Gonzlez Seco Pgina 8
El lenguaje de programacin C# Introduccin a la obra El lenguaje de programacin C# Tema 1: Introduccin a Microsoft.NET

Para ayudar a resaltar la informacin clave se utilizan diferentes convenciones respecto


a los tipos de letra usados para representar cada tipo de contenido: TEMA 1: Introduccin a Microsoft.NET
El texto correspondiente a explicaciones se ha escrito usando la fuente Times
New Roman de 12 puntos de tamao, como es el caso de este prrafo. Microsoft.NET
Los fragmentos de cdigo fuente se han escrito usando la fuente Arial de 10
Microsoft.NET es el conjunto de nuevas tecnologas en las que Microsoft ha estado
puntos de tamao tal y como se muestra a continuacin:
trabajando durante los ltimos aos con el objetivo de obtener una plataforma sencilla y
class HolaMundo potente para distribuir el software en forma de servicios que puedan ser suministrados
{ remotamente y que puedan comunicarse y combinarse unos con otros de manera
static void Main() totalmente independiente de la plataforma, lenguaje de programacin y modelo de
{ componentes con los que hayan sido desarrollados. sta es la llamada plataforma
System.Console.WriteLine(Hola Mundo!);
} .NET, y a los servicios antes comentados se les denomina servicios Web.
}
Para crear aplicaciones para la plataforma .NET, tanto servicios Web como aplicaciones
Esta misma fuente es la que se usar desde las explicaciones cada vez que se tradicionales (aplicaciones de consola, aplicaciones de ventanas, servicios de Windows
haga referencia a algn elemento del cdigo fuente. Si adems dicho elemento es NT, etc.), Microsoft ha publicado el denominado kit de desarrollo de software conocido
una palabra reservada del lenguaje o viene predefinido en la librera de .NET, su como .NET Framework SDK, que incluye las herramientas necesarias tanto para su
nombre se escribir en negrita para as resaltar el carcter especial del mismo desarrollo como para su distribucin y ejecucin y Visual Studio.NET, que permite
hacer todo la anterior desde una interfaz visual basada en ventanas. Ambas herramientas
pueden descargarse gratuitamente desde http://www.msdn.microsoft.com/net, aunque la
Las referencias a textos de la interfaz del sistema operativo (nombres de
ltima slo est disponible para subscriptores MSDN Universal (los no subscriptores
ficheros y directorios, texto de la lnea de comandos, etc. ) se han escrito usando
pueden pedirlo desde dicha direccin y se les enviar gratis por correo ordinario)
la fuente Courier New de 10 puntos de tamao. Por ejemplo:
csc HolaMundo.cs El concepto de Microsoft.NET tambin incluye al conjunto de nuevas aplicaciones que
Microsoft y terceros han (o estn) desarrollando para ser utilizadas en la plataforma
Cuando adems este tipo de texto se utilice para hacer referencia a elementos .NET. Entre ellas podemos destacar aplicaciones desarrolladas por Microsoft tales como
predefinidos tales como extensiones de ficheros recomendadas o nombres de Windows.NET, Hailstorm, Visual Studio.NET, MSN.NET, Office.NET, y los nuevos
aplicaciones incluidas en el SDK, se escribir en negrita. servidores para empresas de Microsoft (SQL Server.NET, Exchange.NET, etc.)

Al describirse la sintaxis de definicin de los elementos del lenguaje se usar


fuente Arial de 10 puntos de tamao y se representarn en cursiva los elementos Common Language Runtime (CLR)
opcionales en la misma, en negrita los que deban escribirse tal cual, y sin
negrita y entre smbolos < y > los que representen de texto que deba colocarse El Common Language Runtime (CLR) es el ncleo de la plataforma .NET. Es el
en su lugar. Por ejemplo, cuando se dice que una clase ha de definirse as: motor encargado de gestionar la ejecucin de las aplicaciones para ella desarrolladas y a
las que ofrece numerosos servicios que simplifican su desarrollo y favorecen su
class <nombreClase>
fiabilidad y seguridad. Las principales caractersticas y servicios que ofrece el CLR son:
{
<miembros>
} Modelo de programacin consistente: A todos los servicios y facilidades
ofrecidos por el CLR se accede de la misma forma: a travs de un modelo de
programacin orientado a objetos. Esto es una diferencia importante respecto al
Lo que se est diciendo es que ha de escribirse la palabra reservada class, modo de acceso a los servicios ofrecidos por los algunos sistemas operativos
seguida de texto que represente el nombre de la clase a definir, seguido de una actuales (por ejemplo, los de la familia Windows), en los que a algunos servicios
llave de apertura ({), seguido opcionalmente de texto que se corresponda con se les accede a travs de llamadas a funciones globales definidas en DLLs y a
definiciones de miembros y seguido de una llave de cierre (}) otros a travs de objetos (objetos COM en el caso de la familia Windows)

Si lo que se define es la sintaxis de llamada a alguna aplicacin concreta, Modelo de programacin sencillo: Con el CLR desaparecen muchos elementos
entonces la notacin que se usar es similar a la anterior slo que en vez de complejos incluidos en los sistemas operativos actuales (registro de Windows,
fuente Arial se utilizar fuente Courier New de 10 puntos de tamao. GUIDs, HRESULTS, IUnknown, etc.) El CLR no es que abstraiga al

Jos Antonio Gonzlez Seco Pgina 9 Jos Antonio Gonzlez Seco Pgina 10
El lenguaje de programacin C# Tema 1: Introduccin a Microsoft.NET El lenguaje de programacin C# Tema 1: Introduccin a Microsoft.NET

programador de estos conceptos, sino que son conceptos que no existen en la Seguridad de tipos: El CLR facilita la deteccin de errores de programacin
plataforma .NET difciles de localizar comprobando que toda conversin de tipos que se realice
durante la ejecucin de una aplicacin .NET se haga de modo que los tipos
Eliminacin del infierno de las DLLs: En la plataforma .NET desaparece el origen y destino sean compatibles.
problema conocido como infierno de las DLLs que se da en los sistemas
operativos actuales de la familia Windows, problema que consiste en que al Aislamiento de procesos: El CLR asegura que desde cdigo perteneciente a un
sustituirse versiones viejas de DLLs compartidas por versiones nuevas puede determinado proceso no se pueda acceder a cdigo o datos pertenecientes a otro,
que aplicaciones que fueron diseadas para ser ejecutadas usando las viejas lo que evita errores de programacin muy frecuentes e impide que unos procesos
dejen de funcionar si las nuevas no son 100% compatibles con las anteriores. En puedan atacar a otros. Esto se consigue gracias al sistema de seguridad de tipos
la plataforma .NET las versiones nuevas de las DLLs pueden coexistir con las antes comentado, pues evita que se pueda convertir un objeto a un tipo de
viejas, de modo que las aplicaciones diseadas para ejecutarse usando las viejas mayor tamao que el suyo propio, ya que al tratarlo como un objeto de mayor
podrn seguir usndolas tras instalacin de las nuevas. Esto, obviamente, tamao podra accederse a espacios en memoria ajenos a l que podran
simplifica mucho la instalacin y desinstalacin de software. pertenecer a otro proceso. Tambin se consigue gracias a que no se permite
acceder a posiciones arbitrarias de memoria.
Ejecucin multiplataforma: El CLR acta como una mquina virtual,
encargndose de ejecutar las aplicaciones diseadas para la plataforma .NET. Es Tratamiento de excepciones: En el CLR todo los errores que se puedan
decir, cualquier plataforma para la que exista una versin del CLR podr producir durante la ejecucin de una aplicacin se propagan de igual manera:
ejecutar cualquier aplicacin .NET. Microsoft ha desarrollado versiones del mediante excepciones. Esto es muy diferente a como se vena haciendo en los
CLR para la mayora de las versiones de Windows: Windows 95, Windows 98, sistemas Windows hasta la aparicin de la plataforma .NET, donde ciertos
Windows ME, Windows NT 4.0, Windows 2000, Windows XP y Windows CE errores se transmitan mediante cdigos de error en formato Win32, otros
(que puede ser usado en CPUs que no sean de la familia x86) Por otro lado mediante HRESULTs y otros mediante excepciones.
Microsoft ha firmado un acuerdo con Corel para portar el CLR a Linux y
tambin hay terceros que estn desarrollando de manera independiente versiones El CLR permite que excepciones lanzadas desde cdigo para .NET escrito en un
de libre distribucin del CLR para Linux. Asimismo, dado que la arquitectura cierto lenguaje se puedan capturar en cdigo escrito usando otro lenguaje, e
del CLR est totalmente abierta, es posible que en el futuro se diseen versiones incluye mecanismos de depuracin que pueden saltar desde cdigo escrito para
del mismo para otros sistemas operativos. .NET en un determinado lenguaje a cdigo escrito en cualquier otro. Por
ejemplo, se puede recorrer la pila de llamadas de una excepcin aunque sta
Integracin de lenguajes: Desde cualquier lenguaje para el que exista un incluya mtodos definidos en otros mdulos usando otros lenguajes.
compilador que genere cdigo para la plataforma .NET es posible utilizar cdigo
generado para la misma usando cualquier otro lenguaje tal y como si de cdigo Soporte multihilo: El CLR es capaz de trabajar con aplicaciones divididas en
escrito usando el primero se tratase. Microsoft ha desarrollado un compilador de mltiples hilos de ejecucin que pueden ir evolucionando por separado en
C# que genera cdigo de este tipo, as como versiones de sus compiladores de paralelo o intercalndose, segn el nmero de procesadores de la mquina sobre
Visual Basic (Visual Basic.NET) y C++ (C++ con extensiones gestionadas) que la que se ejecuten. Las aplicaciones pueden lanzar nuevos hilos, destruirlos,
tambin lo generan y una versin del intrprete de JScript (JScript.NET) que suspenderlos por un tiempo o hasta que les llegue una notificacin, enviarles
puede interpretarlo. La integracin de lenguajes es tal que es posible escribir una notificaciones, sincronizarlos, etc.
clase en C# que herede de otra escrita en Visual Basic.NET que, a su vez, herede
de otra escrita en C++ con extensiones gestionadas. Distribucin transparente: El CLR ofrece la infraestructura necesaria para
crear objetos remotos y acceder a ellos de manera completamente transparente a
Gestin de memoria: El CLR incluye un recolector de basura que evita que el su localizacin real, tal y como si se encontrasen en la mquina que los utiliza.
programador tenga que tener en cuenta cundo ha de destruir los objetos que
dejen de serle tiles. Este recolector es una aplicacin que se activa cuando se Seguridad avanzada: El CLR proporciona mecanismos para restringir la
quiere crear algn objeto nuevo y se detecta que no queda memoria libre para ejecucin de ciertos cdigos o los permisos asignados a los mismos segn su
hacerlo, caso en que el recolector recorre la memoria dinmica asociada a la procedendecia o el usuario que los ejecute. Es decir, puede no darse el mismo
aplicacin, detecta qu objetos hay en ella que no puedan ser accedidos por el nivel de confianza a cdigo procedente de Internet que a cdigo instalado
cdigo de la aplicacin, y los elimina para limpiar la memoria de objetos localmente o procedente de una red local; puede no darse los mismos permisos a
basura y permitir la creacin de otros nuevos. Gracias a este recolector se cdigo procedente de un determinado fabricante que a cdigo de otro; y puede
evitan errores de programacin muy comunes como intentos de borrado de no darse los mismos permisos a un mismo cdigos segn el usuario que lo est
objetos ya borrados, agotamiento de memoria por olvido de eliminacin de ejecutando o segn el rol que ste desempee. Esto permite asegurar al
objetos intiles o solicitud de acceso a miembros de objetos ya destruidos. administrador de un sistema que el cdigo que se est ejecutando no pueda
poner en peligro la integridad de sus archivos, la del registro de Windows, etc.

Jos Antonio Gonzlez Seco Pgina 11 Jos Antonio Gonzlez Seco Pgina 12
El lenguaje de programacin C# Tema 1: Introduccin a Microsoft.NET El lenguaje de programacin C# Tema 1: Introduccin a Microsoft.NET

Interoperabilidad con cdigo antiguo: El CLR incorpora los mecanismos jitter normal: Es el que se suele usar por defecto, y slo compila el cdigo
necesarios para poder acceder desde cdigo escrito para la plataforma .NET a MSIL a cdigo nativo a medida que va siendo necesario, pues as se ahorra
cdigo escrito previamente a la aparicin de la misma y, por tanto, no preparado tiempo y memoria al evitarse tener que compilar innecesariamente cdigo que
para ser ejecutando dentro de ella. Estos mecanismos permiten tanto el acceso a nunca se ejecute. Para conseguir esto, el cargador de clases del CLR sustituye
objetos COM como el acceso a funciones sueltas de DLLs preexistentes (como inicialmente las llamadas a mtodos de las nuevas clases que vaya cargando por
la API Win32) llamadas a funciones auxiliares (stubs) que se encarguen de compilar el
verdadero cdigo del mtodo. Una vez compilado, la llamada al stub es
Como se puede deducir de las caractersticas comentadas, el CLR lo que hace es sustituida por una llamada directa al cdigo ya compilado, con lo que posteriores
gestionar la ejecucin de las aplicaciones diseadas para la plataforma .NET. Por esta llamadas al mismo no necesitarn compilacin.
razn, al cdigo de estas aplicaciones se le suele llamar cdigo gestionado, y al cdigo
no escrito para ser ejecutado directamente en la plataforma .NET se le suele llamar jitter econmico: Funciona de forma similar al jitter normal solo que no realiza
cdigo no gestionado. ninguna optimizacin de cdigo al compilar sino que traduce cada instruccin
MSIL por su equivalente en el cdigo mquina sobre la que se ejecute. Esta
especialmente pensado para ser usado en dispositivos empotrados que dispongan
Microsoft Intermediate Language (MSIL) de poca potencia de CPU y poca memoria, pues aunque genere cdigo ms
ineficiente es menor el tiempo y memoria que necesita para compilar. Es ms,
Ninguno de los compiladores que generan cdigo para la plataforma .NET produce para ahorrar memoria este jitter puede descargar cdigo ya compilado que lleve
cdigo mquina para CPUs x86 ni para ningn otro tipo de CPU concreta, sino que cierto tiempo sin ejecutarse y sustituirlo de nuevo por el stub apropiado. Por
generan cdigo escrito en el lenguaje intermedio conocido como Microsoft estas razones, este es el jitter usado por defecto en Windows CE, sistema
Intermediate Lenguage (MSIL) El CLR da a las aplicaciones la sensacin de que se operativo que se suele incluir en los dispositivos empotrados antes mencionados.
estn ejecutando sobre una mquina virtual, y precisamente MSIL es el cdigo mquina
de esa mquina virtual. Es decir, MSIL es el nico cdigo que es capaz de interpretar el Otra utilidad del jitter econmico es que facilita la adaptacin de la plataforma
CLR, y por tanto cuando se dice que un compilador genera cdigo para la plataforma .NET a nuevos sistemas porque es mucho ms sencillo de implementar que el
.NET lo que se est diciendo es que genera MSIL. normal. De este modo, gracias a l es posible desarrollar rpidamente una
versin del CLR que pueda ejecutar aplicaciones gestionadas aunque sea de una
MSIL ha sido creado por Microsoft tras consultar a numerosos especialistas en la forma poco eficiente, y una vez desarrollada es posible centrarse en desarrollar
escritura de compiladores y lenguajes tanto del mundo acadmico como empresarial. Es el jitter normal para optimizar la ejecucin de las mismas.
un lenguaje de un nivel de abstraccin mucho ms alto que el de la mayora de los
cdigos mquina de las CPUs existentes, e incluye instrucciones que permiten trabajar prejitter: Se distribuye como una aplicacin en lnea de comandos llamada
directamente con objetos (crearlos, destruirlos, inicializarlos, llamar a mtodos ngen.exe mediante la que es posible compilar completamente cualquier
virtuales, etc.), tablas y excepciones (lanzarlas, capturarlas y tratarlas) ejecutable o librera (cualquier ensamblado en general, aunque este concepto se
ver ms adelante) que contenga cdigo gestionado y convertirlo a cdigo
Ya se coment que el compilador de C# compila directamente el cdigo fuente a MSIL, nativo, de modo que posteriores ejecuciones del mismo se harn usando esta
que Microsoft ha desarrollado nuevas versiones de sus lenguajes Visual Basic (Visual versin ya compilada y no se perder tiempo en hacer la compilacin dinmica.
Basic.NET) y C++ (C++ con extensiones gestionadas) cuyos compiladores generan
MSIL, y que ha desarrollado un intrprete de JScript (JScript.NET) que genera cdigo La actuacin de un jitter durante la ejecucin de una aplicacin gestionada puede dar la
MSIL. Pues bien, tambin hay numerosos terceros que han anunciado estar realizando sensacin de hacer que sta se ejecute ms lentamente debido a que ha de invertirse
versiones para la plataforma .NET de otros lenguajes como APL, CAML, Cobol, Eiffel, tiempo en las compilaciones dinmicas. Esto es cierto, pero hay que tener en cuenta que
Fortran, Haskell, Java (J#), Mercury, ML, Mondrian, Oberon, Oz, Pascal, Perl, Python, es una solucin mucho ms eficiente que la usada en otras plataformas como Java, ya
RPG, Scheme y Smalltalk. que en .NET cada cdigo no es interpretado cada vez que se ejecuta sino que slo es
compilado la primera vez que se llama al mtodo al que pertenece. Es ms, el hecho de
La principal ventaja del MSIL es que facilita la ejecucin multiplataforma y la que la compilacin se realice dinmicamente permite que el jitter tenga acceso a mucha
integracin entre lenguajes al ser independiente de la CPU y proporcionar un formato ms informacin sobre la mquina en que se ejecutar la aplicacin del que tendra
comn para el cdigo mquina generado por todos los compiladores que generen cdigo cualquier compilador tradicional, con lo que puede optimizar el cdigo para ella
para .NET. Sin embargo, dado que las CPUs no pueden ejecutar directamente MSIL, generado (por ejemplo, usando las instrucciones especiales del Pentium III si la
antes de ejecutarlo habr que convertirlo al cdigo nativo de la CPU sobre la que se mquina las admite, usando registros extra, incluyendo cdigo inline, etc.) Adems,
vaya a ejecutar. De esto se encarga un componente del CLR conocido como compilador como el recolector de basura de .NET mantiene siempre compactada la memoria
JIT (Just-In-Time) o jitter que va convirtiendo dinmicamente el cdigo MSIL a dinmica las reservas de memoria se harn ms rpido, sobre todo en aplicaciones que
ejecutar en cdigo nativo segn sea necesario. Este jitter se distribuye en tres versiones: no agoten la memoria y, por tanto, no necesiten de una recoleccin de basura. Por estas

Jos Antonio Gonzlez Seco Pgina 13 Jos Antonio Gonzlez Seco Pgina 14
El lenguaje de programacin C# Tema 1: Introduccin a Microsoft.NET El lenguaje de programacin C# Tema 1: Introduccin a Microsoft.NET

razones, los ingenieros de Microsoft piensan que futuras versiones de sus jitters podrn cada uno se almacena informacin sobre cul es su nombre, tipo y
incluso conseguir que el cdigo gestionado se ejecute ms rpido que el no gestionado. modificadores.
PropertyDef Define las caractersticas de las propiedades definidas en el mdulo.
De cada una se indica su nombre, tipo, modificadores y referencias a
Metadatos los elementos de la tabla MethodDef correspondientes a sus mtodos
set/get.
En la plataforma .NET se distinguen dos tipos de mdulos de cdigo compilado: EventDef Define las caractersticas de los eventos definidos en el mdulo. De
ejecutables (extensin .exe) y libreras de enlace dinmico (extensin .dll cada uno se indica su nombre, tipo, modificadores. y referencias a los
generalmente) Ambos son ficheros que contienen definiciones de tipos de datos, y la elementos de la tabla MethodDef correspondientes a sus mtodos
diferencia entre ellos es que slo los primeros disponen de un mtodo especial que sirve add/remove.
de punto de entrada a partir del que es posible ejecutar el cdigo que contienen haciendo AssemblyRef Indica cules son los ensamblados externos a los que se referencia en
una llamada desde la lnea de comandos del sistema operativo. A ambos tipos de el mdulo. De cada uno se indica cul es su nombre de fichero (sin
mdulos se les suele llamar ejecutables portables (PE), ya que su cdigo puede extensin), versin, idioma y marca de clave pblica.
ejecutarse en cualquiera de los diferentes sistemas operativos de la familia Windows ModuleRef Indica cules son los otros mdulos del mismo ensamblado a los que
para los que existe alguna versin del CLR. referencia el mdulo. De cada uno se indica cul es su nombre de
fichero.
El contenido de un mdulo no es slo MSIL, sino que tambin consta de otras dos reas TypeRef Indica cules son los tipos externos a los que se referencia en el
muy importantes: la cabecera de CLR y los metadatos: mdulo. De cada uno se indica cul es su nombre y, segn donde
estn definidos, una referencia a la posicin adecuada en la tabla
La cabecera de CLR es un pequeo bloque de informacin que indica que se trata AssemblyRef o en la tabla ModuleRef.
de un mdulo gestionado e indica es la versin del CLR que necesita, cul es su MemberRef Indican cules son los miembros definidos externamente a los que se
firma digital, cul es su punto de entrada (si es un ejecutable), etc. referencia en el mdulo. Estos miembros pueden ser campos, mtodos,
propiedades o eventos; y de cada uno de ellos se almacena
Los metadatos son un conjunto de datos organizados en forma de tablas que informacin sobre su nombre y signatura, as como una referencia a la
almacenan informacin sobre los tipos definidos en el mdulo, los miembros de posicin de la tabla TypeRef donde se almacena informacin relativa
stos y sobre cules son los tipos externos al mdulo a los que se les referencia en el al tipo del que es miembro.
mdulo. Los metadatos de cada modulo los genera automticamente el compilador Tabla 1: Principales tablas de metadatos
al crearlo, y entre sus tablas se incluyen1:

Tabla Descripcin Ntese que el significado de los metadatos es similar al de otras tecnologas previas a la
plataforma .NET como lo son los ficheros IDL. Sin embargo, los metadatos tienen dos
ModuleDef Define las caractersticas del mdulo. Consta de un nico elemento
ventajas importantes sobre stas: contiene ms informacin y siempre se almacenan
que almacena un identificador de versin de mdulo (GUID creado
incrustados en el mdulo al que describen, haciendo imposible la separacin entre
por el compilador) y el nombre de fichero que se dio al mdulo al
ambos. Adems, como se ver ms adelante, es posible tanto consultar los metadatos de
compilarlo (as este nombre siempre estar disponible, aunque se
cualquier mdulo a travs de las clases del espacio de nombres System.Reflection de la
renombre el fichero)
BCL como aadirles informacin adicional mediante atributos (se ver ms adelante)
TypeDef Define las caractersticas de los tipos definidos en el mdulo. De cada
tipo se almacena su nombre, su tipo padre, sus modificadores de
acceso y referencias a los elementos de las tablas de miembros
correspondientes a sus miembros.
Ensamblados
MethodDef Define las caractersticas de los mtodos definidos en el mdulo. De
cada mtodo se guarda su nombre, signatura (por cada parmetro se Un ensamblado es una agrupacin lgica de uno o ms mdulos o ficheros de recursos
incluye una referencia al elemento apropiado en la tabla ParamDef), (ficheros .GIF, .HTML, etc.) que se engloban bajo un nombre comn. Un programa
modificadores y posicin del mdulo donde comienza el cdigo MSIL puede acceder a informacin o cdigo almacenados en un ensamblado sin tener que
de su cuerpo. conocer cul es el fichero en concreto donde se encuentran, por lo que los ensamblados
ParamDef Define las caractersticas de los parmetros definidos en el mdulo. De nos permiten abstraernos de la ubicacin fsica del cdigo que ejecutemos o de los
cada parmetro se guarda su nombre y modificadores. recursos que usemos. Por ejemplo, podemos incluir todos los tipos de una aplicacin en
FieldDef Define las caractersticas de los campos definidos en el mdulo. De un mismo ensamblado pero colocando los ms frecuentemente usados en un cierto
mdulo y los menos usados en otro, de modo que slo se descarguen de Internet los
1
ltimos si es que se van a usar.
No se preocupe si no entiende an algunos de los conceptos nuevos introducido en las descripciones de
las tablas de metadatos, pues ms adelante se irn explicando detalladamente.

Jos Antonio Gonzlez Seco Pgina 15 Jos Antonio Gonzlez Seco Pgina 16
El lenguaje de programacin C# Tema 1: Introduccin a Microsoft.NET El lenguaje de programacin C# Tema 1: Introduccin a Microsoft.NET

Todo ensamblado contiene un manifiesto, que son metadatos con informacin sobre las
caractersticas del ensamblado. Este manifiesto puede almacenarse en cualquiera de los Para asegurar que no se haya alterado la informacin de ningn ensamblado se usa el
mdulos que formen el ensamblado o en uno especficamente creado para ello, siendo lo criptosistema de clave pblica RSA. Lo que se hace es calcular el cdigo de dispersin
ltimo necesario cuando slo contiene recursos (ensamblado satlite) SHA-1 del mdulo que contenga el manifiesto e incluir tanto este valor cifrado con
RSA (firma digital) como la clave pblica necesaria para descifrarlo en algn lugar del
Las principales tablas incluidas en los manifiestos son las siguientes: mdulo que se indicar en la cabecera de CLR. Cada vez que se vaya a cargar en
memoria el ensamblado se calcular su valor de dispersin de nuevo y se comprobar
Tabla Descripcin que es igual al resultado de descifrar el original usando su clave pblica. Si no fuese as
AssemblyDef Define las caractersticas del ensamblado. Consta de un nico se detectara que se ha adulterado su contenido.
elemento que almacena el nombre del ensamblado sin
extensin, versin, idioma, clave pblica y tipo de algoritmo Para asegurar tambin que los contenidos del resto de ficheros que formen un
de dispersin usado para hallar los valores de dispersin de la ensamblado no hayan sido alterados lo que se hace es calcular el cdigo de dispersin
tabla FileDef. de stos antes de cifrar el ensamblado y guardarlo en el elemento correspondiente a cada
FileDef Define cules son los archivos que forman el ensamblado. De fichero en la tabla FileDef del manifiesto. El algoritmo de cifrado usado por defecto es
cada uno se da su nombre y valor de dispersin. Ntese que SHA-1, aunque en este caso tambin se da la posibilidad de usar MD5. En ambos casos,
slo el mdulo que contiene el manifiesto sabr qu ficheros cada vez que se accede al fichero para acceder a un tipo o recurso se calcular de nuevo
que forman el ensamblado, pero el resto de ficheros del mismo su valor de dispersin y se comprobar que coincida con el almacenado en FileDef.
no sabrn si pertenecen o no a un ensamblado (no contienen
metadatos que les indique si pertenecen a un ensamblado) Dado que las claves pblicas son valores que ocupan muchos bytes (2048 bits), lo que
ManifestResourceDef Define las caractersticas de los recursos incluidos en el se hace para evitar que los metadatos sean excesivamente grandes es no incluir en las
mdulo. De cada uno se indica su nombre y modificadores de referencias a ensamblados externos de la tabla AssemblyRef las claves pblicas de
acceso. Si es un recurso incrustado se indica dnde empieza dichos ensamblados, sino slo los 64 ltimos bits resultantes de aplicar un algoritmo de
dentro del PE que lo contiene, y si es un fichero independiente dispersin a dichas claves. A este valor recortado se le llama marca de clave pblica.
se indica cul es el elemento de la tabla FileDef
correspondiente a dicho fichero. Hay dos tipos de ensamblados: ensamblados privados y ensamblados compartidos.
ExportedTypesDef Indica cules son los tipos definidos en el ensamblado y Los privados se almacenan en el mismo directorio que la aplicacin que los usa y slo
accesibles desde fuera del mismo. Para ahorrar espacio slo puede usarlos sta, mientras que los compartidos se almacenan en un cach de
recogen los que no pertenezcan al mdulo donde se incluye el ensamblado global (GAC) y pueden usarlos cualquiera que haya sido compilada
manifiesto, y de cada uno se indica su nombre, la posicin en referencindolos.
la tabla FileDef del fichero donde se ha implementado y la
posicin en la tabla TypeDef correspondiente a su definicin. Los compartidos han de cifrase con RSA ya que lo que los identifica es en el GAC es
AssemblyProccesorDef Indica en qu procesadores se puede ejecutar el ensamblado, lo su nombre (sin extensin) ms su clave pblica, lo que permite que en el GAC puedan
que puede ser til saberlo si el ensamblado contiene mdulos instalarse varios ensamblados con el mismo nombre y diferentes claves pblicas. Es
con cdigo nativo (podra hacerse usando C++ con decir, es como si la clave pblica formase parte del nombre del ensamblado, razn por
extensiones gestionadas) Suele estar vaca, lo que indica que se la que a los ensamblados as cifrados se les llama ensamblados de nombre fuerte. Esta
puede ejecutar en cualquier procesador; pero si estuviese llena, poltica permite resolver los conflictos derivados de que se intente instalar en un mismo
cada elemento indicara un tipo de procesador admitido segn equipo varios ensamblados compartidos con el mismo nombre pero procedentes de
el formato de identificadores de procesador del fichero distintas empresas, pues stas tendrn distintas claves pblicas.
WinNT.h incluido con Visual Studio.NET (por ejemplo, 586 =
Pentium, 2200 = Arquitectura IA64, etc.) Tambin para evitar problemas, en el GAC se pueden mantener mltiples versiones de
AssemblyOSDef Indica bajo qu sistemas operativos se puede ejecutar el un mismo ensamblado. As, si una aplicacin fue compilada usando una cierta versin
ensamblado, lo que puede ser til si contiene mdulos con de un determinado ensamblado compartido, cuando se ejecute slo podr hacer uso de
tipos o mtodos disponibles slo en ciertos sistemas. Suele esa versin del ensamblado y no de alguna otra ms moderna que se hubiese instalado
estar vaca, lo que indica que se puede ejecutar en cualquier en el GAC. De esta forma se soluciona el problema del infierno de las DLL comentado
procesador; pero si estuviese llena, indicara el identificador de al principio del tema.
cada uno de los sistemas admitidos siguiendo el formato del
WinNT.h de Visual Studio.NET (por ejemplo, 0 = familia En realidad es posible modificar tanto las polticas de bsqueda de ensamblados (por
Windows 9X, 1 = familia Windows NT, etc.) y el nmero de la ejemplo, para buscar ensamblados privados fuera del directorio de la aplicacin) como
versin del mismo a partir de la que se admite. la poltica de aceptacin de ensamblados compartidos (por ejemplo, para que se haga
Tabla 2: Principales tablas de un manifiesto automticamente uso de las nuevas versiones que se instalen de DLLs compartidas)

Jos Antonio Gonzlez Seco Pgina 17 Jos Antonio Gonzlez Seco Pgina 18
El lenguaje de programacin C# Tema 1: Introduccin a Microsoft.NET El lenguaje de programacin C# Tema 1: Introduccin a Microsoft.NET

incluyendo en el directorio de instalacin de la aplicacin un fichero de configuracin Common Type System (CTS)
en formato XML con las nuevas reglas para las mismas. Este fichero ha de llamarse
igual que el ejecutable de la aplicacin pero ha de tener extensin .cfg. El Common Type System (CTS) o Sistema de Tipo Comn es el conjunto de reglas
que han de seguir las definiciones de tipos de datos para que el CLR las acepte. Es
decir, aunque cada lenguaje gestionado disponga de su propia sintaxis para definir tipos
Librera de clase base (BCL) de datos, en el MSIL resultante de la compilacin de sus cdigos fuente se han de
cumplir las reglas del CTS. Algunos ejemplos de estas reglas son:
La Librera de Clase Base (BCL) es una librera incluida en el .NET Framework
formada por cientos de tipos de datos que permiten acceder a los servicios ofrecidos por Cada tipo de dato puede constar de cero o ms miembros. Cada uno de estos
el CLR y a las funcionalidades ms frecuentemente usadas a la hora de escribir miembros puede ser un campo, un mtodo, una propiedad o un evento.
programas. Adems, a partir de estas clases prefabricadas el programador puede crear
nuevas clases que mediante herencia extiendan su funcionalidad y se integren a la No puede haber herencia mltiple, y todo tipo de dato ha de heredar directa o
perfeccin con el resto de clases de la BCL. Por ejemplo, implementando ciertos indirectamente de System.Object.
interfaces podemos crear nuevos tipos de colecciones que sern tratadas exactamente
igual que cualquiera de las colecciones incluidas en la BCL. Los modificadores de acceso admitidos son:
Esta librera est escrita en MSIL, por lo que puede usarse desde cualquier lenguaje Modificador Cdigo desde el que es accesible el miembro
cuyo compilador genere MSIL. A travs de las clases suministradas en ella es posible public Cualquier cdigo
desarrollar cualquier tipo de aplicacin, desde las tradicionales aplicaciones de private Cdigo del mismo tipo de dato
ventanas, consola o servicio de Windows NT hasta los novedosos servicios Web y family Cdigo del mismo tipo de dato o de hijos de ste.
pginas ASP.NET. Es tal la riqueza de servicios que ofrece que incluso es posible crear assembly Cdigo del mismo ensamblado
lenguajes que carezcan de librera de clases propia y slo se basen en la BCL -como C#. family and Cdigo del mismo tipo o de hijos de ste ubicado en
assembly el mismo ensamblado
Dada la amplitud de la BCL, ha sido necesario organizar las clases en ella incluida en
family or Cdigo del mismo tipo o de hijos de ste, o cdigo
espacios de nombres que agrupen clases con funcionalidades similares. Por ejemplo, assembly
los espacios de nombres ms usados son: ubicado en el mismo ensamblado
Tabla 4: Modificadores de acceso a miembros admitidos por el CTS
Espacio de nombres Utilidad de los tipos de datos que contiene
System Tipos muy frecuentemente usados, como los los tipos
bsicos, tablas, excepciones, fechas, nmeros aleatorios, Common Language Specification (CLS)
recolector de basura, entrada/salida en consola, etc.
System.Collections Colecciones de datos de uso comn como pilas, colas,
El Common Language Specification (CLS) o Especificacin del Lenguaje Comn es
listas, diccionarios, etc.
un conjunto de reglas que han de seguir las definiciones de tipos que se hagan usando
System.Data Manipulacin de bases de datos. Forman la denominada un determinado lenguaje gestionado si se desea que sean accesibles desde cualquier otro
arquitectura ADO.NET. lenguaje gestionado. Obviamente, slo es necesario seguir estas reglas en las
System.IO Manipulacin de ficheros y otros flujos de datos. definiciones de tipos y miembros que sean accesibles externamente, y no la en las de
System.Net Realizacin de comunicaciones en red. los privados. Adems, si no importa la interoperabilidad entre lenguajes tampoco es
System.Reflection Acceso a los metadatos que acompaan a los mdulos de necesario seguirlas. A continuacin se listan algunas de reglas significativas del CLS:
cdigo.
System.Runtime.Remoting Acceso a objetos remotos. Los tipos de datos bsicos admitidos son bool, char, byte, short, int, long, float,
System.Security Acceso a la poltica de seguridad en que se basa el CLR. double, string y object Ntese pues que no todos los lenguajes tienen porqu
System.Threading Manipulacin de hilos. admitir los tipos bsicos enteros sin signo o el tipo decimal como lo hace C#.
System.Web.UI.WebControls Creacin de interfaces de usuario basadas en ventanas
para aplicaciones Web. Las tablas han de tener una o ms dimensiones, y el nmero de dimensiones de
System.Windows.Forms Creacin de interfaces de usuario basadas en ventanas cada tabla ha de ser fijo. Adems, han de indexarse empezando a contar desde 0.
para aplicaciones estndar.
System.XML Acceso a datos en formato XML. Se pueden definir tipos abstractos y tipos sellados. Los tipos sellados no pueden
Tabla 3: Espacios de nombres de la BCL ms usados tener miembros abstractos.

Jos Antonio Gonzlez Seco Pgina 19 Jos Antonio Gonzlez Seco Pgina 20
El lenguaje de programacin C# Tema 1: Introduccin a Microsoft.NET El lenguaje de programacin C# Tema 2: Introduccin a C#

Las excepciones han de derivar de System.Exception, los delegados de


System.Delegate, las enumeraciones de System.Enum, y los tipos por valor que Tema 2: Introduccin a C#
no sean enumeraciones de System.ValueType.

Los mtodos de acceso a propiedades en que se traduzcan las definiciones Origen y necesidad de un nuevo lenguaje
get/set de stas han de llamarse de la forma get_X y set_X respectivamente,
donde X es el nombre de la propiedad; los de acceso a indizadores han de
C# (ledo en ingls C Sharp y en espaol C Almohadilla) es el nuevo lenguaje de
traducirse en mtodos get_Item y set_Item; y en el caso de los eventos, sus
propsito general diseado por Microsoft para su plataforma .NET. Sus principales
definiciones add/remove han de traducirse en mtodos add_X y remove_X.
creadores son Scott Wiltamuth y Anders Hejlsberg, ste ltimo tambin conocido por
haber sido el diseador del lenguaje Turbo Pascal y la herramienta RAD Delphi.
En las definiciones de atributos slo pueden usarse enumeraciones o datos de los
siguientes tipos: System.Type, string, char, bool, byte, short, int, long, float,
Aunque es posible escribir cdigo para la plataforma .NET en muchos otros lenguajes,
double y object.
C# es el nico que ha sido diseado especficamente para ser utilizado en ella, por lo
que programarla usando C# es mucho ms sencillo e intuitivo que hacerlo con
En un mismo mbito no se pueden definir varios identificadores cuyos nombres cualquiera de los otros lenguajes ya que C# carece de elementos heredados innecesarios
slo difieran en la capitalizacin usada. De este modo se evitan problemas al en .NET. Por esta razn, se suele decir que C# es el lenguaje nativo de .NET
acceder a ellos usando lenguajes no sensibles a maysculas.
La sintaxis y estructuracin de C# es muy parecida a la de C++ o Java, puesto que la
Las enumeraciones no pueden implementar interfaces, y todos sus campos han intencin de Microsoft es facilitar la migracin de cdigos escritos en estos lenguajes a
de ser estticos y del mismo tipo. El tipo de los campos de una enumeracin slo C# y facilitar su aprendizaje a los desarrolladores habituados a ellos. Sin embargo, su
puede ser uno de estos cuatro tipos bsicos: byte, short, int o long. sencillez y el alto nivel de productividad son comparables con los de Visual Basic.

Un lenguaje que hubiese sido ideal utilizar para estos menesteres es Java, pero debido a
problemas con la empresa creadora del mismo -Sun-, Microsoft ha tenido que
desarrollar un nuevo lenguaje que aadiese a las ya probadas virtudes de Java las
modificaciones que Microsoft tena pensado aadirle para mejorarlo an ms y hacerlo
un lenguaje orientado al desarrollo de componentes.

En resumen, C# es un lenguaje de programacin que toma las mejores caractersticas de


lenguajes preexistentes como Visual Basic, Java o C++ y las combina en uno solo. El
hecho de ser relativamente reciente no implica que sea inmaduro, pues Microsoft ha
escrito la mayor parte de la BCL usndolo, por lo que su compilador es el ms depurado
y optimizado de los incluidos en el .NET Framework SDK

Caractersticas de C#

Con la idea de que los programadores ms experimentados puedan obtener una visin
general del lenguaje, a continuacin se recoge de manera resumida las principales
caractersticas de C# Alguna de las caractersticas aqu sealadas no son exactamente
propias del lenguaje sino de la plataforma .NET en general, y si aqu se comentan es
porque tienen una repercusin directa en el lenguaje:

Sencillez: C# elimina muchos elementos que otros lenguajes incluyen y que son
innecesarios en .NET. Por ejemplo:

o El cdigo escrito en C# es autocontenido, lo que significa que no


necesita de ficheros adicionales al propio fuente tales como ficheros de
cabecera o ficheros IDL

Jos Antonio Gonzlez Seco Pgina 21 Jos Antonio Gonzlez Seco Pgina 22
El lenguaje de programacin C# Tema 2: Introduccin a C# El lenguaje de programacin C# Tema 2: Introduccin a C#

Orientacin a componentes: La propia sintaxis de C# incluye elementos


o El tamao de los tipos de datos bsicos es fijo e independiente del propios del diseo de componentes que otros lenguajes tienen que simular
compilador, sistema operativo o mquina para quienes se compile (no mediante construcciones ms o menos complejas. Es decir, la sintaxis de C#
como en C++), lo que facilita la portabilidad del cdigo. permite definir cmodamente propiedades (similares a campos de acceso
controlado), eventos (asociacin controlada de funciones de respuesta a
o No se incluyen elementos poco tiles de lenguajes como C++ tales como notificaciones) o atributos (informacin sobre un tipo o sus miembros)
macros, herencia mltiple o la necesidad de un operador diferente del
punto (.) acceder a miembros de espacios de nombres (::) Gestin automtica de memoria: Como ya se coment, todo lenguaje de .NET
tiene a su disposicin el recolector de basura del CLR. Esto tiene el efecto en el
Modernidad: C# incorpora en el propio lenguaje elementos que a lo largo de lenguaje de que no es necesario incluir instrucciones de destruccin de objetos.
los aos ha ido demostrndose son muy tiles para el desarrollo de aplicaciones Sin embargo, dado que la destruccin de los objetos a travs del recolector de
y que en otros lenguajes como Java o C++ hay que simular, como un tipo bsico basura es indeterminista y slo se realiza cuando ste se active ya sea por falta
decimal que permita realizar operaciones de alta precisin con reales de 128 bits de memoria, finalizacin de la aplicacin o solicitud explcita en el fuente-, C#
(muy til en el mundo financiero), la inclusin de una instruccin foreach que tambin proporciona un mecanismo de liberacin de recursos determinista a
permita recorrer colecciones con facilidad y es ampliable a tipos definidos por el travs de la instruccin using.
usuario, la inclusin de un tipo bsico string para representar cadenas o la
distincin de un tipo bool especfico para representar valores lgicos. Seguridad de tipos: C# incluye mecanismos que permiten asegurar que los
accesos a tipos de datos siempre se realicen correctamente, lo que permite evita
Orientacin a objetos: Como todo lenguaje de programacin de propsito que se produzcan errores difciles de detectar por acceso a memoria no
general actual, C# es un lenguaje orientado a objetos, aunque eso es ms bien perteneciente a ningn objeto y es especialmente necesario en un entorno
una caracterstica del CTS que de C#. Una diferencia de este enfoque orientado a gestionado por un recolector de basura. Para ello se toman medidas del tipo:
objetos respecto al de otros lenguajes como C++ es que el de C# es ms puro en
tanto que no admiten ni funciones ni variables globales sino que todo el cdigo y o Slo se admiten conversiones entre tipos compatibles. Esto es, entre un
datos han de definirse dentro de definiciones de tipos de datos, lo que reduce tipo y antecesores suyos, entre tipos para los que explcitamente se haya
problemas por conflictos de nombres y facilita la legibilidad del cdigo. definido un operador de conversin, y entre un tipo y un tipo hijo suyo
del que un objeto del primero almacenase una referencia del segundo
C# soporta todas las caractersticas propias del paradigma de programacin (downcasting) Obviamente, lo ltimo slo puede comprobarlo en tiempo
orientada a objetos: encapsulacin, herencia y polimorfismo. de ejecucin el CLR y no el compilador, por lo que en realidad el CLR y
el compilador colaboran para asegurar la correccin de las conversiones.
En lo referente a la encapsulacin es importante sealar que aparte de los tpicos
modificadores public, private y protected, C# aade un cuarto modificador o No se pueden usar variables no inicializadas. El compilador da a los
llamado internal, que puede combinarse con protected e indica que al elemento a campos un valor por defecto consistente en ponerlos a cero y controla
cuya definicin precede slo puede accederse desde su mismo ensamblado. mediante anlisis del flujo de control del fuente que no se lea ninguna
variable local sin que se le haya asignado previamente algn valor.
Respecto a la herencia -a diferencia de C++ y al igual que Java- C# slo admite
herencia simple de clases ya que la mltiple provoca ms quebraderos de cabeza o Se comprueba que todo acceso a los elementos de una tabla se realice
que facilidades y en la mayora de los casos su utilidad puede ser simulada con con ndices que se encuentren dentro del rango de la misma.
facilidad mediante herencia mltiple de interfaces. De todos modos, esto vuelve
a ser ms bien una caracterstica propia del CTS que de C#. o Se puede controlar la produccin de desbordamientos en operaciones
aritmticas, informndose de ello con una excepcin cuando ocurra. Sin
Por otro lado y a diferencia de Java, en C# se ha optado por hacer que todos los embargo, para conseguirse un mayor rendimiento en la aritmtica estas
mtodos sean por defecto sellados y que los redefinibles hayan de marcarse con comprobaciones no se hacen por defecto al operar con variables sino slo
el modificador virtual (como en C++), lo que permite evitar errores derivados de con constantes (se pueden detectar en tiempo de compilacin)
redefiniciones accidentales. Adems, un efecto secundario de esto es que las
llamadas a los mtodos sern ms eficientes por defecto al no tenerse que buscar o A diferencia de Java, C# incluye delegados, que son similares a los
en la tabla de funciones virtuales la implementacin de los mismos a la que se ha punteros a funciones de C++ pero siguen un enfoque orientado a objetos,
de llamar. Otro efecto secundario es que permite que las llamadas a los mtodos pueden almacenar referencias a varios mtodos simultneamente, y se
virtuales se puedan hacer ms eficientemente al contribuir a que el tamao de comprueba que los mtodos a los que apunten tengan parmetros y valor
dicha tabla se reduzca. de retorno del tipo indicado al definirlos.

Jos Antonio Gonzlez Seco Pgina 23 Jos Antonio Gonzlez Seco Pgina 24
El lenguaje de programacin C# Tema 2: Introduccin a C# El lenguaje de programacin C# Tema 2: Introduccin a C#

o Pueden definirse mtodos que admitan un nmero indefinido de


parmetros de un cierto tipo, y a diferencia lenguajes como C/C++, en Tambin se da la posibilidad, a travs del concepto de indizador, de redefinir el
C# siempre se comprueba que los valores que se les pasen en cada significado del operador [] para los tipos de dato definidos por el usuario, con lo
llamada sean de los tipos apropiados. que se consigue que se pueda acceder al mismo como si fuese una tabla. Esto es
muy til para trabajar con tipos que acten como colecciones de objetos.
Instrucciones seguras: Para evitar errores muy comunes, en C# se han
impuesto una serie de restricciones en el uso de las instrucciones de control ms Extensibilidad de modificadores: C# ofrece, a travs del concepto de
comunes. Por ejemplo, la guarda de toda condicin ha de ser una expresin atributos, la posibilidad de aadir a los metadatos del mdulo resultante de la
condicional y no aritmtica, con lo que se evitan errores por confusin del compilacin de cualquier fuente informacin adicional a la generada por el
operador de igualdad (==) con el de asignacin (=); y todo caso de un switch ha compilador que luego podr ser consultada en tiempo ejecucin a travs de la
de terminar en un break o goto que indique cul es la siguiente accin a realizar, librera de reflexin de .NET . Esto, que ms bien es una caracterstica propia de
lo que evita la ejecucin accidental de casos y facilita su reordenacin. la plataforma .NET y no de C#, puede usarse como un mecanismo para definir
nuevos modificadores.
Sistema de tipos unificado: A diferencia de C++, en C# todos los tipos de datos
que se definan siempre derivarn, aunque sea de manera implcita, de una clase Versionable: C# incluye una poltica de versionado que permite crear nuevas
base comn llamada System.Object, por lo que dispondrn de todos los versiones de tipos sin temor a que la introduccin de nuevos miembros
miembros definidos en sta clase (es decir, sern objetos) provoquen errores difciles de detectar en tipos hijos previamente desarrollados
y ya extendidos con miembros de igual nombre a los recin introducidos.
A diferencia de Java, en C# esto tambin es aplicable a los tipos de datos bsicos
Adems, para conseguir que ello no tenga una repercusin negativa en su nivel Si una clase introduce un nuevo mtodo cuyas redefiniciones deban seguir la
de rendimiento, se ha incluido un mecanismo transparente de boxing y unboxing regla de llamar a la versin de su padre en algn punto de su cdigo,
con el que se consigue que slo sean tratados como objetos cuando la situacin difcilmente seguiran esta regla miembros de su misma signatura definidos en
lo requiera, y mientras tanto puede aplicrseles optimizaciones especficas. clases hijas previamente a la definicin del mismo en la clase padre; o si
introduce un nuevo campo con el mismo nombre que algn mtodo de una clase
El hecho de que todos los tipos del lenguaje deriven de una clase comn facilita hija, la clase hija dejar de funcionar. Para evitar que esto ocurra, en C# se
enormemente el diseo de colecciones genricas que puedan almacenar objetos toman dos medidas:
de cualquier tipo.
o Se obliga a que toda redefinicin deba incluir el modificador override,
Extensibilidad de tipos bsicos: C# permite definir, a travs de estructuras, con lo que la versin de la clase hija nunca sera considerada como una
tipos de datos para los que se apliquen las mismas optimizaciones que para los redefinicin de la versin de miembro en la clase padre ya que no
tipos de datos bsicos. Es decir, que se puedan almacenar directamente en pila incluira override. Para evitar que por accidente un programador incluya
(luego su creacin, destruccin y acceso sern ms rpidos) y se asignen por este modificador, slo se permite incluirlo en miembros que tengan la
valor y no por referencia. Para conseguir que lo ltimo no tenga efectos misma signatura que miembros marcados como redefinibles mediante el
negativos al pasar estructuras como parmetros de mtodos, se da la posibilidad modificador virtual. As adems se evita el error tan frecuente en Java de
de pasar referencias a pila a travs del modificador de parmetro ref. creerse haber redefinido un miembro, pues si el miembro con override no
existe en la clase padre se producir un error de compilacin.
Extensibilidad de operadores: Para facilitar la legibilidad del cdigo y
conseguir que los nuevos tipos de datos bsicos que se definan a travs de las o Si no se considera redefinicin, entonces se considera que lo que se
estructuras estn al mismo nivel que los bsicos predefinidos en el lenguaje, al desea es ocultar el mtodo de la clase padre, de modo que para la clase
igual que C++ y a diferencia de Java, C# permite redefinir el significado de la hija sea como si nunca hubiese existido. El compilador avisar de esta
mayora de los operadores -incluidos los de conversin, tanto para conversiones decisin a travs de un mensaje de aviso que puede suprimirse
implcitas como explcitas- cuando se apliquen a diferentes tipos de objetos. incluyendo el modificador new en la definicin del miembro en la clase
hija para as indicarle explcitamente la intencin de ocultacin.
Las redefiniciones de operadores se hacen de manera inteligente, de modo que a
partir de una nica definicin de los operadores ++ y -- el compilador puede Eficiente: En principio, en C# todo el cdigo incluye numerosas restricciones
deducir automticamente como ejecutarlos de manera prefijas y postifja; y para asegurar su seguridad y no permite el uso de punteros. Sin embargo, y a
definiendo operadores simples (como +), el compilador deduce cmo aplicar su diferencia de Java, en C# es posible saltarse dichas restricciones manipulando
versin de asignacin compuesta (+=) Adems, para asegurar la consistencia, el objetos a travs de punteros. Para ello basta marcar regiones de cdigo como
compilador vigila que los operadores con opuesto siempre se redefinan por inseguras (modificador unsafe) y podrn usarse en ellas punteros de forma
parejas (por ejemplo, si se redefine ==, tambin hay que redefinir !=)

Jos Antonio Gonzlez Seco Pgina 25 Jos Antonio Gonzlez Seco Pgina 26
El lenguaje de programacin C# Tema 2: Introduccin a C# El lenguaje de programacin C# Tema 2: Introduccin a C#

similar a cmo se hace en C++, lo que puede resultar vital para situaciones cuya definicin estar comprendida entre la llave de apertura de la lnea 2: y su
donde se necesite una eficiencia y velocidad procesamiento muy grandes. correspondiente llave de cierre en la lnea lnea 7:

Compatible: Para facilitar la migracin de programadores, C# no slo mantiene Dentro de la definicin de la clase (lnea 3:) se define un mtodo de nombre Main cuyo
una sintaxis muy similar a C, C++ o Java que permite incluir directamente en cdigo es el indicado entre la llave de apertura de la lnea 4: y su respectiva llave de
cdigo escrito en C# fragmentos de cdigo escrito en estos lenguajes, sino que el cierre (lnea 6:) Un mtodo no es ms que un conjunto de instrucciones a las que se les
CLR tambin ofrece, a travs de los llamados Platform Invocation Services asocia un nombre, de modo que para posteriormente ejecutarlas baste referenciarlas por
(PInvoke), la posibilidad de acceder a cdigo nativo escrito como funciones su nombre en vez de tener que rescribirlas.
sueltas no orientadas a objetos tales como las DLLs de la API Win32. Ntese
que la capacidad de usar punteros en cdigo inseguro permite que se pueda La partcula que antecede al nombre del mtodo indica cul es el tipo de valor que se
acceder con facilidad a este tipo de funciones, ya que stas muchas veces devuelve tras la ejecucin del mtodo, y en este caso es void que significa que no se
esperan recibir o devuelven punteros. devuelve nada. Por su parte, los parntesis colocados tras el nombre del mtodo indican
cules son los parmetros que ste toma, y el que estn vacos significa que el mtodo
Tambin es posible acceder desde cdigo escrito en C# a objetos COM. Para no toma ninguno. Los parmetros de un mtodo permiten modificar el resultado de su
facilitar esto, el .NET Framework SDK incluye una herramientas llamadas ejecucin en funcin de los valores que se les d en cada llamada.
tlbimp y regasm mediante las que es posible generar automticamente clases
proxy que permitan, respectivamente, usar objetos COM desde .NET como si de La palabra static que antecede a la declaracin del tipo de valor devuelto es un
objetos .NET se tratase y registrar objetos .NET para su uso desde COM. modificador del significado de la declaracin de mtodo que indica que el mtodo est
asociado a la clase dentro de la que se define y no a los objetos que se creen a partir de
Finalmente, tambin se da la posibilidad de usar controles ActiveX desde cdigo ella. Main() es lo que se denomina el punto de entrada de la aplicacin, que no es ms
.NET y viceversa. Para lo primero se utiliza la utilidad aximp, mientras que para que el mtodo por el que comenzar su ejecucin. Necesita del modificador static para
lo segundo se usa la ya mencionada regasm. evitar que para llamarlo haya que crear algn objeto de la clase donde se haya definido.

Finalmente, la lnea 5: contiene la instruccin con el cdigo a ejecutar, que lo que se


Escritura de aplicaciones hace es solicitar la ejecucin del mtodo WriteLine() de la clase Console definida en el
espacio de nombres System pasndole como parmetro la cadena de texto con el
Aplicacin bsica Hola Mundo! contenido Hola Mundo! Ntese que las cadenas de textos son secuencias de caracteres
delimitadas por comillas dobles aunque dichas comillas no forman parte de la cadena.
Por su parte, un espacio de nombres puede considerarse que es para las clases algo
Bsicamente una aplicacin en C# puede verse como un conjunto de uno o ms similar a lo que un directorio es para los ficheros: una forma de agruparlas.
ficheros de cdigo fuente con las instrucciones necesarias para que la aplicacin
funcione como se desea y que son pasados al compilador para que genere un ejecutable. El mtodo WriteLine() se usar muy a menudo en los prximos temas, por lo que es
Cada uno de estos ficheros no es ms que un fichero de texto plano escrito usando conveniente sealar ahora que una forma de llamarlo que se utilizar en repetidas
caracteres Unicode y siguiendo la sintaxis propia de C#. ocasiones consiste en pasarle un nmero indefinido de otros parmetros de cualquier
tipo e incluir en el primero subcadenas de la forma {i}. Con ello se consigue que se
Como primer contacto con el lenguaje, nada mejor que el tpico programa de iniciacin muestre por la ventana de consola la cadena que se le pasa como primer parmetro pero
Hola Mundo! que lo nico que hace al ejecutarse es mostrar por pantalla el mensaje sustituyndole las subcadenas {i} por el valor convertido en cadena de texto del
2
Hola Mundo! Su cdigo es:
parmetro que ocupe la posicin i+2 en la llamada a WriteLine(). Por ejemplo, la
1: class HolaMundo siguiente instruccin mostrara Tengo 5 aos por pantalla si x valiese 5:
2: {
3: static void Main() System.Console.WriteLine(Tengo {0} aos, x);
4: {
5: System.Console.WriteLine(Hola Mundo!); Para indicar cmo convertir cada objeto en un cadena de texto basta redefinir su mtodo
6: } ToString(), aunque esto es algo que no se ver hasta el Tema 5: Clases.
7: }
Antes de seguir es importante resaltar que C# es sensible a las maysculas, los que
Todo el cdigo escrito en C# se ha de escribir dentro de una definicin de clase, y lo significa que no da igual la capitalizacin con la que se escriban los identificadores. Es
que en la lnea 1: se dice es que se va a definir una clase (class) de nombre HolaMundo1 decir, no es lo mismo escribir Console que COnsole o CONSOLE, y si se hace de alguna
de las dos ltimas formas el compilador producir un error debido a que en el espacio de
2 nombres System no existe ninguna clase con dichos nombres. En este sentido, cabe
Los nmeros de lnea no forman parte del cdigo sino que slo se incluyen para facilitar su posterior
explicacin. sealar que un error comn entre programadores acostumbrados a Java es llamar al

Jos Antonio Gonzlez Seco Pgina 27 Jos Antonio Gonzlez Seco Pgina 28
El lenguaje de programacin C# Tema 2: Introduccin a C# El lenguaje de programacin C# Tema 2: Introduccin a C#

punto de entrada main en vez de Main, lo que provoca un error al compilar ejecutables en Inicio de Windows que no hace ms que abrir la ventana de consola y llamar
tanto que el compilador no detectar ninguna definicin de punto de entrada. automticamente a vsvars32.bat. En cualquier caso, si usa otros compiladores de C#
puede que varie la forma de realizar la compilacin, por lo que lo que aqu se explica
en principio slo ser vlido para los compiladores de C# de Microsoft para Windows.
Puntos de entrada
Tras la compilacin se obtendra un ejecutable llamado HolaMundo.exe cuya ejecucin
Ya se ha dicho que el punto de entrada de una aplicacin es un mtodo de nombre producira la siguiente salida por la ventana de consola:
Main que contendr el cdigo por donde se ha de iniciar la ejecucin de la misma. Hasta
Hola Mundo!
ahora slo se ha visto una versin de Main() que no toma parmetros y tiene como tipo
de retorno void, pero en realidad todas sus posibles versiones son:
Si la aplicacin que se vaya a compilar no utilizase la ventana de consola para mostrar
static void Main() su salida sino una interfaz grfica de ventanas, entonces habra que compilarla pasando
static int Main() al compilador la opcin /t con el valor winexe antes del nombre del fichero a
static int Main(string[] args) compilar. Si no se hiciese as se abrra la ventana de consola cada vez que ejecutase la
static void Main(string[] args) aplicacin de ventanas, lo que suele ser indeseable en este tipo de aplicaciones. As,
Como se ve, hay versiones de Main() que devuelven un valor de tipo int. Un int no es para compilar Ventanas.cs como ejecutable de ventanas sera conveniente escribir:
ms que un tipo de datos capaz de almacenar valor enteros comprendidos entre csc /t:winexe Ventanas.cs
2.1471483.648 y 2.1471483.647, y el nmero devuelto por Main() sera interpretado
como cdigo de retorno de la aplicacin. ste valor suele usarse para indicar si la Ntese que aunque el nombre winexe d la sensacin de que este valor para la opcin
aplicacin a terminado con xito (generalmente valor 0) o no (valor segn la causa de la /t slo permite generar ejecutables de ventanas, en realidad lo que permite es generar
terminacin anormal), y en el Tema 8: Mtodos se explicar como devolver valores. ejecutables sin ventana de consola asociada. Por tanto, tambin puede usarse para
generar ejecutables que no tengan ninguna interfaz asociada, ni de consola ni grfica.
Tambin hay versiones de Main() que toman un parmetro donde se almacenar la lista
de argumentos con los que se llam a la aplicacin, por lo que slo es til usar estas Si en lugar de un ejecutable -ya sea de consola o de ventanas- se desea obtener una
versiones del punto de entrada si la aplicacin va a utilizar dichos argumentos para algo. librera, entonces al compilar hay que pasar al compilador la opcin /t con el valor
El tipo de este parmetro es string[], lo que significa que es una tabla de cadenas de library. Por ejemplo, siguiendo con el ejemplo inicial habra que escribir:
texto (en el Tema 5: Claes se explicar detenidamente qu son las tablas y las cadenas),
y su nombre -que es el que habr de usarse dentro del cdigo de Main() para hacerle csc /t:library HolaMundo.cs
referencia- es args en el ejemplo, aunque podra drsele cualquier otro
En este caso se generara un fichero HolaMundo.dll cuyos tipos de datos podran
utilizarse desde otros fuentes pasando al compilador una referencia a los mismos
Compilacin en lnea de comandos mediante la opcin /r. Por ejemplo, para compilar como ejecutable un fuente A.cs que
use la clase HolaMundo de la librera HolaMundo.dll se escribira:
Una vez escrito el cdigo anterior con algn editor de textos como el Bloc de Notas csc /r:HolaMundo.dll A.cs
de Windows- y almacenado en formato de texto plano en un fichero HolaMundo.cs3,
para compilarlo basta abrir una ventana de consola (MS-DOS en Windows), colocarse En general /r permite referenciar a tipos definidos en cualquier ensamblado, por lo que
en el directorio donde se encuentre y pasrselo como parmetro al compilador as: el valor que se le indique tambin puede ser el nombre de un ejecutable. Adems, en
cada compilacin es posible referenciar mltiples ensamblados ya sea incluiyendo la
csc HolaMundo.cs opcin /r una vez por cada uno o incluiyendo mltiples referencias en una nica
opcin /r usando comas o puntos y comas como separadores. Por ejemplo, las
csc.exe es el compilador de C# incluido en el .NET Framework SDK para Windows
siguientes tres llamadas al compilador son equivalentes:
de Microsoft. Aunque en principio el programa de instalacin del SDK lo aade
automticamente al path para poder llamarlo sin problemas desde cualquier directorio, csc /r:HolaMundo.dll;Otro.dll;OtroMs.exe A.cs
si lo ha instalado a travs de VS.NET esto no ocurrir y deber configurrselo ya sea csc /r:HolaMundo.dll,Otro.dll,OtroMs.exe A.cs
manualmente, o bien ejecutando el fichero por lotes Common7\Tools\vsvars32.bat csc /t:HolaMundo.dll /r:Otro.dll /r:OtroMs.exe A.cs
que VS.NET incluye bajo su directorio de instalacin, o abriendo la ventana de consola
desde el icono Herramientas de Visual Studio.NET Smbolo del sistema de Hay que sealar que aunque no se indique nada, en toda compilacin siempre se
Visual Studio.NET correspondiente al grupo de programas de VS.NET en el men referencia por defecto a la librera mscorlib.dll de la BCL, que incluye los tipos de
uso ms frecuente. Si se usan tipos de la BCL no incluidos en ella habr que incluir al
3
El nombre que se d al fichero puede ser cualquiera, aunque se recomienda darle la extensin .cs ya compilar referencias a las libreras donde estn definidos (en la documentacin del SDK
que es la utilizada por convenio sobre cada tipo de la BCL puede encontrar informacin sobre donde se defini)

Jos Antonio Gonzlez Seco Pgina 29 Jos Antonio Gonzlez Seco Pgina 30
El lenguaje de programacin C# Tema 2: Introduccin a C# El lenguaje de programacin C# Tema 2: Introduccin a C#

Lgicamente, para que esto funcione A.cs o B.cs tiene que contener alguna definicin
Tanto las libreras como los ejecutables son ensamblados. Para generar un mdulo de de algn tipo llamado Principal con un nico mtodo vlido como punto de entrada
cdigo que no forme parte de ningn ensamblado sino que contenga definiciones de (obviamente, si contiene varios se volvera a tener el problema de no saber cul utilizar)
tipos que puedan aadirse a ensamblados que se compilen posteriormente, el valor que
ha de darse al compilar a la opcin /t es module. Por ejemplo:
Compilacin con Visual Studio.NET
csc /t:module HolaMundo.cs
Para compilar una aplicacin en Visual Studio.NET primero hay que incluirla dentro de
Con la instruccin anterior se generara un mdulo llamado HolaMundo.netmodule
algn proyecto. Para ello basta pulsar el botn New Project en la pgina de inicio que
que podra ser aadido a compilaciones de ensamblados incluyndolo como valor de la
se muestra nada ms arrancar dicha herramienta, tras lo que se obtendr una pantalla
opcin /addmodule. Por ejemplo, para aadir el mdulo anterior a la compilacin del
con el aspecto mostrado en la Ilustracin 1.
fuente librera Lib.cs como librera se escribira:
csc /t:library /addmodule:HolaMundo.netmodule Lib.cs En el recuadro de la ventana mostrada etiquetado como Project Types se ha de
seleccionar el tipo de proyecto a crear. Obviamente, si se va a trabajar en C# la opcin
Aunque hasta ahora todas las compilaciones de ejemplo se han realizado utilizando un que habr que escoger en la misma ser siempre Visual C# Projects.
nico fichero de cdigo fuente, en realidad nada impide que se puedan utilizar ms. Por
ejemplo, para compilar los ficheros A.cs y B.cs en una librera A.dll se ejecutara: En el recuadro Templates se ha de seleccionar la plantilla correspondiente al subtipo
de proyecto dentro del tipo indicado en Project Types que se va a realizar. Para
csc /t:library A.cs B.cs
realizar un ejecutable de consola, como es nuestro caso, hay que seleccionar el icono
Ntese que el nombre que por defecto se d al ejecutable generado siempre es igual al etiquetado como Console Application. Si se quisiese realizar una librera habra que
del primer fuente especificado pero con la extensin propia del tipo de compilacin seleccionar Class Library, y si se quisies realizar un ejecutable de ventanas habra
realizada (.exe para ejecutables, .dll para libreras y .netmodule para mdulos) Sin que seleccionar Windows Application. Ntese que no se ofrece ninguna plantilla para
embargo, puede especificrse como valor en la opcin /out del compilador cualquier realizar mdulos, lo que se debe a que desde Visual Studio.NET no pueden crearse.
otro tal y como muestra el siguiente ejemplo que compila el fichero A.cs como una Por ltimo, en el recuadro de texto Name se ha de escribir el nombre a dar al proyecto y
librera de nombre Lib.exe: en Location el del directorio base asociado al mismo. Ntese que bajo de Location
aparecer un mensaje informando sobre cual ser el directorio donde finalmente se
csc /t:library /out:Lib.exe A.cs almacenarn los archivos del proyecto, que ser el resultante de concatenar la ruta
especificada para el directorio base y el nombre del proyecto.
Vase que aunque se haya dado un nombre terminado en .exe al fichero resultante,
ste sigue siendo una librera y no un ejecutable e intentar ejecutarlo producira un
mensaje de error. Obviamente no tiene mucho sentido darle esa extensin, y slo se le
ha dado en este ejemplo para demostrar que, aunque recomendable, la extensin del
fichero no tiene porqu corresponderse realmente con el tipo de fichero del que se trate.

A la hora de especificar ficheros a compilar tambin se pueden utilizar los caracteres de


comodn tpicos del sistema operativo. Por ejemplo, para compilar todos los ficheros
con extensin .cs del directorio actual en una librera llamada Varios.dll se hara:
csc /t:library /out:varios.dll *.cs

Con lo que hay que tener cuidado, y en especial al compilar varios fuentes, es con que
no se compilen a la vez ms de un tipo de dato con punto de entrada, pues entonces el
compilador no sabra cul usar como inicio de la aplicacin. Para orientarlo, puede
especificarse como valor de la opcin /main el nombre del tipo que contenga el Main()
ha usar como punto de entrada. As, para compilar los ficheros A.cs y B.cs en un
ejecutable cuyo punto de entrada sea el definido en el tipo Principal, habra que escribir:
csc /main:Principal A.cs B.cs
Ilustracin 1: Ventana de creacin de nuevo proyecto en Visual Studio.NET

Jos Antonio Gonzlez Seco Pgina 31 Jos Antonio Gonzlez Seco Pgina 32
El lenguaje de programacin C# Tema 2: Introduccin a C# El lenguaje de programacin C# Tema 2: Introduccin a C#

Una vez configuradas todas estas opciones, al pulsar botn OK Visual Studio crear
toda la infraestructura adecuada para empezar a trabajar cmodamente en el proyecto.
Como puede apreciarse en la Ilustracin 2, esta infraestructura consistir en la
generacin de un fuente que servir de plantilla para la realizacin de proyectos del tipo
elegido (en nuestro caso, aplicaciones de consola en C#):

Ilustracin 3: Hoja de propiedades del proyecto en Visual Studio.NET

Esta ventana permite configurar de manera visual la mayora de opciones con las que se
llamar al compilador en lnea de comandos. Por ejemplo, para cambiar el nombre del
Ilustracin 2: Plantilla para aplicaciones de consola generada por Visual Studio.NET fichero de salida (opcin /out) se indica su nuevo nombre en el cuadro de texto Common
Properties General Assembly Name, para cambiar el tipo de proyecto a
A partir de esta plantilla, escribir el cdigo de la aplicacin de ejemplo es tan sencillo generar (opcin /t) se utiliza Common Properties General Output Type
con simplemente teclear System.Console.WriteLine(Hola Mundo!) dentro de la definicin (como ver si intenta cambiarlo, no es posible generar mdulos desde Visual
del mtodo Main() creada por Visual Studio.NET. Claro est, otra posibilidad es borrar Studio.NET), y el tipo que contiene el punto de entrada a utilizar (opcin /main) se
toda la plantilla y sustituirla por el cdigo para HolaMundo mostrado anteriormente. indica en Common Properties General Startup Object
Sea haga como se haga, para compilar y ejecutar tras ello la aplicacin slo hay que Finalemente, para aadir al proyecto referencias a ensamblados externos (opcin /r)
pulsar CTRL+F5 o seleccionar Debug Start Without Debugging en el men basta seleccionar Project Add Reference en el men principal de VS.NET.
principal de Visual Studio.NET. Para slo compilar el proyecto, entonces hay que
seleccionar Build Rebuild All. De todas formas, en ambos casos el ejecutable
generado se almacenar en el subdirectorio Bin\Debug del directorio del proyecto.

En el extremo derecho de la ventana principal de Visual Studio.NET puede encontrar el


denominado Solution Explorer (si no lo encuentra, seleccione View Solution
Explorer), que es una herramienta que permite consultar cules son los archivos que
forman el proyecto. Si selecciona en l el icono correspondiente al proyecto en que
estamos trabajando y pulsa View Property Pages obtendr una hoja de
propiedades del proyecto con el aspecto mostrado en la Ilustracin 3:

Jos Antonio Gonzlez Seco Pgina 33 Jos Antonio Gonzlez Seco Pgina 34
El lenguaje de programacin C# Tema 2: Introduccin a C# El lenguaje de programacin C# Tema 3: El Preprocesador

TEMA 3: EL PREPROCESADOR

Concepto de preprocesador

El preprocesado es un paso previo4 a la compilacin mediante el que es posible


controlar la forma en que se realizar sta. El preprocesador es el mdulo auxiliar que
utiliza el compilador para realizar estas tareas, y lo que finalmente el compilador
compila es el resultado de aplicar el preprocesador al fichero de texto fuente, resultado
que tambin es un fichero de texto. Ntese pues, que mientras que el compilador hace
una traduccin de texto a binario, lo que el preprocesador hace es una traduccin de
texto a texto.

Aquellos que tengan experiencia en el uso del preprocesador en lenguajes como C++ y
conozcan los problemas que implica el uso del mismo pueden respirar tranquilos, ya
que en C# se han eliminado la mayora de caractersticas de ste que provocaban errores
difciles de detectar (macros, directivas de inclusin, etc.) y prcticamente slo se usa
para permitir realizar compilaciones condicionales de cdigo.

Directivas de preprocesado

Concepto de directiva. Sintaxis

El preprocesador no interpreta de ninguna manera el cdigo fuente del fichero, sino que
slo interpreta de dicho fichero lo que se denominan directivas de preprocesado. Estas
directivas son lneas de texto del fichero fuente que se caracterizan porque en ellas el
primer carcter no blanco que aparece es una almohadilla (carcter #) Por ejemplo:

#define TEST
#error Ha habido un error fatal

No se preocupe ahora si no entiendo el significado de estas directivas, ya que se


explicarn ms adelante. Lo nico debe saber es que el nombre que se indica tras el
smbolo # es el nombre de la directiva, y el texto que se incluye tras l (no todas las
directivas tienen porqu incluirlo) es el valor que se le da. Por tanto, la sintaxis de una
directiva es:

#<nombreDirectiva> <valorDirectiva>

Es posible incluir comentarios en la misma lnea en que se declara una directiva, aunque
estos slo pueden ser comentarios de una lnea que empiecen con // Por ejemplo, el
siguiente comentario es vlido:

#define TEST // Ha habido algn error durante el preprocesado

4
En realidad, en C# se realiza a la vez que el anlisis lxico del cdigo fuente; pero para simplificar la
explicacin consideraremos que se realiza antes que ste, en una etapa previa independiente.

Jos Antonio Gonzlez Seco Pgina 35 Jos Antonio Gonzlez Seco Pgina 36
El lenguaje de programacin C# Tema 3: El Preprocesador El lenguaje de programacin C# Tema 3: El Preprocesador

Pero este otro no, pues aunque ocupa una lnea tiene la sintaxis de los comentarios que compilador son equivalentes y definen identificadores de preprocesado de nombres
pueden ocupar varias lneas: PRUEBA y TRAZA durante la compilacin de un fichero fuente de nombre ejemplo.cs:

#define TEST /* Ha habido algn error durante el preprocesado */ csc /d:PRUEBA /d:TRAZA ejemplo.cs
csc /d:PRUEBA,TRAZA ejemplo.cs
csc /d:PRUEBA;TRAZA ejemplo.cs
Definicin de identificadores de preprocesado
Ntese en el ejemplo que si queremos definir ms de un identificador usando esta
tcnica tenemos dos alternativas: incluir varias opciones /d en la llamada al compilador
Como ya se ha comentado, la principal utilidad del preprocesador en C# es la de
o definir varios de estos identificadores en una misma opcin /d separndolos mediante
permitir determinar cules regiones de cdigo de un fichero fuente se han de compilar.
caracteres de coma (,) o punto y coma (;)
Para ello, lo que se hace es encerrar las secciones de cdigo opcionales dentro de
directivas de compilacin condicional, de modo que slo se compilarn si determinados
Si se trabaja con Visual Studio.NET en lugar de directamente con el compilador en
identificadores de preprocesado estn definidos. Para definir un identificador de este
lnea de comandos, entonces puede conseguir mismo efecto a travs de View
tipo la directiva que se usa sigue esta sintaxis: Property Pages Configuration Options Build Conditional
Compilation Constants, donde nuevamente usado el punto y coma (;) o la coma (,)
#define <nombreIdentificador>
como separadores, puede definir varias constantes. Para que todo funcione bien, antes
Esta directiva define un identificador de preprocesado <nombreIdentificador>. de seleccionar View ha de seleccionar en el Solution Explorer (se abre con View
Aunque ms adelante estudiaremos detalladamente cules son los nombres vlidos Solution Explorer) el proyecto al que aplicar la definicin de las constantes.
como identificadores en C#, por ahora podemos considerar que son vlidos aquellos
formados por uno o ms caracteres alfanumricos tales que no sean ni true ni false y no Finalmente, respecto al uso de #define slo queda comentar que es posible definir varias
empiecen con un numero. Por ejemplo, para definir un identificador de preprocesado de veces una misma directiva sin que ello provoque ningn tipo de error en el compilador,
nombre PRUEBA se hara: lo que permite que podamos pasar tantos valores a la opcin /d del compilador como
queramos sin temor a que entren en conflicto con identificadores de preprocesado ya
#define PRUEBA incluidos en los fuentes a compilar.

Por convenio se da a estos identificadores nombres en los que todas las letras se
escriben en maysculas, como en el ejemplo anterior. Aunque es slo un convenio y Eliminacin de identificadores de preprocesado
nada obliga a usarlo, sta ser la nomenclatura que usaremos en el presente documento
ya que es la que sigue Microsoft en sus cdigos de ejemplo. Conviene familiarizarse Del mismo modo que es posible definir identificadores de preprocesado, tambin es
con ella porque hay mucho cdigo escrito que la usa y porque emplearla facilitar a los posible eliminar definiciones de este tipo de identificadores previamente realizadas.
dems la lectura de nuestro cdigo ya que es la notacin que esperarn encontrar. Para ello la directiva que se usa tiene la siguiente sintaxis:

Es importante sealar que cualquier definicin de identificador ha de preceder a #undef <nombreIdentificador>


cualquier aparicin de cdigo en el fichero fuente. Por ejemplo, el siguiente cdigo no
es vlido puesto que en l antes del #define se ha incluido cdigo fuente (el class A): En caso de que se intente eliminar con esta directiva un identificador que no haya sido
definido o cuya definicin ya haya sido eliminada no se producir error alguno, sino que
class A simplemente la directiva de eliminacin ser ignorada. El siguiente ejemplo muestra un
#define PRUEBA ejemplo de esto en el que el segundo #undef es ignorado:
{}
#define VERSION1
Sin embargo, aunque no pueda haber cdigo antes de un #define s que existe total #undef VERSION1
libertad para precederlo de otras directivas de preprocesado. #undef VERSION1

Existe una forma alternativa de definir un identificador de preprocesado y que adems Al igual que ocurra con las directivas #define, no se puede incluir cdigo fuente antes
permite que dicha definicin slo sea vlida en una compilacin en concreto. Esta forma de las directivas #undef, sino que, todo lo ms, lo nico que podran incluirse antes que
consiste en pasarle al compilador en su llamada la opcin /d:<nombreIdentificador> ellas seran directivas de preprocesado.
(forma abreviada de /define:<nombreIdentificador>), caso en que durante la
Compilacin condicional
compilacin se considerar que al principio de todos los ficheros fuente a compilar se
encuentra definido el identificador indicado. Las siguientes tres formas de llamar al
Como se ha repetido varias veces a lo largo del tema, la principal utilidad del
preprocesador en C# es la de permitir la compilacin de cdigo condicional, lo que

Jos Antonio Gonzlez Seco Pgina 37 Jos Antonio Gonzlez Seco Pgina 38
El lenguaje de programacin C# Tema 3: El Preprocesador El lenguaje de programacin C# Tema 3: El Preprocesador

consiste en slo permitir que se compile determinadas regiones de cdigo fuente si las
variables de preprocesado definidas cumplen alguna condicin determinada. Para El cdigo fuente que el preprocesador pasar al compilador en caso de que compilemos
conseguir esto se utiliza el siguiente juego de directivas: sin especificar ninguna opcin /d en la llamada al compilador ser:

#if <condicin1> using System;


<cdigo1>
#elif <condicin2> class A
<cdigo2> {
... public static void Main()
#else {
<cdigoElse> Console.Write(Esto es una prueba);
#endif Console.Write( sin traza);
}
El significado de una estructura como esta es que si se cumple <condicin1> entonces }
se pasa al compilador el <cdigo1>, si no ocurre esto pero se cumple <condicin2>
entonces lo que se pasara al compilador sera <cdigo2>, y as continuamente hasta Ntese como en el cdigo que se pasa al compilador ya no aparece ninguna directiva de
que se llegue a una rama #elif cuya condicin se cumpla. Si no se cumple ninguna pero preprocesado, pues lo que el preprocesador le pasa es el cdigo resultante de aplicar al
hay una rama #else se pasar al compilador el <cdigoElse>, pero si dicha rama no original las directivas de preprocesado que contuviese.
existiese entonces no se le pasara cdigo alguno y se continuara preprocesando el
cdigo siguiente al #endif en el fuente original. Asimismo, si compilsemos el cdigo fuente original llamando al compilador con
/d:TRAZA, lo que el preprocesador pasara al compilador sera:
Aunque las ramas #else y #eldif son opcionales, hay que tener cuidado y no mezclarlas,
using System;
ya que la rama #else slo puede aparecer como ltima rama del bloque #if...#endif.
class A
Es posible anidar varias estructuras #if...#endif, como muestra el siguiente cdigo: {
public static void Main()
#define PRUEBA {
Console.Write (Esto es una prueba);
using System; Console.Write( sin traza);
}
class A }
{
public static void Main() Hasta ahora solo hemos visto que la condicin de un #if o #elif puede ser un
{ identificador de preprocesado, y que ste valdr true o false segn est o no definido.
#if PRUEBA
Console.Write (Esto es una prueba);
Pues bien, stos no son el nico tipo de condiciones vlidas en C#, sino que tambin es
#if TRAZA posible incluir condiciones que contengan expresiones lgicas formadas por
Console.Write( con traza); identificadores de preprocesado, operadores lgicos (! para not, && para and y ||
#elif !TRAZA para or), operadores relacionales de igualdad (==) y desigualdad (!=), parntesis (( y ))
Console.Write( sin traza); y los identificadores especiales true y false. Por ejemplo:
#endif
#endif
} #if TRAZA // Se cumple si TRAZA esta definido.
} #if TRAZA==true // dem al ejemplo anterior aunque con una sintaxis menos cmoda
#if !TRAZA // Slo se cumple si TRAZA no est definido.
#if TRAZA==false // dem a al ejemplo anterior aunque con una sintaxis menos cmoda
Como se ve en el ejemplo, las condiciones especificadas son nombres de identificadores #if TRAZA == PRUEBA // Solo se cumple si tanto TRAZA como PRUEBA estn
de preprocesado, considerndose que cada condicin slo se cumple si el identificador // definidos o si no ninguno lo est.
que se indica en ella est definido. O lo que es lo mismo: un identificador de #if TRAZA != PRUEBA // Solo se cumple si TRAZA esta definido y PRUEBA no o
preprocesado vale cierto (true en C#) si est definido y falso (false en C#) si no. // viceversa
#if TRAZA && PRUEBA // Solo se cumple si estn definidos TRAZA y PRUEBA.
#if TRAZA || PRUEBA // Solo se cumple si estn definidos TRAZA o PRUEBA.
El smbolo ! incluido en junto al valor de la directiva #elif es el smbolo de no lgico, #if false // Nunca se cumple (por lo que es absurdo ponerlo)
y el #elif en el que se usa lo que nos permite es indicar que en caso de que no se #if true // Siempre se cumple (por lo que es absurdo ponerlo)
encuentre definido el identificador de preprocesado TRAZA se han de pasar al compilador
las instrucciones a continuacin indicadas (o sea, el Console.Write(sin traza);)

Jos Antonio Gonzlez Seco Pgina 39 Jos Antonio Gonzlez Seco Pgina 40
El lenguaje de programacin C# Tema 3: El Preprocesador El lenguaje de programacin C# Tema 3: El Preprocesador

Es fcil ver que la causa de la restriccin antes comentada de que no es vlido dar un Por defecto el compilador enumera las lneas de cada fichero fuente segn el orden
como nombre true o false a un identificador de preprocesado se debe al significado normal en que estas aparecen en el mismo, y este orden es el que sigue a la hora de
especial que stos tienen en las condiciones de los #if y #elif informar de errores o de avisos durante la compilacin. Sin embargo, hay situaciones en
las que interesa cambiar esta numeracin, y para ello se ofrece una directiva con la
siguiente sintaxis:
Generacin de avisos y errores
#line <nmero> <nombreFichero>
El preprocesador de C# tambin ofrece directivas que permiten generar avisos y errores
durante el proceso de preprocesado en caso de llegar a ser interpretadas por el Esta directiva indica al preprocesador que ha de considerar que la siguiente lnea del
preprocesador. Estas directivas tienen la siguiente sintaxis: fichero fuente en que aparece es la lnea cuyo nmero se le indica, independientemente
del valor que tuviese segn la numeracin usada en ese momento. El valor indicado en
#warning <mensajeAviso> <nombreFichero> es opcional, y en caso de aparecer indica el nombre que se ha de
#error <mensajeError> considerar que tiene el fichero a la hora de dar mensajes de error. Un ejemplo:

La directiva #warning lo que hace al ser procesada es provocar que el compilador #line 127 csmace.cs
produzca un mensaje de aviso que siga el formato estndar usado por ste para ello y
cuyo texto descriptivo tenga el contenido indicado en <mensajeAviso>; y #error hace lo Este uso de #line indica que el compilador ha de considerar que la lnea siguiente es la
mismo pero provocando un mensaje de error en vez de uno de aviso. lnea 127 del fichero csmace.cs. A partir de ella se seguir usando el sistema de
numeracin normal (la siguiente a esa ser la 128 de csmace.cs, la prxima la 129,
Usando directivas de compilacin condicional se puede controlar cuando se han de etc.) salvo que ms adelante se vuelva a cambiar la numeracin con otra directiva #line.
producir estos mensajes, cuando se han de procesar estas directivas. De hecho la
principal utilidad de estas directivas es permitir controlar errores de asignacin de Aunque en principio puede parecer que esta directiva es de escasa utilidad, lo cierto es
valores a los diferentes identificadores de preprocesado de un cdigo, y un ejemplo de que suele venir bastante bien para la escritura de compiladores y otras herramientas que
ello es el siguiente: generen cdigo en C# a partir de cdigo escrito en otros lenguajes.

#warning Cdigo aun no revisado


#define PRUEBA Marcado de regiones de cdigo
#if PRUEBA && FINAL
#error Un cdigo no puede ser simultneamente de prueba y versin final
#endif Es posible marcar regiones de cdigo y asociarles un nombre usando el juego de
class A directivas #region y #endregion. Estas directivas se usan as:
{}
#region <nombreRegin>
En este cdigo siempre se producir el mensaje de aviso, pero el #if indica que slo se <cdigo>
producir el mensaje de error si se han definido simultneamente los identificadores de #endregion
preprocesado PRUEBA y FINAL
La utilidad que se d a estas marcaciones depende de cada herramienta, pero en el
Como puede deducirse del ejemplo, el preprocesador de C# considera que los mensajes momento de escribir estas lneas la nica herramienta disponible que haca uso de ellas
asociados a directivas #warning o #error son todo el texto que se encuentra tras el era Visual Studio.NET, donde se usa para marcar cdigo de modo que desde la ventana
nombre de dichas directivas y hasta el final de la lnea donde stas aparecen. Por tanto, de cdigo podamos expandirlo y contraerlo con una nica pulsacin de ratn. En
todo comentario que se incluya en una lnea de este tipo ser considerado como parte concreto, en la ventana de cdigo de Visual Studio aparecer un smbolo [-] junto a las
del mensaje a mostrar, y no como comentario como tal. Por ejemplo, ante la directiva: regiones de cdigo as marcadas de manera que pulsando sobre l todo el cdigo
contenido en la regin se comprimir y ser sustituido por el nombre dado en
#error La compilacin ha fallado // Error
<nombreRegin>. Tras ello, el [-] se convertir en un [+] y si volvemos a pulsarlo el
Lo que se mostrar en pantalla es un mensaje de la siguiente forma: cdigo contrado se expandir y recuperar su aspecto original. A continuacin se
muestra un ejemplo de cada caso:
Fichero.cs(3,5): error CS1029: La compilacin ha fallado // Error

Cambios en la numeracin de lneas

Jos Antonio Gonzlez Seco Pgina 41 Jos Antonio Gonzlez Seco Pgina 42
El lenguaje de programacin C# Tema 3: El Preprocesador El lenguaje de programacin C# Tema 4: Aspectos lxicos

TEMA 4: ASPECTOS LXICOS

Comentarios

Un comentario es texto que se incluye en el cdigo fuente para facilitar su lectura a los
programadores y cuyo contenido es, por defecto, completamente ignorado por el
compilador. Suelen usarse para incluir informacin sobre el autor del cdigo, para
aclarar el significado o el porqu de determinadas secciones de cdigo, para describir el
funcionamiento de los mtodos de las clases, etc.

En C# hay dos formas de escribir comentarios. La primera consiste en encerrar todo el


texto que se desee comentar entre caracteres /* y */ siguiendo la siguiente sintaxis:

/*<texto>*/

Ilustracin 4: Cdigo de regin expandido Estos comentarios pueden abarcar tantas lneas como sea necesario. Por ejemplo:

/* Esto es un comentario
que ejemplifica cmo se escribe comentarios que ocupen varias lneas */

Ahora bien, hay que tener cuidado con el hecho de que no es posible anidar comentarios
de este tipo. Es decir, no vale escribir comentarios como el siguiente:

/* Comentario contenedor /* Comentario contenido */ */

Esto se debe a que como el compilador ignora todo el texto contenido en un comentario
y slo busca la secuencia */ que marca su final, ignorar el segundo /* y cuando llegue al
primer */ considerar que ha acabado el comentario abierto con el primer /* (no el
abierto con el segundo) y pasar a buscar cdigo. Como el */ slo lo admite si ha
detectado antes algn comentario abierto y an no cerrado (no mientras busca cdigo),
cuando llegue al segundo */ considerar que ha habido un error ya que encontrar el */
donde esperaba encontrar cdigo

Dado que muchas veces los comentarios que se escriben son muy cortos y no suelen
ocupar ms de una lnea, C# ofrece una sintaxis alternativa ms compacta para la
Ilustracin 5: Cdigo de regin contrado escritura este tipo de comentarios en las que se considera como indicador del comienzo
del comentario la pareja de caracteres // y como indicador de su final el fin de lnea. Por
Hay que tener cuidado al anidar regiones con directivas de compilacin condicional, ya tanto, la sintaxis que siguen estos comentarios es:
que todo bloque #if...#endif que comience dentro de una regin ha de terminar tambin
// <texto>
dentro de ella. Por tanto, el siguiente uso de la directiva #region no es valido ya que
ReginErrnea termina estando el bloque #if...#endif abierto:
Y un ejemplo de su uso es:
#region ReginErrnea
#if A // Este comentario ejemplifica como escribir comentarios abreviados de una sola lnea
#endregion
#endif Estos comentarios de una sola lnea s que pueden anidarse sin ningn problema. Por
ejemplo, el siguiente comentario es perfectamente vlido:

// Comentario contenedor // Comentario contenido

Jos Antonio Gonzlez Seco Pgina 43 Jos Antonio Gonzlez Seco Pgina 44
El lenguaje de programacin C# Tema 4: Aspectos lxicos El lenguaje de programacin C# Tema 4: Aspectos lxicos

Aparte de estas palabras reservadas, si en futuras implementaciones del lenguaje se


Identificadores decidiese incluir nuevas palabras reservadas, Microsoft dice que dichas palabras habran
de incluir al menos dos smbolos de subrayado consecutivos (__) Por tanto, para evitar
Al igual que en cualquier lenguaje de programacin, en C# un identificador no es ms posibles conflictos futuros no se recomienda dar a nuestros identificadores nombres que
que, como su propio nombre indica, un nombre con el que identificaremos algn contengan dicha secuencia de smbolos.
elemento de nuestro cdigo, ya sea una clase, una variable, un mtodo, etc.
Aunque directamente no podemos dar estos nombres a nuestros identificadores, C#
Tpicamente el nombre de un identificador ser una secuencia de cualquier nmero de proporciona un mecanismo para hacerlo indirectamente y de una forma mucho ms
caracteres alfanumricos incluidas vocales acentuadas y ees- tales que el primero de legible que usando secuencias de escape. Este mecanismo consiste en usar el carcter @
ellos no sea un nmero. Por ejemplo, identificadores vlidos seran: Arriba, caa, C3P0, para prefijar el nombre coincidente con el de una palabra reservada que queramos dar a
, etc; pero no lo seran 3com, 127, etc.
nuestra variable. Por ejemplo, el siguiente cdigo es vlido:

class @class
Sin embargo, y aunque por motivos de legibilidad del cdigo no se recomienda, C# {
tambin permite incluir dentro de un identificador caracteres especiales imprimibles static void @static(bool @bool)
tales como smbolos de diresis, subrayados, etc. siempre y cuando estos no tengan un {
significado especial dentro del lenguaje. Por ejemplo, tambin seran identificadores if (@bool)
vlidos, _barco_, ck y AB; pero no C# (# indica inicio de directiva de preprocesado) o Console.WriteLine("cierto");
else
a!b (! indica operacin lgica not) Console.WriteLine("falso");
}
Finalmente, C# da la posibilidad de poder escribir identificadores que incluyan }
caracteres Unicode que no se puedan imprimir usando el teclado de la mquina del
programador o que no sean directamente vlidos debido a que tengan un significado Lo que se ha hecho en el cdigo anterior ha sido usar @ para declarar una clase de
especial en el lenguaje. Para ello, lo que permite es escribir estos caracteres usando nombre class con un mtodo de nombre static que toma un parmetro de nombre bool,
secuencias de escape, que no son ms que secuencias de caracteres con las sintaxis: an cuando todos estos nombres son palabras reservadas en C#.

\u<dgito><dgito><dgito><dgito> Hay que precisar que aunque el nombre que nosotros escribamos sea por ejemplo
\U<dgito><dgito><dgito><dgito><dgito><dgito><dgito><dgito> @class, el nombre con el que el compilador va a tratar internamente al identificador es
solamente class. De hecho, si desde cdigo escrito en otro lenguaje adaptado a .NET
Estos dgitos indican es el cdigo Unicode del carcter que se desea incluir como parte distinto a C# hacemos referencia a ste identificador y en ese lenguaje su nombre no es
del identificador, y cada uno de ellos ha de ser un dgito hexadecimal vlido. (0-9, a-f una palabra reservada, el nombre con el que deberemos referenciarlo es class, y no
A-F) Hay que sealar que el carcter u ha de escribise en minscula cuando se @class (si tambin fuese en ese lenguaje palabra reservada habra que referenciarlo
indiquen caracteres Unicode con 4 dgitos y en mayscula cuando se indiquen con con el mecanismo que el lenguaje incluyese para ello, que quizs tambin podra
caracteres de ocho. Ejemplos de identificadores vlidos son C\u0064 (equivale a C#, consistir en usar @ como en C#)
pues 64 es el cdigo de # en Unicode) a\U00000033b (equivale a a!b)
En realidad, el uso de @ no se tiene porqu limitar a preceder palabras reservadas en
C#, sino que podemos preceder cualquier nombre con l. Sin embargo, hacer esto no se
Palabras reservadas recomienda, pues es considerado como un mal hbito de programacin y puede
provocar errores muy sutiles como el que muestra el siguiente ejemplo:
Aunque antes se han dado una serie de restricciones sobre cules son los nombres
vlidos que se pueden dar en C# a los identificadores, falta todava por dar una: los class A
{
siguientes nombres no son vlidos como identificadores ya que tienen un significado
int a; // (1)
especial en el lenguaje: int @a; // (2)

abstract, as, base, bool, break, byte, case, catch, char, checked, class, const, public static void Main()
continue, decimal, default, delegate, do, double, else, enum, event, explicit, extern, {}
false, finally, fixed, float, for, foreach, goto, if, implicit, in, int, interface, internal, lock, }
is, long, namespace, new, null, object, operator, out, override, params, private,
protected, public, readonly, ref, return, sbyte, sealed, short, sizeof, stackalloc, static,
Si intentamos compilar este cdigo se producir un error que nos informar de que el
string, struct, switch, this, throw, true, try, typeof, uint, ulong, unchecked, unsafe, ushort,
using, virtual, void, while campo de nombre a ha sido declarado mltiples veces en la clase A. Esto se debe a que

Jos Antonio Gonzlez Seco Pgina 45 Jos Antonio Gonzlez Seco Pgina 46
El lenguaje de programacin C# Tema 4: Aspectos lxicos El lenguaje de programacin C# Tema 4: Aspectos lxicos

como @ no forma parte en realidad del nombre del identificador al que precede, las Comilla doble \u0022 \
declaraciones marcadas con comentarios como (1) y (2) son equivalentes. Carcter nulo \u0000 \0
Alarma \u0007 \a
Hay que sealar por ltimo una cosa respecto al carcter @: slo puede preceder al Retroceso \u0008 \b
nombre de un identificador, pero no puede estar contenido dentro del mismo. Es decir, Salto de pgina \u000C \f
identificadores como i5322@fie.us.es no son vlidos. Nueva lnea \u000A \n
Retorno de carro \u000D \r
Tabulacin horizontal \u0009 \t
Literales Tabulacin vertical \u000B \v
Barra invertida \u005C \\
Un literal es la representacin explcita de los valores que pueden tomar los tipos Tabla 4.1: Cdigos de escape especiales
bsicos del lenguaje. A continuacin se explica cul es la sintaxis con que se escriben
los literales en C# desglosndolos segn el tipo de valores que representan: En realidad, de la tabla anterior hay que matizar que el carcter de comilla doble
tambin puede aparecer dentro de un literal de cadena directamente, sin
Literales enteros: Un nmero entero se puede representar en C# tanto en necesidad de usar secuencias de escape. Por tanto, otros ejemplos de literales de
formato decimal como hexadecimal. En el primer caso basta escribir los dgitos carcter vlidos sern '\', '', '\f', '\u0000', '\\', '\'', etc.
decimales (0-9) del nmero unos tras otros, mientras que en el segundo hay que
preceder los dgitos hexadecimales (0-9, a-f, A-F) con el prefijo 0x. En ambos Aparte de para representar los caracteres de la tabla anterior, tambin es posible
casos es posible preceder el nmero de los operadores + para indicar si es usar los cdigos de escape Unicode para representar cualquier cdigo Unicode,
positivo o negativo, aunque si no se pone nada se considerar que es positivo. lo que suele usarse para representar literales de caracteres no incluidos en los
Ejemplos de literales enteros son 0, 5, +15, -23, 0x1A, -0x1a, etc teclados estndares.

En realidad, la sintaxis completa para la escritura de literales enteros tambin Junto al formato de representacin de cdigos de escape Unicode ya visto, C#
puede incluir un sufijo que indique el tipo de dato entero al que ha de pertenecer incluye un formato abreviado para representar estos cdigos en los literales de
el literal. Esto no lo veremos hasta el Tema 7: Variables y tipos de datos. carcter si necesidad de escribir siempre cuatro dgitos an cuando el cdigo a
representar tenga muchos ceros en su parte izquierda. Este formato consiste en
Literales reales: Los nmeros reales se escriben de forma similar a los enteros, preceder el cdigo de \x en vez de \u. De este modo, los literales de carcter
\U00000008, '\u0008', '\x0008', '\x008', '\x08' y '\x8' son todos equivalentes. Hay que
aunque slo se pueden escribir en forma decimal y para separar la parte entera
de la real usan el tradicional punto decimal (carcter .) Tambin es posible tener en cuenta que este formato abreviado slo es vlido en los literales de
representar los reales en formato cientfico, usndose para indicar el exponente carcter, y no a la hora de dar nombres a los identificadores.
los caracteres e E. Ejemplos de literales reales son 0.0, 5.1, -5.1, +15.21,
3.02e10, 2.02e-2, 98.8E+1, etc. Literales de cadena: Una cadena no es ms que una secuencia de caracteres
encerrados entre comillas dobles. Por ejemplo Hola, mundo, camin, etc. El
Al igual que ocurra con los literales enteros, los literales reales tambin pueden texto contenido dentro estos literales puede estar formado por cualquier nmero
incluir sufijos que indiquen el tipo de dato real al que pertenecen, aunque de literales de carcter concatenados y sin las comillas simples, aunque si
nuevamente no los veremos hasta el Tema 7: Variables y tipos de datos incluye comillas dobles stas han de escribirse usando secuencias de escape
porque si no el compilador las interpretara como el final de la cadena.
Literales lgicos: Los nicos literales lgicos vlidos son true y false, que
respectivamente representan los valores lgicos cierto y falso. Aparte del formato de escritura de literales de cadenas antes comentado, que es
el comnmente utilizado en la mayora de lenguajes de programacin, C#
Literales de carcter: Prcticamente cualquier carcter se puede representar tambin admite uno nuevo consistente en precederlos de un smbolo @, caso en
encerrndolo entre comillas simples. Por ejemplo, 'a' (letra a), ' ' (carcter de que todo el contenido de la cadena sera interpretado tal cual, sin considerar la
espacio), '?' (smbolo de interrogacin), etc. Las nicas excepciones a esto son existencia de secuencias de escape. A este tipo de literales se les conoce como
los caracteres que se muestran en la Tabla 4.1, que han de representarse con literales de cadena planos o literales verbatim y pueden incluso ocupar varias
secuencias de escape que indiquen su valor como cdigo Unicode o mediante un lneas. La siguiente tabla recoge algunos ejemplos de cmo se interpretan:
formato especial tal y como se indica a continuacin:
Literal de cadena Interpretado como...
Hola\tMundo Hola Mundo
Carcter Cdigo de Cdigo de
@Hola\tMundo Hola\tMundo
escape Unicode escape especial
\u0027 \' @Hola Hola
Comilla simple

Jos Antonio Gonzlez Seco Pgina 47 Jos Antonio Gonzlez Seco Pgina 48
El lenguaje de programacin C# Tema 4: Aspectos lxicos El lenguaje de programacin C# Tema 4: Aspectos lxicos

Mundo Mundo Operaciones lgicas: Se incluyen operadores que permiten realizar las
@Hola Mundo Hola Mundo operaciones lgicas tpicas: and (&& y &), or (|| y |), not (!) y xor (^)
Tabla 4.2: Ejemplos de literales de cadena planos
Los operadores && y || se diferencia de & y | en que los primeros realizan
El ltimo ejemplo de la tabla se ha aprovechado para mostrar que si dentro de un evaluacin perezosa y los segundos no. La evaluacin perezosa consiste en que
literal de cadena plano se desea incluir caracteres de comilla doble sin que sean si el resultado de evaluar el primer operando permite deducir el resultado de la
confundidos con el final de la cadena basta duplicarlos. operacin, entonces no se evala el segundo y se devuelve dicho resultado
directamente, mientras que la evaluacin no perezosa consiste en evaluar
Literal nulo: El literal nulo es un valor especial que se representa en C# con la siempre ambos operandos. Es decir, si el primer operando de una operacin &&
palabra reservada null y se usa como valor de las variables de objeto no es falso se devuelve false directamente, sin evaluar el segundo; y si el primer
inicializadas para as indicar que contienen referencias nulas. operando de una || es cierto se devuelve true directamente, sin evaluar el otro.

Operaciones relacionales: Se han incluido los tradicionales operadores de


Operadores igualdad (==), desigualdad (!=), mayor que (>), menor que (<), mayor o
igual que (>=) y menor o igual que (<=)
Un operador en C# es un smbolo formado por uno o ms caracteres que permite
realizar una determinada operacin entre uno o ms datos y produce un resultado. Operaciones de manipulacin de bits: Se han incluido operadores que
permiten realizar a nivel de bits operaciones and (&), or (|), not (~), xor
A continuacin se describen cules son los operadores incluidos en el lenguaje (^), desplazamiento a izquierda (<<) y desplazamiento a derecha (>>) El
clasificados segn el tipo de operaciones que permiten realizar, aunque hay que tener en operador << desplaza a izquierda rellenando con ceros, mientras que el tipo de
cuenta que C# permite la redefinicin del significado de la mayora de los operadores relleno realizado por >> depende del tipo de dato sobre el que se aplica: si es un
segn el tipo de dato sobre el que se apliquen, por lo que lo que aqu se cuenta se dato con signo mantiene el signo, y en caso contrario rellena con ceros.
corresponde con los usos ms comunes de los mismos:
Operaciones de asignacin: Para realizar asignaciones se usa en C# el operador
Operaciones aritmticas: Los operadores aritmticos incluidos en C# son los =, operador que adems de realizar la asignacin que se le solicita devuelve el
tpicos de suma (+), resta (-), producto (*), divisin (/) y mdulo (%) Tambin se valor asignado. Por ejemplo, la expresin a = b asigna a la variable a el valor de
incluyen operadores de menos unario () y ms unario (+) la variable b y devuelve dicho valor, mientras que la expresin c = a = b asigna a
las variables c y a el valor de b (el operador = es asociativo por la derecha)
Relacionados con las operaciones aritmticas se encuentran un par de operadores
llamados checked y unchecked que permiten controlar si se desea detectar los Tambin se han incluido operadores de asignacin compuestos que permiten
desbordamientos que puedan producirse si al realizar este tipo de operaciones el ahorrar tecleo a la hora de realizar asignaciones tan comunes como:
resultado es superior a la capacidad del tipo de datos de sus operandos. Estos
operadores se usan as: temperatura = temperatura + 15; // Sin usar asignacin compuesta
temperatura += 15; // Usando asignacin compuesta
checked (<expresinAritmtica>)
unchecked(<expresinAritmtica>) Las dos lneas anteriores son equivalentes, pues el operador compuesto += asigna
a su primer operando el valor que tena ms el de su segundo operando (o sea, le
Ambos operadores calculan el resultado de <expresinAritmtica> y lo devuelven suma el segundo operando) Como se ve, permite compactar bastante el cdigo.
si durante el clculo no se produce ningn desbordamiento. Sin embargo, en
caso de que haya desbordamiento cada uno acta de una forma distinta: checked Aparte del operador de asignacin compuesto +=, tambin se ofrecen operadores
provoca un error de compilacin si <expresinAritmtica> es una expresin de asignacin compuestos para la mayora de los operadores binarios ya vistos.
constante y una excepcin System.OverflowException si no lo es, mientras que Estos son: +=, -=, *=, /=, %=, &=, |=, ^=, <<= y >>=. Ntese que no hay versiones
unchecked devuelve el resultado de la expresin aritmtica truncado para que
compuestas para los operadores binarios && y ||.
quepa en el tamao esperado.
Otros dos operadores de asignacin incluidos son los de incremento(++) y
Por defecto, en ausencia de los operadores checked y unchecked lo que se hace decremento (--) Estos operadores permiten, respectivamente, aumentar y
es evaluar las operaciones aritmticas entre datos constantes como si se les disminuir en una unidad el valor de la variable sobre el que se aplican. As, estas
aplicase checked y las operaciones entre datos no constantes como si se les lneas de cdigo son equivalentes:
hubiese aplicado unchecked.
temperatura = temperatura + 1; // Sin usar asignacin compuesta ni incremento
temperatura += 1; // Usando asignacin compuesta

Jos Antonio Gonzlez Seco Pgina 49 Jos Antonio Gonzlez Seco Pgina 50
El lenguaje de programacin C# Tema 4: Aspectos lxicos El lenguaje de programacin C# Tema 4: Aspectos lxicos

temperatura++; // Usando incremento


Hay que tener en cuenta que este operador es asociativo por la derecha, por lo
Si el operador ++ se coloca tras el nombre de la variable (como en el ejemplo) que una expresin como a?b:c?d:e es equivalente a a?b:(c?d:e)
devuelve el valor de la variable antes de incrementarla, mientras que si se coloca
antes, devuelve el valor de sta tras incrementarla; y lo mismo ocurre con el No hay que confundir este operador con la instruccin condicional if que se
operador --. Por ejemplo: tratar en el Tema 8:Instrucciones, pues aunque su utilidad es similar al de sta,
? devuelve un valor e if no.
c = b++; // Se asigna a c el valor de b y luego se incrementa b
c = ++b; // Se incrementa el valor de b y luego se asigna a c Operaciones de delegados: Un delegado es un objeto que puede almacenar en
referencias a uno o ms mtodos y a travs del cual es posible llamar a estos
La ventaja de usar los operadores ++ y -- es que en muchas mquinas son ms mtodos. Para aadir objetos a un delegado se usan los operadores + y +=,
eficientes que el resto de formas de realizar sumas o restas de una unidad, pues mientras que para quitrselos se usan los operadores y -=. Estos conceptos se
el compilador puede traducirlos en una nica instruccin en cdigo mquina5. estudiarn detalladamente en el Tema 13: Eventos y delegados
Operaciones con cadenas: Para realizar operaciones de concatenacin de Operaciones de acceso a objetos: Para acceder a los miembros de un objeto se
cadenas se puede usar el mismo operador que para realizar sumas, ya que en C# usa el operador ., cuya sintaxis es:
se ha redefinido su significado para que cuando se aplique entre operandos que
sean cadenas o que sean una cadena y un carcter lo que haga sea concatenarlos. <objeto>.<miembro>
Por ejemplo, Hola+ mundo devuelve Hola mundo, y Hola mund + o tambin.
Si a es un objeto, ejemplos de cmo llamar a diferentes miembros suyos son:
Operaciones de acceso a tablas: Una tabla es un conjunto de ordenado de
objetos de tamao fijo. Para acceder a cualquier elemento de este conjunto se a.b = 2; // Asignamos a su propiedad a el valor 2
aplica el operador postfijo [] sobre la tabla para indicar entre corchetes la a.f(); // Llamamos a su mtodo f()
posicin que ocupa el objeto al que se desea acceder dentro del conjunto. Es a.g(2); // Llamamos a su mtodo g() pasndole como parmetro el valor entero 2
decir, este operador se usa as:
No se preocupe si no conoce los conceptos de mtodos, propiedades, eventos y
[<posicinElemento>]
delegados en los que se basa este ejemplo, pues se explican detalladamente en
temas posteriores.
Un ejemplo de su uso en el que se asigna al elemento que ocupa la posicin 3 en
una tabla de nombre tablaPrueba el valor del elemento que ocupa la posicin 18 Operaciones con punteros: Un puntero es una variable que almacena una
de dicha tabla es el siguiente: referencia a una direccin de memoria. Para obtener la direccin de memoria de
un objeto se usa el operador &, para acceder al contenido de la direccin de
tablaPrueba[3] = tablaPrueba[18]; memoria almacenada en un puntero se usa el operador *, para acceder a un
miembro de un objeto cuya direccin se almacena en un puntero se usa ->, y para
Las tablas se estudian detenidamente en el Tema 7: Variables y tipos de datos referenciar una direccin de memoria de forma relativa a un puntero se le aplica
el operador [] de la forma puntero[desplazamiento]. Todos estos conceptos se
Operador condicional: Es el nico operador incluido en C# que toma 3 explicarn ms a fondo en el Tema 18: Cdigo inseguro.
operandos, y se usa as:
Operaciones de obtencin de informacin sobre tipos: De todos los
<condicin> ? <expresin1> : <expresin2> operadores que nos permiten obtener informacin sobre tipos de datos el ms
importante es typeof, cuya forma de uso es:
El significado del operando es el siguiente: se evala <condicin> Si es cierta se
devuelve el resultado de evaluar <expresin1>, y si es falsa se devuelve el typeof(<nombreTipo>)
resultado de evaluar <condicin2>. Un ejemplo de su uso es:
Este operador devuelve un objeto de tipo System.Type con informacin sobre el
b = (a>0)? a : 0; // Suponemos a y b de tipos enteros tipo de nombre <nombreTipo> que podremos consultar a travs de los miembros
ofrecidos por dicho objeto. Esta informacin incluye detalles tales como cules
En este ejemplo, si el valor de la variable a es superior a 0 se asignar a b el son sus miembros, cul es su tipo padre o a qu espacio de nombres pertenece.
valor de a, mientras que en caso contrario el valor que se le asignar ser 0.
Si lo que queremos es determinar si una determinada expresin es de un tipo u
otro, entonces el operador a usar es is, cuya sintaxis es la siguiente:
5
Generalmente, en estas mquinas ++ se convierte en una instruccin INC y -- en una instruccin DEC

Jos Antonio Gonzlez Seco Pgina 51 Jos Antonio Gonzlez Seco Pgina 52
El lenguaje de programacin C# Tema 4: Aspectos lxicos El lenguaje de programacin C# Tema 4: Aspectos lxicos

<expresin> is <nombreTipo> stackalloc <nombreTipo>[<nElementos>]

El significado de este operador es el siguiente: se evala <expresin>. Si el Este operador lo que hace es crear en pila una tabla de tantos elementos de tipo
resultado de sta es del tipo cuyo nombre se indica en <nombreTipo> se devuelve <nombreTipo> como indique <nElementos> y devolver la direccin de memoria en
true; y si no, se devuelve false. Como se ver en el Tema 5: Clases, este que sta ha sido creada. Por ejemplo:
operador suele usarse en mtodos polimrficos.
int * p = stackalloc[100]; // p apunta a una tabla de 100 enteros.
Finalmente, C# incorpora un tercer operador que permite obtener informacin
sobre un tipo de dato: sizeof Este operador permite obtener el nmero de bytes stackalloc slo puede usarse para inicializar punteros a objetos de tipos valor
que ocuparn en memoria los objetos de un tipo, y se usa as: declarados como variables locales.

sizeof(<nombreTipo>) Operaciones de conversin: Para convertir unos objetos en otros se utiliza el


operador de conversin, que no consiste ms que en preceder la expresin a
sizeof slo puede usarse dentro de cdigo inseguro, que por ahora basta convertir del nombre entre parntesis del tipo al que se desea convertir el
considerar que son zonas de cdigo donde es posible usar punteros. No ser resultado de evaluarla. Por ejemplo, si l es una variable de tipo long y se desea
hasta el Tema 18: Cdigo inseguro cuando lo trataremos en profundidad. almacenar su valor dentro de una variable de tipo int llamada i, habra que
convertir previamente su valor a tipo int as:
Adems, sizeof slo se puede aplicar sobre nombres de tipos de datos cuyos
objetos se puedan almacenar directamente en pila. Es decir, que sean estructuras i = (int) l; // Asignamos a i el resultado de convertir el valor de l a tipo int
(se vern en el Tema 13) o tipos enumerados (se vern en el Tema 14)
Los tipos int y long estn predefinidos en C# y permite almacenar valores
Operaciones de creacin de objetos: El operador ms tpicamente usado para enteros con signo. La capacidad de int es de 32 bits, mientras que la de long es
crear objetos es new, que se usa as: de 64 bits. Por tanto, a no ser que hagamos uso del operador de conversin, el
compilador no nos dejar hacer la asignacin, ya que al ser mayor la capacidad
new <nombreTipo>(<parametros>) de los long, no todo valor que se pueda almacenar en un long tiene porqu
poderse almacenar en un int. Es decir, no es vlido:
Este operador crea un objeto de <nombreTipo> pasndole a su mtodo constructor
los parmetros indicados en <parmetros> y devuelve una referencia al mismo. i = l; //ERROR: El valor de l no tiene porqu caber en i
En funcin del tipo y nmero de estos parmetros se llamar a uno u otro de los
constructores del objeto. As, suponiendo que a1 y a2 sean variables de tipo Esta restriccin en la asignacin la impone el compilador debido a que sin ella
Avin, ejemplos de uso del operador new son: podran producirse errores muy difciles de detectar ante truncamientos no
esperados debido al que el valor de la variable fuente es superior a la capacidad
Avin a1 = new Avin(); // Se llama al constructor sin parmetros de Avin de la variable destino.
Avin a2 = new Avin(Caza); // Se llama al constructor de Avin que toma
// como parmetro una cadena Existe otro operador que permite realizar operaciones de conversin de forma
muy similar al ya visto. ste es el operador as, que se usa as:
En caso de que el tipo del que se haya solicitado la creacin del objeto sea una
clase, ste se crear en memoria dinmica, y lo que new devolver ser una <expresin> as <tipoDestino>
referencia a la direccin de pila donde se almacena una referencia a la direccin
del objeto en memoria dinmica. Sin embargo, si el objeto a crear pertenece a Lo que hace es devolver el resultado de convertir el resultado de evaluar
una estructura o a un tipo enumerado, entonces ste se crear directamente en la <expresin> al tipo indicado en <tipoDestino> Por ejemplo, para almacenar en una
pila y la referencia devuelta por el new se referir directamente al objeto creado. variable p el resultado de convertir un objeto t a tipo tipo Persona se hara:
Por estas razones, a las clases se les conoce como tipos referencia ya que de sus
objetos en pila slo se almacena una referencia a la direccin de memoria p = t as Persona;
dinmica donde verdaderamente se encuentran; mientras que a las estructuras y
tipos enumerados se les conoce como tipos valor ya sus objetos se almacenan Las nicas diferencias entre usar uno u otro operador de conversin son:
directamente en pila.
as slo es aplicable a tipos referencia y slo a aquellos casos en que
C# proporciona otro operador que tambin nos permite crear objetos. ste es existan conversiones predefinidas en el lenguaje. Como se ver ms
stackalloc, y se usa as:

Jos Antonio Gonzlez Seco Pgina 53 Jos Antonio Gonzlez Seco Pgina 54
El lenguaje de programacin C# Tema 4: Aspectos lxicos El lenguaje de programacin C# Tema 5: Clases

adelante, esto slo incluye conversiones entre un tipo y tipos padres


suyos y entre un tipo y tipos hijos suyos. TEMA 5: Clases
Una consecuencia de esto es que el programador puede definir cmo
hacer conversiones de tipos por l definidos y otros mediante el operador Definicin de clases
(), pero no mediante as.Esto se debe a que as nicamente indica que se
desea que una referencia a un objeto en memoria dinmica se trate como
si el objeto fuese de otro tipo, pero no implica conversin ninguna. Sin Conceptos de clase y objeto
embargo, () s que implica conversin si el <tipoDestino> no es compatible
con el tipo del objeto referenciado. Obviamente, el operador se aplicar C# es un lenguaje orientado a objetos puro6, lo que significa que todo con lo que vamos
mucho ms rpido en los casos donde no sea necesario convertir. a trabajar en este lenguaje son objetos. Un objeto es un agregado de datos y de mtodos
que permiten manipular dichos datos, y un programa en C# no es ms que un conjunto
En caso de que se solicite hacer una conversin invlida as devuelve null de objetos que interaccionan unos con otros a travs de sus mtodos.
mientras que () produce una excepcin System.InvalidCastException.
Una clase es la definicin de las caractersticas concretas de un determinado tipo de
objetos. Es decir, de cules son los datos y los mtodos de los que van a disponer todos
los objetos de ese tipo. Por esta razn, se suele decir que el tipo de dato de un objeto es
la clase que define las caractersticas del mismo7.

Sintaxis de definicin de clases

La sintaxis bsica para definir una clase es la que a continuacin se muestra:

class <nombreClase>
{
<miembros>
}

De este modo se definira una clase de nombre <nombreClase> cuyos miembros son los
definidos en <miembros>. Los miembros de una clase son los datos y mtodos de los
que van a disponer todos los objetos de la misma. Un ejemplo de cmo declarar una
clase de nombre A que no tenga ningn miembro es la siguiente:

class A
{}

Una clase as declarada no dispondr de ningn miembro a excepcin de los


implcitamente definidos de manera comn para todos los objetos que creemos en C#.
Estos miembros los veremos dentro de poco en este mismo tema bajo el epgrafe La
clase primegina: System.Object.

Aunque en C# hay muchos tipos de miembros distintos, por ahora vamos a considerar
que stos nicamente pueden ser campos o mtodos y vamos a hablar un poco acerca de
ellos y de cmo se definen:

6
Esta afirmacin no es del todo cierta, pues como veremos ms adelante hay elementos del lenguaje que
no estn asociados a ningn objeto en concreto. Sin embargo, para simplificar podemos considerarlo por
ahora como tal.
7
En realidad hay otras formas de definir las caractersticas de un tipo de objetos, como son las estructuras
y las enumeraciones. Por tanto, el tipo de dato de un objeto no tiene porqu ser una clase, aunque a
efectos de simplificacin por ahora consideraremos que siempre lo es.

Jos Antonio Gonzlez Seco Pgina 55 Jos Antonio Gonzlez Seco Pgina 56
El lenguaje de programacin C# Tema 5: Clases El lenguaje de programacin C# Tema 5: Clases

dicho nombre en vez de tener que escribirlas. Dentro de estas instrucciones es


Campos: Un campo es un dato comn a todos los objetos de una determinada posible acceder con total libertad a la informacin almacenada en los campos
clase. Para definir cules son los campos de los que una clase dispone se usa la pertenecientes a la clase dentro de la que el mtodo se ha definido, por lo que
siguiente sintaxis dentro de la zona sealada como <miembros> en la definicin como al principio del tema se indic, los mtodos permiten manipular los datos
de la misma: almacenados en los objetos.

<tipoCampo> <nombreCampo>; La sintaxis que se usa en C# para definir los mtodos es la siguiente:

El nombre que demos al campo puede ser cualquier identificador que queramos <tipoDevuelto> <nombreMtodo> (<parametros>)
siempre y cuando siga las reglas descritas en el Tema 4: Aspectos Lxicos para la {
escritura de identificadores y no coincida con el nombre de ningn otro miembro <instrucciones>
}
previamente definido en la definicin de clase.
Todo mtodo puede devolver un objeto como resultado de la ejecucin de las
Los campos de un objeto son a su vez objetos, y en <tipoCampo> hemos de
instrucciones que lo forman, y el tipo de dato al que pertenece este objeto es lo
indicar cul es el tipo de dato del objeto que vamos a crear. ste tipo puede
que se indica en <tipoDevuelto>. Si no devuelve nada se indica void, y si devuelve
corresponderse con cualquiera que los predefinidos en la BCL o con cualquier
algo es obligatorio finalizar la ejecucin de sus instrucciones con alguna
otro que nosotros hallamos definido siguiendo la sintaxis arriba mostrada. A
instruccin return <objeto>; que indique qu objeto ha de devolverse.
continuacin se muestra un ejemplo de definicin de una clase de nombre
Persona que dispone de tres campos:
Opcionalmente todo mtodo puede recibir en cada llamada una lista de objetos a
class Persona
los que podr acceder durante la ejecucin de sus instrucciones. En <parametros>
{ se indica es cules son los tipos de dato de estos objetos y cul es el nombre con
string Nombre; // Campo de cada objeto Persona que almacena su nombre el que harn referencia las instrucciones del mtodo a cada uno de ellos. Aunque
int Edad; // Campo de cada objeto Persona que almacena su edad los objetos que puede recibir el mtodo pueden ser diferentes cada vez que se
string NIF; // Campo de cada objeto Persona que almacena su NIF solicite su ejecucin, siempre han de ser de los mismos tipos y han de seguir el
}
orden establecido en <parametros>.
Segn esta definicin, todos los objetos de clase Persona incorporarn campos
Un ejemplo de cmo declarar un mtodo de nombre Cumpleaos es la siguiente
que almacenarn cul es el nombre de la persona que cada objeto representa,
modificacin de la definicin de la clase Persona usada antes como ejemplo:
cul es su edad y cul es su NIF. El tipo int incluido en la definicin del campo
Edad es un tipo predefinido en la BCL cuyos objetos son capaces de almacenar class Persona
nmeros enteros con signo comprendidos entre -2.147.483.648 y 2.147.483.647 {
(32 bits), mientras que string es un tipo predefinido que permite almacenar string Nombre; // Campo de cada objeto Persona que almacena su nombre
cadenas de texto que sigan el formato de los literales de cadena visto en el Tema int Edad; // Campo de cada objeto Persona que almacena su edad
4: Aspectos Lxicos string NIF; // Campo de cada objeto Persona que almacena su NIF

void Cumpleaos() // Incrementa en uno de la edad del objeto Persona


Para acceder a un campo de un determinado objeto se usa la sintaxis: {
Edad++;
<objeto>.<campo> }
}
Por ejemplo, para acceder al campo Edad de un objeto Persona llamado p y
cambiar su valor por 20 se hara: La sintaxis usada para llamar a los mtodos de un objeto es la misma que la
usada para llamar a sus campos, slo que ahora tras el nombre del mtodo al que
p.Edad = 20; se desea llamar hay que indicar entre parntesis cules son los valores que se
desea dar a los parmetros del mtodo al hacer la llamada. O sea, se escribe:
En realidad lo marcado como <objeto> no tiene porqu ser necesariamente el
nombre de algn objeto, sino que puede ser cualquier expresin que produzca <objeto>.<mtodo>(<parmetros>)
como resultado una referencia no nula a un objeto (si produjese null se lanzara
una excepcin del tipo predefinido System.NullPointerException) Como es lgico, si el mtodo no tomase parmetros se dejaran vacos los
parmetros en la llamada al mismo. Por ejemplo, para llamar al mtodo
Mtodos: Un mtodo es un conjunto de instrucciones a las que se les asocia un Cumpleaos() de un objeto Persona llamado p se hara:
nombre de modo que si se desea ejecutarlas basta referenciarlas a travs de

Jos Antonio Gonzlez Seco Pgina 57 Jos Antonio Gonzlez Seco Pgina 58
El lenguaje de programacin C# Tema 5: Clases El lenguaje de programacin C# Tema 5: Clases

p.Cumpleaos(); // El mtodo no toma parmetros, luego no le pasamos ninguno de memoria donde se cree el objeto y los constructores slo pueden usarse como
operandos de new, no tiene sentido que un constructor devuelva objetos, por lo que no
Es importante sealar que en una misma clase pueden definirse varios mtodos tiene sentido incluir en su definicin un campo <tipoDevuelto> y el compilador considera
con el mismo nombre siempre y cuando tomen diferente nmero o tipo de errneo hacerlo (aunque se indique void)
parmetros. A esto se le conoce como sobrecarga de mtodos, y es posible ya
que el compilador sabr a cual llamar a partir de los <parmetros> especificados. El constructor recibe ese nombre debido a que su cdigo suele usarse precisamente para
construir el objeto, para inicializar sus miembros. Por ejemplo, a nuestra clase de
Sin embargo, lo que no se permite es definir varios mtodos que nicamente se ejemplo Persona le podramos aadir un constructor dejndola as:
diferencien en su valor de retorno, ya que como ste no se tiene porqu indicar al
llamarlos no podra diferenciarse a que mtodo en concreto se hace referencia en class Persona
cada llamada. Por ejemplo, a partir de la llamada: {
string Nombre; // Campo de cada objeto Persona que almacena su nombre
p.Cumpleaos(); int Edad; // Campo de cada objeto Persona que almacena su edad
string NIF; // Campo de cada objeto Persona que almacena su NIF
Si adems de la versin de Cumpleaos() que no retorna nada hubiese otra que
void Cumpleaos() // Incrementa en uno la edad del objeto Persona
retornase un int, cmo sabra entonces el compilador a cul llamar? {
Edad++;
Antes de continuar es preciso sealar que en C# todo, incluido los literales, son objetos }
del tipo de cada literal y por tanto pueden contar con miembros a los que se accedera
tal y como se ha explicado. Para entender esto no hay nada mejor que un ejemplo: Persona (string nombre, int edad, string nif) // Constructor
{
string s = 12.ToString(); Nombre = nombre;
Edad = edad;
NIF = nif;
Este cdigo almacena el literal de cadena 12 en la variable s, pues 12 es un objeto de }
tipo int (tipo que representa enteros) y cuenta cuenta con el mtodo comn a todos los }
ints llamado ToString() que lo que hace es devolver una cadena cuyos caracteres son los
dgitos que forman el entero representado por el int sobre el que se aplica; y como la Como se ve en el cdigo, el constructor toma como parmetros los valores con los que
variable s es de tipo string (tipo que representa cadenas) es perfectamente posible deseemos inicializar el objeto a crear. Gracias a l, podemos crear un objeto Persona de
almacenar dicha cadena en ella, que es lo que se hace en el cdigo anterior. nombre Jos, de 22 aos de edad y NIF 12344321-A as:

new Persona(Jos, 22, 12344321-A)


Creacin de objetos
Ntese que la forma en que se pasan parmetros al constructor consiste en indicar los
Operador new valores que se ha de dar a cada uno de los parmetros indicados en la definicin del
mismo separndolos por comas. Obviamente, si un parmetro se defini como de tipo
string habr que pasarle una cadena, si se defini de tipo int habr que pasarle un entero
Ahora que ya sabemos cmo definir las clases de objetos que podremos usar en nuestras
aplicaciones ha llegado el momento de explicar cmo crear objetos de una determinada y, en general, a todo parmetro habr que pasarle un valor de su mismo tipo (o de
clase. Algo de ello ya se introdujo en el Tema 4: Aspectos Lxicos cuando se coment la alguno convertible al mismo), producindose un error al compilar si no se hace as.
utilidad del operador new, que precisamente es crear objetos y cuya sintaxis es:
En realidad un objeto puede tener mltiples constructores, aunque para diferenciar a
new <nombreTipo>(<parametros>) unos de otros es obligatorio que se diferencien en el nmero u orden de los parmetros
que aceptan, ya que el nombre de todos ellos ha de coincidir con el nombre de la clase
Este operador crea un nuevo objeto del tipo cuyo nombre se le indica y llama durante su de la que son miembros. De ese modo, cuando creemos el objeto el compilador podr
proceso de creacin al constructor del mismo apropiado segn los valores que se le inteligentemente determinar cul de los constructores ha de ejecutarse en funcin de los
pasen en <parametros>, devolviendo una referencia al objeto recin creado. Hay que valores que le pasemos al new.
resaltar el hecho de que new no devuelve el propio objeto creado, sino una referencia a
la direccin de memoria dinmica donde en realidad se ha creado. Una vez creado un objeto lo ms normal es almacenar la direccin devuelta por new en
una variable del tipo apropiado para el objeto creado. El siguiente ejemplo -que como es
El antes comentado constructor de un objeto no es ms que un mtodo definido en la lgico ir dentro de la definicin de algn mtodo- muestra cmo crear una variable de
definicin de su tipo que tiene el mismo nombre que la clase a la que pertenece el objeto tipo Persona llamada p y cmo almacenar en ella la direccin del objeto que devolvera
y no tiene valor de retorno. Como new siempre devuelve una referencia a la direccin la anterior aplicacin del operador new:

Jos Antonio Gonzlez Seco Pgina 59 Jos Antonio Gonzlez Seco Pgina 60
El lenguaje de programacin C# Tema 5: Clases El lenguaje de programacin C# Tema 5: Clases

Dentro del cdigo de cualquier mtodo de un objeto siempre es posible hacer referencia
Persona p; // Creamos variable p al propio objeto usando la palabra reservada this. Esto puede venir bien a la hora de
p = new Persona(Jose, 22, 12344321-A); // Almacenamos en p el objeto creado con new escribir constructores de objetos debido a que permite que los nombres que demos a los
parmetros del constructor puedan coincidir nombres de los campos del objeto sin que
A partir de este momento la variable p contendr una referencia a un objeto de clase haya ningn problema. Por ejemplo, el constructor de la clase Persona escrito
Persona que representar a una persona llamada Jos de 22 aos y NIF 12344321-A. O
anteriormente se puede rescribir as usando this:
lo que prcticamente es lo mismo y suele ser la forma comnmente usada para decirlo:
la variable p representa a una persona llamada Jos de 22 aos y NIF 12344321-A. Persona (string Nombre, int Edad, string NIF)
{
Como lo ms normal suele ser crear variables donde almacenar referencias a objetos this.Nombre = Nombre;
que creemos, las instrucciones anteriores pueden compactarse en una sola as: this.Edad = Edad;
this.NIF = NIF;
}
Persona p = new Persona(Jos, 22, 12344321-A);

Es decir, dentro de un mtodo con parmetros cuyos nombres coincidan con campos, se
De hecho, una sintaxis ms general para la definicin de variables es la siguiente:
da preferencia a los parmetros y para hacer referencia a los campos hay que prefijarlos
<tipoDato> <nombreVariable> = <valorInicial>; con el this tal y como se muestra en el ejemplo.

La parte = <valorInicial> de esta sintaxis es en realidad opcional, y si no se incluye la El ejemplo anterior puede que no resulte muy interesante debido a que para evitar tener
variable declarada pasar a almacenar una referencia nula (contendr el literal null) que usar this podra haberse escrito el constructor tal y como se mostr en la primera
versin del mismo: dando nombres que empiecen en minscula a los parmetros y
nombres que empiecen con maysculas a los campos. De hecho, ese es el convenio que
Constructor por defecto Microsoft recomienda usar. Sin embargo, como ms adelante se ver s que puede ser
til this cuando los campos a inicializar a sean privados, ya que el convenio de
No es obligatorio definir un constructor para cada clase, y en caso de que no definamos escritura de identificadores para campos privados recomendado por Microsoft coincide
ninguno el compilador crear uno por nosotros sin parmetros ni instrucciones. Es decir, con el usado para dar identificadores a parmetros (obviamente otra solucin sera dar
como si se hubiese definido de esta forma: cualquier otro nombre a los parmetros del constructor o los campos afectados, aunque
as el cdigo perdera algo legibilidad)
<nombreTipo>()
{ Un uso ms frecuente de this en C# es el de permitir realizar llamadas a un mtodo de
} un objeto desde cdigo ubicado en mtodos del mismo objeto. Es decir, en C# siempre
es necesario que cuando llamemos a algn mtodo de un objeto precedamos al operador
Gracias a este constructor introducido automticamente por el compilador, si Coche es . de alguna expresin que indique cul es el objeto a cuyo mtodo se desea llamar, y si
una clase en cuya definicin no se ha incluido ningn constructor, siempre ser posible ste mtodo pertenece al mismo objeto que hace la llamada la nica forma de conseguir
crear uno nuevo usando el operador new as: indicarlo en C# es usando this.
Coche c = new Coche(); // Crea coche c llamando al constructor por defecto de Coche Finalmente, una tercera utilidad de this es permitir escribir mtodos que puedan
devolver como objeto el propio objeto sobre el que el mtodo es aplicado. Para ello
Hay que tener en cuenta una cosa: el constructor por defecto es slo incluido por el bastara usar una instruccin return this; al indicar el objeto a devolver.
compilador si no hemos definido ningn otro constructor. Por tanto, si tenemos una
clase en la que hayamos definido algn constructor con parmetros pero ninguno sin
parmetros no ser vlido crear objetos de la misma llamando al constructor sin Herencia y mtodos virtuales
parmetros, pues el compilador no lo habr definido automticamente. Por ejemplo, con
la ltima versin de la clase de ejemplo Persona es invlido hacer:
Concepto de herencia
Persona p = new Persona(); // ERROR: El nico constructor de Persona toma 3 parmetros
El mecanismo de herencia es uno de los pilares fundamentales en los que se basa la
programacin orientada a objetos. Es un mecanismo que permite definir nuevas clases a
Referencia al objeto actual con this partir de otras ya definidas de modo que si en la definicin de una clase indicamos que
sta deriva de otra, entonces la primera -a la que se le suele llamar clase hija- ser
tratada por el compilador automticamente como si su definicin incluyese la definicin

Jos Antonio Gonzlez Seco Pgina 61 Jos Antonio Gonzlez Seco Pgina 62
El lenguaje de programacin C# Tema 5: Clases El lenguaje de programacin C# Tema 5: Clases

de la segunda a la que se le suele llamar clase padre o clase base. Las clases que NIF = nif;
derivan de otras se definen usando la siguiente sintaxis: }
}
class <nombreHija>:<nombrePadre> class Trabajador: Persona
{ {
<miembrosHija> public int Sueldo; // Campo de cada objeto Trabajador que almacena cunto gana
}
Trabajador(string nombre, int edad, string nif, int sueldo): base(nombre, edad, nif)
A los miembros definidos en <miembrosHijas> se le aadirn los que hubisemos { // Inicializamos cada Trabajador en base al constructor de Persona
definido en la clase padre. Por ejemplo, a partir de la clase Persona puede crearse una Sueldo = sueldo;
clase Trabajador as: }

public static void Main()


class Trabajador:Persona {
{ Trabajador p = new Trabajador("Josan", 22, "77588260-Z", 100000);
public int Sueldo;
Console.WriteLine ("Nombre="+p.Nombre);
public Trabajador(string nombre, int edad, string nif, int sueldo): Console.WriteLine ("Edad="+p.Edad);
base(nombre, edad, nif) Console.WriteLine ("NIF="+p.NIF);
{ Console.WriteLine ("Sueldo="+p.Sueldo);
Sueldo = sueldo; }
} }
}

Ntese que ha sido necesario prefijar la definicin de los miembros de Persona del
Los objetos de esta clase Trabajador contarn con los mismos miembros que los objetos
palabra reservada public. Esto se debe a que por defecto los miembros de una tipo slo
Persona y adems incorporarn un nuevo campo llamado Sueldo que almacenar el
son accesibles desde cdigo incluido dentro de la definicin de dicho tipo, e incluyendo
dinero que cada trabajador gane. Ntese adems que a la hora de escribir el constructor
public conseguimos que sean accesibles desde cualquier cdigo, como el mtodo Main()
de esta clase ha sido necesario escribirlo con una sintaxis especial consistente en
definido en Trabajador. public es lo que se denomina un modificador de acceso,
preceder la llave de apertura del cuerpo del mtodo de una estructura de la forma:
concepto que se tratar ms adelante en este mismo tema bajo el epgrafe titulado
: base(<parametrosBase>) Modificadores de acceso.

A esta estructura se le llama inicializador base y se utiliza para indicar cmo deseamos
inicializar los campos heredados de la clase padre. No es ms que una llamada al Llamadas por defecto al constructor base
constructor de la misma con los parmetros adecuados, y si no se incluye el compilador
considerara por defecto que vale :base(), lo que sera incorrecto en este ejemplo debido Si en la definicin del constructor de alguna clase que derive de otra no incluimos
a que Persona carece de constructor sin parmetros. inicializador base el compilador considerar que ste es :base() Por ello hay que estar
seguros de que si no se incluye base en la definicin de algn constructor, el tipo padre
Un ejemplo que pone de manifiesto cmo funciona la herencia es el siguiente: del tipo al que pertenezca disponga de constructor sin parmetros.

using System; Es especialmente significativo resear el caso de que no demos la definicin de ningn
constructor en la clase hija, ya que en estos casos la definicin del constructor que por
class Persona defecto introducir el compilador ser en realidad de la forma:
{
public string Nombre; // Campo de cada objeto Persona que almacena su nombre
public int Edad; // Campo de cada objeto Persona que almacena su edad <nombreClase>(): base()
public string NIF; // Campo de cada objeto Persona que almacena su NIF {}

void Cumpleaos() // Incrementa en uno de edad del objeto Persona Es decir, este constructor siempre llama al constructor sin parmetros del padre del tipo
{ que estemos definiendo, y si se no dispone de alguno se producir un error al compilar.
Edad++;
}

public Persona (string nombre, int edad, string nif) // Constructor de Persona Mtodos virtuales
{
Nombre = nombre;
Edad = edad;

Jos Antonio Gonzlez Seco Pgina 63 Jos Antonio Gonzlez Seco Pgina 64
El lenguaje de programacin C# Tema 5: Clases El lenguaje de programacin C# Tema 5: Clases

Ya hemos visto que es posible definir tipos cuyos mtodos se hereden de definiciones
de otros tipos. Lo que ahora vamos a ver es que adems es posible cambiar dichar public virtual void Cumpleaos() // Incrementa en uno de la edad del objeto Persona
{
definicin en la clase hija, para lo que habra que haber precedido con la palabra Edad++;
reservada virtual la definicin de dicho mtodo en la clase padre. A este tipo de mtodos Console.WriteLine(Incrementada edad de persona);
se les llama mtodos virtuales, y la sintaxis que se usa para definirlos es la siguiente: }

virtual <tipoDevuelto> <nombreMtodo>(<parmetros>) public Persona (string nombre, int edad, string nif) // Constructor de Persona
{ {
<cdigo> Nombre = nombre;
} Edad = edad;
NIF = nif;
}
Si en alguna clase hija quisisemos dar una nueva definicin del <cdigo> del mtodo,
simplemente lo volveramos a definir en la misma pero sustituyendo en su definicin la }
palabra reservada virtual por override. Es decir, usaramos esta sintaxis:
class Trabajador: Persona
override <tipoDevuelto> <nombreMtodo>(<parmetros>) {
{ public int Sueldo; // Campo de cada objeto Trabajador que almacena cunto gana
<nuevoCdigo>
} Trabajador(string nombre, int edad, string nif, int sueldo): base(nombre, edad, nif)
{ // Inicializamos cada Trabajador en base al constructor de Persona
Sueldo = sueldo;
Ntese que esta posibilidad de cambiar el cdigo de un mtodo en su clase hija slo se }
da si en la clase padre el mtodo fue definido como virtual. En caso contrario, el
compilador considerar un error intentar redefinirlo. public override void Cumpleaos()
{
El lenguaje C# impone la restriccin de que toda redefinicin de mtodo que queramos Edad++;
Console.WriteLine(Incrementada edad de trabajador);
realizar incorpore la partcula override para forzar a que el programador est seguro de }
que verdaderamente lo que quiere hacer es cambiar el significado de un mtodo
heredado. As se evita que por accidente defina un mtodo del que ya exista una public static void Main()
definicin en una clase padre. Adems, C# no permite definir un mtodo como {
Persona p = new Persona("Carlos", 22, "77588261-Z");
override y virtual a la vez, ya que ello tendra un significado absurdo: estaramos
Trabajador t = new Trabajador("Josan", 22, "77588260-Z", 100000);
dando una redefinicin de un mtodo que vamos a definir.
t.Cumpleaos();
Por otro lado, cuando definamos un mtodo como override ha de cumplirse que en p.Cumpleaos();
alguna clase antecesora (su clase padre, su clase abuela, etc.) de la clase en la que se ha }
}
realizado la definicin del mismo exista un mtodo virtual con el mismo nombre que el
redefinido. Si no, el compilador informar de error por intento de redefinicin de
Ntese cmo se ha aadido el modificador virtual en la definicin de Cumpleaos() en la
mtodo no existente o no virtual. As se evita que por accidente un programador crea
clase Persona para habilitar la posibilidad de que dicho mtodo puede ser redefinido en
que est redefiniendo un mtodo del que no exista definicin previa o que redefina un
clase hijas de Persona y cmo se ha aado override en la redefinicin del mismo
mtodo que el creador de la clase base no desee que se pueda redefinir.
dentro de la clase Trabajador para indicar que la nueva definicin del mtodo es una
Para aclarar mejor el concepto de mtodo virtual, vamos a mostrar un ejemplo en el que redefinicin del heredado de la clase. La salida de este programa confirma que la
cambiaremos la definicin del mtodo Cumpleaos() en los objetos Persona por una implementacin de Cumpleaos() es distinta en cada clase, pues es de la forma:
nueva versin en la que se muestre un mensaje cada vez que se ejecute, y redefiniremos Incrementada edad de trabajador
dicha nueva versin para los objetos Trabajador de modo que el mensaje mostrado sea Incrementada edad de persona
otro. El cdigo de este ejemplo es el que se muestra a continuacin:
Tambin es importante sealar que para que la redefinicin sea vlida ha sido necesario
using System;
aadir la partcula public a la definicin del mtodo original, pues si no se incluyese se
class Persona considerara que el mtodo slo es accesible desde dentro de la clase donde se ha
{ definido, lo que no tiene sentido en mtodos virtuales ya que entonces nunca podra ser
public string Nombre; // Campo de cada objeto Persona que almacena su nombre redefinido. De hecho, si se excluyese el modificador public el compilador informara
public int Edad; // Campo de cada objeto Persona que almacena su edad de un error ante este absurdo. Adems, este modificador tambin se ha mantenido en la
public string NIF; // Campo de cada objeto Persona que almacena su NIF

Jos Antonio Gonzlez Seco Pgina 65 Jos Antonio Gonzlez Seco Pgina 66
El lenguaje de programacin C# Tema 5: Clases El lenguaje de programacin C# Tema 5: Clases

redefinicin de Cumpleaos() porque toda redefinicin de un mtodo virtual ha de La clase primegenia: System.Object
mantener los mismos modificadores de acceso que el mtodo original para ser vlida.
Ahora que sabemos lo que es la herencia es el momento apropiado para explicar que en
.NET todos los tipos que se definan heredan implcitamente de la clase System.Object
Clases abstractas predefinida en la BCL, por lo que dispondrn de todos los miembros de sta. Por esta
razn se dice que System.Object es la raz de la jerarqua de objetos de .NET.
Una clase abstracta es aquella que forzosamente se ha de derivar si se desea que se
puedan crear objetos de la misma o acceder a sus miembros estticos (esto ltimo se A continuacin vamos a explicar cules son estos mtodos comunes a todos los objetos:
ver ms adelante en este mismo tema) Para definir una clase abstracta se antepone
abstract a su definicin, como se muestra en el siguiente ejemplo: public virtual bool Equals(object o): Se usa para comparar el objeto sobre el que
se aplica con cualquier otro que se le pase como parmetro. Devuelve true si
public abstract class A
{
ambos objetos son iguales y false en caso contrario.
public abstract void F();
} La implementacin que por defecto se ha dado a este mtodo consiste en usar
abstract public class B: A igualdad por referencia para los tipos por referencia e igualdad por valor para los
{ tipos por valor. Es decir, si los objetos a comparar son de tipos por referencia
public void G() {}
} slo se devuelve true si ambos objetos apuntan a la misma referencia en
class C: B memoria dinmica, y si los tipos a comparar son tipos por valor slo se devuelve
{ true si todos los bits de ambos objetos son iguales, aunque se almacenen en
public override void F() posiciones diferentes de memoria.
{}
}
Como se ve, el mtodo ha sido definido como virtual, lo que permite que los
Las clases A y B del ejemplo son abstractas, y como puede verse es posible combinar en programadores puedan redefinirlo para indicar cundo ha de considerarse que
cualquier orden el modificador abstract con modificadores de acceso. son iguales dos objetos de tipos definidos por ellos. De hecho, muchos de los
tipos incluidos en la BCL cuentan con redefiniciones de este tipo, como es el
La utilidad de las clases abstractas es que pueden contener mtodos para los que no se caso de string, quien an siendo un tipo por referencia, sus objetos se
d directamente una implementacin sino que se deje en manos de sus clases hijas darla. consideran iguales si apuntan a cadenas que sean iguales carcter a carcter
No es obligatorio que las clases abstractas contengan mtodos de este tipo, pero s lo es (aunque referencien a distintas direcciones de memoria dinmica)
marcar como abstracta a toda la que tenga alguno. Estos mtodos se definen
precediendo su definicin del modificador abstract y sustituyendo su cdigo por un El siguiente ejemplo muestra cmo hacer una redefinicin de Equals() de
punto y coma (;), como se muestra en el mtodo F() de la clase A del ejemplo (ntese manera que aunque los objetos Persona sean de tipos por referencia, se considere
que B tambin ha de definirse como abstracta porque tampoco implementa el mtodo F() que dos Personas son iguales si tienen el mismo NIF:
que hereda de A)
public override bool Equals(object o)
{
Obviamente, como un mtodo abstracto no tiene cdigo no es posible llamarlo. Hay que if (o==null)
tener especial cuidado con esto a la hora de utilizar this para llamar a otros mtodos de return this==null;
un mismo objeto, ya que llamar a los abstractos provoca un error al compilar. else
return (o is Persona) && (this.NIF == ((Persona) o).NIF);
Vase que todo mtodo definido como abstracto es implcitamente virtual, pues si no }
sera imposible redefinirlo para darle una implementacin en las clases hijas de la clase
abstracta donde est definido. Por ello es necesario incluir el modificador override a la Hay que tener en cuenta que es conveniente que toda redefinicin del mtodo
hora de darle implementacin y es redundante marcar un mtodo como abstract y Equals() que hagamos cumpla con una serie de propiedades que muchos de los
virtual a la vez (de hecho, hacerlo provoca un error al compilar) mtodos incluidos en las distintas clases de la BCL esperan que se cumplan.
Estas propiedades son:
Es posible marcar un mtodo como abstract y override a la vez, lo que convertira al
mtodo en abstracto para sus clases hijas y forzara a que stas lo tuviesen que Reflexividad: Todo objeto ha de ser igual a s mismo. Es decir, x.Equals(x)
reimplementar si no se quisiese que fuesen clases abstractas. siempre ha de devolver true.
Simetra: Ha de dar igual el orden en que se haga la comparacin. Es decir,
x.Equals(y) ha de devolver lo mismo que y.Equals(x) .

Jos Antonio Gonzlez Seco Pgina 67 Jos Antonio Gonzlez Seco Pgina 68
El lenguaje de programacin C# Tema 5: Clases El lenguaje de programacin C# Tema 5: Clases

Transitividad: Si dos objetos son iguales y uno de ellos es igual a otro, Del mismo modo, cuando a Console.WriteLine() y Console.Write() se les pasa
entonces el primero tambin ha de ser igual a ese otro objeto. Es decir, si como parmetro un objeto lo que hacen es mostrar por la salida estndar el
x.Equals(y) e y.Equals(z) entonces x.Equals(z) . resultado de convertirlo en cadena llamando a su mtodo ToString(); y si se les
Consistencia: Siempre que el mtodo se aplique sobre los mismos objetos pasa como parmetros una cadena seguida de varios objetos lo muestran por la
ha de devolver el mismo resultado. salida estndar esa cadena pero sustituyendo en ella toda subcadena de la forma
Tratamiento de objetos nulos: Si uno de los objetos comparados es nulo {<nmero>} por el resultado de convertir en cadena el parmetro que ocupe la
(null), slo se ha de devolver true si el otro tambin lo es. posicin <nmero>+2 en la lista de valores de llamada al mtodo.

Hay que recalcar que el hecho de que redefinir Equals() no implica que el protected object MemberWiseClone(): Devuelve una copia shallow copy
operador de igualdad (==) quede tambin redefinido. Ello habra que hacerlo de del objeto sobre el que se aplica. Esta copia es una copia bit a bit del mismo, por
independientemente como se indica en el Tema 11: Redefinicin de operadores. lo que el objeto resultante de la copia mantendr las mismas referencias a otros
que tuviese el objeto copiado y toda modificacin que se haga a estos objetos a
public virtual int GetHashCode(): Devuelve un cdigo de dispersin (hash) que travs de la copia afectar al objeto copiado y viceversa.
representa de forma numrica al objeto sobre el que el mtodo es aplicado.
GetHashCode() suele usarse para trabajar con tablas de dispersin, y se cumple Si lo que interesa es disponer de una copia ms normal, en la que por cada objeto
que si dos objetos son iguales sus cdigos de dispersin sern iguales, mientras referenciado se crease una copia del mismo a la que referenciase el objeto
que si son distintos la probabilidad de que sean iguales es nfima. clonado, entonces el programador ha de escribir su propio mtodo clonador pero
puede servirse de MemberwiseClone() como base con la que copiar los campos
En tanto que la bsqueda de objetos en tablas de dispersin no se realiza que no sean de tipos referencia.
nicamente usando la igualdad de objetos (mtodo Equals()) sino usando tambin
la igualdad de cdigos de dispersin, suele ser conveniente redefinir
GetHashCode() siempre que se redefina Equals() De hecho, si no se hace el public System.Type GetType(): Devuelve un objeto de clase System.Type que
compilador informa de la situacin con un mensaje de aviso. representa al tipo de dato del objeto sobre el que el mtodo es aplicado. A travs
de los mtodos ofrecidos por este objeto se puede acceder a metadatos sobre el
public virtual string ToString(): Devuelve una representacin en forma de cadena mismo como su nombre, su clase padre, sus miembros, etc. La explicacin de
del objeto sobre el que se el mtodo es aplicado, lo que es muy til para depurar cmo usar los miembros de este objeto para obtener dicha informacin queda
aplicaciones ya que permite mostrar con facilidad el estado de los objetos. fuera del alcance de este documento ya que es muy larga y puede ser fcilmente
consultada en la documentacin que acompaa al .NET SDK.
La implementacin por defecto de este mtodo simplemente devuelve una
cadena de texto con el nombre de la clase a la que pertenece el objeto sobre el protected virtual void Finalize(): Contiene el cdigo que se ejecutar siempre que
que es aplicado. Sin embargo, como lo habitual suele ser implementar ToString() vaya ha ser destruido algn objeto del tipo del que sea miembro. La
en cada nueva clase que es defina, a continuacin mostraremos un ejemplo de implementacin dada por defecto a Finalize() consiste en no hacer nada.
cmo redefinirlo en la clase Persona para que muestre los valores de todos los
campos de los objetos Persona: Aunque es un mtodo virtual, en C# no se permite que el programador lo
redefina explcitamente dado que hacerlo es peligroso por razones que se
public override string ToString() explicarn en el Tema 8: Mtodos (otros lenguajes de .NET podran permitirlo)
{
string cadena = ;
Aparte de los mtodos ya comentados que todos los objetos heredan, la clase
cadena += DNI = + this.DNI + \n; System.Object tambin incluye en su definicin los siguientes mtodos de tipo:
cadena +=Nombre = + this.Nombre + \n;
cadena +=Edad = + this.Edad + \n; public static bool Equals(object objeto1, object objeto2) Versin esttica
return cadena;
del mtodo Equals() ya visto. Indica si los objetos que se le pasan como
} parmetros son iguales, y para compararlos lo que hace es devolver el
resultado de calcular objeto1.Equals(objeto2) comprobando antes si alguno
Es de resear el hecho de que en realidad los que hace el operador de de los objetos vale null (slo se devolvera true slo si el otro tambin lo es)
concatenacin de cadenas (+) para concatenar una cadena con un objeto
cualquiera es convertirlo primero en cadena llamando a su mtodo ToString() y Obviamente si se da una redefinicin al Equals() no esttico, sus efectos
luego realizar la concatenacin de ambas cadenas. tambin se vern cuando se llame al esttico.

Jos Antonio Gonzlez Seco Pgina 69 Jos Antonio Gonzlez Seco Pgina 70
El lenguaje de programacin C# Tema 5: Clases El lenguaje de programacin C# Tema 5: Clases

public static bool ReferenceEquals(object objeto1, object objeto2) Indica public int Edad; // Campo de cada objeto Persona que almacena su edad
si los dos objetos que se le pasan como parmetro se almacenan en la misma public string NIF; // Campo de cada objeto Persona que almacena su NIF
posicin de memoria dinmica. A travs de este mtodo, aunque se hayan public virtual void Cumpleaos() // Incrementa en uno la edad del objeto Persona
redefinido Equals() y el operador de igualdad (==) para un cierto tipo por {
referencia, se podrn seguir realizando comparaciones por referencia entre Console.WriteLine(Incrementada edad de persona);
objetos de ese tipo en tanto que redefinir de Equals() no afecta a este mtodo. }
Por ejemplo, dada la anterior redefinicin de Equals() para objetos Persona:
public Persona (string nombre, int edad, string nif) // Constructor de Persona
{
Persona p = new Persona(Jos, 22, 83721654-W);
Nombre = nombre;
Persona q = new Persona(Antonio, 23, 83721654-W);
Edad = edad;
Console.WriteLine(p.Equals(q));
NIF = nif;
Console.WriteLine(Object.Equals(p, q));
}
Console.WriteLine(Object.ReferenceEquals(p, q));
Console.WriteLine(p == q);
}
La salida que por pantalla mostrar el cdigo anterior es: class Trabajador: Persona
{
True int Sueldo; // Campo de cada objeto Trabajador que almacena cunto gana
True
False Trabajador(string nombre, int edad, string nif, int sueldo): base(nombre, edad, nif)
False { // Inicializamos cada Trabajador en base al constructor de Persona
Sueldo = sueldo;
En los primeros casos se devuelve true porque segn la redefinicin de }
Equals() dos personas son iguales si tienen el mismo DNI, como pasa con los
public override Cumpleaos()
objetos p y q. Sin embargo, en los ltimos casos se devuelve false porque {
aunque ambos objetos tienen el mismo DNI cada uno se almacena en la Edad++;
memoria dinmica en una posicin distinta, que es lo que comparan Console.WriteLine("Incrementada edad de trabajador");
ReferenceEquals() y el operador == (ste ltimo slo por defecto) }

public static void Main()


{
Polimorfismo Persona p = new Trabajador("Josan", 22, "77588260-Z", 100000);

Concepto de polimorfismo p.Cumpleaos();


// p.Sueldo++; //ERROR: Sueldo no es miembro de Persona
}
El polimorfismo es otro de los pilares fundamentales de la programacin orientada a }
objetos. Es la capacidad de almacenar objetos de un determinado tipo en variables de
tipos antecesores del primero a costa, claro est, de slo poderse acceder a travs de El mensaje mostrado por pantalla al ejecutar este mtodo confirma lo antes dicho
dicha variable a los miembros comunes a ambos tipos. Sin embargo, las versiones de los respecto a que la versin de Cumpleaos() a la que se llama, ya que es:
mtodos virtuales a las que se llamara a travs de esas variables no seran las definidas
como miembros del tipo de dichas variables, sino las definidas en el verdadero tipo de Incrementada edad de trabajador
los objetos que almacenan.

A continuacin se muestra un ejemplo de cmo una variable de tipo Persona puede Mtodos genricos
usarse para almacenar objetos de tipo Trabajador. En esos casos el campo Sueldo del
objeto referenciado por la variable no ser accesible, y la versin del mtodo El polimorfismo es muy til ya que permite escribir mtodos genricos que puedan
Cumpleaos() a la que se podra llamar a travs de la variable de tipo Persona sera la recibir parmetros que sean de un determinado tipo o de cualquiera de sus tipos hijos.
definida en la clase Trabajador, y no la definida en Persona: Es ms, en tanto que cmo se ver en el epgrafe siguiente, en C# todos los tipos
derivan implcitamente del tipo System.Object, podemos escribir mtodos que admitan
using System; parmetros de cualquier tipo sin ms que definirlos como mtodos que tomen
parmetros de tipo System.Object. Por ejemplo:
class Persona
{
public void MtodoGenrico(object o)
public string Nombre; // Campo de cada objeto Persona que almacena su nombre

Jos Antonio Gonzlez Seco Pgina 71 Jos Antonio Gonzlez Seco Pgina 72
El lenguaje de programacin C# Tema 5: Clases El lenguaje de programacin C# Tema 5: Clases

{ public virtual void F()


// Cdigo del mtodo {
} Console.WriteLine(A);
}
Ntese que en vez de System.Object se ha escrito object, que es el nombre abreviado }
incluido en C# para hacer referencia de manera compacta a un tipo tan frecuentemente class B:A
usado como System.Object. {
public override void F()
{
Determinacin de tipo. Operador is Console.WriteLine(Antes);
((A) this).F(); // (2)
Console.WriteLine(Despus);
Dentro de una rutina polimrifica que, como la del ejemplo anterior, admita parmetros }
que puedan ser de cualquier tipo, muchas veces es conveniente poder consultar en el
cdigo de la misma cul es el tipo en concreto del parmetro que se haya pasado al public static void Main()
{
mtodo en cada llamada al mismo. Para ello C# ofrece el operador is, cuya forma B b = new B();
sintaxis de uso es: b.F();
}
<expresin> is <nombreTipo> }

Este operador devuelve true en caso de que el resultado de evaluar <expresin> sea del Pues bien, si ejecutamos el cdigo anterior veremos que la aplicacin nunca termina de
tipo cuyo nombre es <nombreTipo> y false en caso contrario8. Gracias a ellas podemos ejecutarse y est constantemente mostrando el mensaje Antes por pantalla. Esto se debe
escribir mtodos genricos que puedan determinar cul es el tipo que tienen los a que debido al polimorfismo se ha entrado en un bucle infinito: aunque usemos el
parmetros que en cada llamada en concreto se les pasen. O sea, mtodos como: operador de conversin para tratar el objeto como si fuese de tipo A, su verdadero tipo
sigue siendo B, por lo que la versin de F() a la que se llamar en (2) es a la de B de
public void MtodoGenrico(object o) nuevo, que volver a llamarse as misma una y otra vez de manera indefinida.
{
if (o is int) // Si o es de tipo int (entero)...
// ...Cdigo a ejecutar si el objeto o es de tipo int Para solucionar esto, los diseadores de C# han incluido una palabra reservada llamada
else if (o is string) // Si no, si o es de tipo string (cadena)... base que devuelve una referencia al objeto actual semejante a this pero con la
// ...Cdigo a ejecutar si o es de tipo string peculiaridad de que los accesos a ella son tratados como si el verdadero tipo fuese el de
//... dem para otros tipos su clase base. Usando base, podramos reemplazar el cdigo de la redefinicin de F() de
}
ejemplo anterior por:
El bloque if...else es una instruccin condicional que permite ejecutar un cdigo u otro
public override void F()
en funcin de si la condicin indicada entre parntesis tras el if es cierta (true) o no {
(false) Esta instruccin se explicar ms detalladamente en el Tema 16: Instrucciones Console.WriteLine(Antes);
base.F();
Console.WriteLine(Despus);
Acceso a la clase base }

Si ahora ejecutamos el programa veremos que ahora s que la versin de F() en B llama a
Hay determinadas circunstancias en las que cuando redefinamos un determinado la versin de F() en A, resultando la siguiente salida por pantalla:
mtodo nos interese poder acceder al cdigo de la versin original. Por ejemplo, porque
el cdigo redefinido que vayamos a escribir haga lo mismo que el original y adems Antes
algunas cosas extras. En estos casos se podra pensar que una forma de conseguir esto A
sera convirtiendo el objeto actual al tipo del mtodo a redefinir y entonces llamar as a Despus
ese mtodo, como por ejemplo en el siguiente cdigo:
A la hora de redefinir mtodos abstractos hay que tener cuidado con una cosa: desde el
using System; mtodo redefinidor no es posible usar base para hacer referencia a mtodos abstractos
de la clase padre, aunque s para hacer referencia a los no abstractos. Por ejemplo:
class A
{ abstract class A
{
8
Si la expresin vale null se devolver false, pues este valor no est asociado a ningn tipo en concreto. public abstract void F();

Jos Antonio Gonzlez Seco Pgina 73 Jos Antonio Gonzlez Seco Pgina 74
El lenguaje de programacin C# Tema 5: Clases El lenguaje de programacin C# Tema 5: Clases

public void G() Una clase sellada es una clase que no puede tener clases hijas, y para definirla basta
{} anteponer el modificador sealed a la definicin de una clase normal. Por ejemplo:
}

class B: A sealed class ClaseSellada


{ {
public override void F() }
{
base.G(); // Correcto Una utilidad de definir una clase como sellada es que permite que las llamadas a sus
base.F(); // Error, base.F() es abstracto mtodos virtuales heredados se realicen tan eficientemente como si fuesen no virtuales,
} pues al no poder existir clases hijas que los redefinan no puede haber polimorfismo y no
} hay que determinar cul es la versin correcta del mtodo a la que se ha de llamar.
Ntese que se ha dicho mtodos virtuales heredados, pues lo que no se permite es
definir miembros virtuales dentro de este tipo de clases, ya que al no poderse heredarse
Downcasting de ellas es algo sin sentido en tanto que nunca podran redefinirse.

Dado que una variable de un determinado tipo puede estar en realidad almacenando un Ahora bien, hay que tener en cuenta que sellar reduce enormemente su capacidad de
objeto que sea de algn tipo hijo del tipo de la variable y en ese caso a travs de la reutilizacin, y eso es algo que el aumento de eficiencia obtenido en las llamadas a sus
variable slo puede accederse a aquellos miembros del verdadero tipo del objeto que mtodos virtuales no suele compensar. En realidad la principal causa de la inclusin de
sean comunes con miembros del tipo de la variable que referencia al objeto, muchas estas clases en C# es que permiten asegurar que ciertas clases crticas nunca podrn
veces nos va a interesar que una vez que dentro de un mtodo genrico hayamos tener clases hijas y sus variables siempre almacenarn objetos del mismo tipo. Por
determinado cul es el verdadero tipo de un objeto (por ejemplo, con el operador is) ejemplo, para simplificar el funcionamiento del CLR y los compiladores se ha optado
podamos tratarlo como tal. En estos casos lo que hay es que hacer una conversin del por hacer que todos los tipos de datos bsicos excepto System.Object estn sellados.
tipo padre al verdadero tipo del objeto, y a esto se le llama downcasting
Tngase en cuenta que es absurdo definir simultneamente una clase como abstract y
Para realizar un downcasting una primera posibilidad es indicar preceder la expresin a sealed, pues nunca podra accederse a la misma al no poderse crear clases hijas suyas
convertir del tipo en el que se la desea convertir indicado entre parntesis. Es decir, que definan sus mtodos abstractos. Por esta razn, el compilador considera errneo
siguiendo la siguiente sintaxis: definir una clase con ambos modificadores a la vez.
(<tipoDestino>) <expresinAConvertir> Aparte de para sellar clases, tambin se puede usar sealed como modificador en la
redefinicin de un mtodo para conseguir que la nueva versin del mismo que se defina
El resultado de este tipo de expresin es el objeto resultante de convertir el resultado de deje de ser virtual y se le puedan aplicar las optimizaciones arriba comentadas. Un
<expresinAConvertir> a <tipoDestino>. En caso de que la conversin no se pudiese ejemplo de esto es el siguiente:
realizar se lanzara una excepcin del tipo predefinido System.InvalidCastException
class A
Otra forma de realizar el downcasting es usando el operador as, que se usa as: {
public abstract F();
<expresinAConvertir> as <tipoDestino> }

class B:A
La principal diferencia de este operador con el anterior es que si ahora la conversin no {
se pudiese realizar se devolvera null en lugar de lanzarse una excepcin. La otra public sealed override F() // F() deja de ser redefinible
diferencia es que as slo es aplicable a tipos referencia y slo a conversiones entre tipos {}
de una misma jerarqua (de padres a hijos o viceversa) }

Los errores al realizar conversiones de este tipo en mtodos genricos se producen


cuando el valor pasado a la variable genrica no es ni del tipo indicado en <tipoDestino> Ocultacin de miembros
ni existe ninguna definicin de cmo realizar la conversin a ese tipo (cmo definirla se
ver en el Tema 11: Redefinicin de operadores) Hay ocasiones en las que puede resultar interesante usar la herencia nicamente como
mecanismo de reutilizacin de cdigo pero no necesariamente para reutilizar miembros.
Es decir, puede que interese heredar de una clase sin que ello implique que su clase hija
Clases y mtodos sellados herede sus miembros tal cuales sino con ligeras modificaciones.

Jos Antonio Gonzlez Seco Pgina 75 Jos Antonio Gonzlez Seco Pgina 76
El lenguaje de programacin C# Tema 5: Clases El lenguaje de programacin C# Tema 5: Clases

Esto puede muy til al usar la herencia para definir versiones especializadas de clases de }
uso genrico. Por ejemplo, los objetos de la clase System.Collections.ArrayList incluida
en la BCL pueden almacenar cualquier nmero de objetos System.Object, que al ser la En realidad la ocultacin de miembros no implica los miembros ocultados tengan que
clase primigenia ello significa que pueden almacenar objetos de cualquier tipo. Sin ser mtodos, sino que tambin pueden ser campos o cualquiera de los dems tipos de
embargo, al recuperarlos de este almacn genrico se tiene el problema de que los miembro que en temas posteriores se vern. Por ejemplo, puede que se desee que un
mtodos que para ello se ofrecen devuelven objetos System.Object, lo que implicar que campo X de tipo int est disponible en la clase hija como si fuese de tipo string.
muchas veces haya luego que reconvertirlos a su tipo original mediante downcasting
para poder as usar sus mtodos especficos. En su lugar, si slo se va a usar un Tampoco implica que los miembros mtodos ocultados tengan que diferenciarse de los
ArrayList para almacenar objetos de un cierto tipo puede resultar ms cmodo usar un mtodos ocultadores en su tipo de retorno, sino que pueden tener exactamente su mismo
objeto de alguna clase derivada de ArrayList cuyo mtodo extractor de objetos oculte al tipo de retorno, parmetros y nombre. Hacer esto puede dar lugar a errores muy sutiles
heredado de ArrayList y devuelva directamente objetos de ese tipo. como el incluido en la siguiente variante de la clase Trabajador donde en vez de
redefinirse Cumpleaos() lo que se hace es ocultarlo al olvidar incluir el override:
Para ver ms claramente cmo hacer la ocultacin, vamos a tomar el siguiente ejemplo
using System;
donde se deriva de una clase con un mtodo void F() pero se desea que en la clase hija el
mtodo que se tenga sea de la forma int F(): class Persona
{
class Padre public string Nombre; // Campo de cada objeto Persona que almacena su nombre
{ public int Edad; // Campo de cada objeto Persona que almacena su edad
public void F() public string NIF; // Campo de cada objeto Persona que almacena su NIF
{}
} public virtual void Cumpleaos() // Incrementa en uno la edad del objeto Persona
{
class Hija:Padre Console.WriteLine(Incrementada edad de persona);
{ }
public int F()
{return 1;} public Persona (string nombre, int edad, string nif) // Constructor de Persona
} {
Nombre = nombre;
Como en C# no se admite que en una misma clase hayan dos mtodos que slo se Edad = edad;
diferencien en sus valores de retorno, puede pensarse que el cdigo anterior producir NIF = nif;
un error de compilacin. Sin embargo, esto no es as sino que el compilador lo que har }
ser quedarse nicamente con la versin definida en la clase hija y desechar la heredada }
de la clase padre. A esto se le conoce como ocultacin de miembro ya que hace
desparacer en la clase hija el miembro heredado, y cuando al compilar se detecte se class Trabajador: Persona
generar el siguiente de aviso (se supone que clases.cs almacena el cdigo anterior): {
int Sueldo; // Campo de cada objeto Trabajador que almacena cunto gana
clases.cs(9,15): warning CS0108: The keyword new is required on
'Hija.F()' because it hides inherited member 'Padre.F()' Trabajador(string nombre, int edad, string nif, int sueldo): base(nombre, edad, nif)
{ // Inicializamos cada Trabajador en base al constructor de Persona
Como generalmente cuando se hereda interesa que la clase hija comparta los mismos Sueldo = sueldo;
}
miembros que la clase padre (y si acaso que aada miembros extra), el compilador
emite el aviso anterior para indicar que no se est haciendo lo habitual. Si queremos public Cumpleaos()
evitarlo hemos de preceder la definicin del mtodo ocultador de la palabra reservada {
new para as indicar explcitamente que queremos ocultar el F() heredado: Edad++;
Console.WriteLine("Incrementada edad de trabajador");
class Padre }
{
public void F() public static void Main()
{} {
} Persona p = new Trabajador("Josan", 22, "77588260-Z", 100000);

class Hija:Padre p.Cumpleaos();


{ // p.Sueldo++; //ERROR: Sueldo no es miembro de Persona
new public int F() }
{return 1;} }

Jos Antonio Gonzlez Seco Pgina 77 Jos Antonio Gonzlez Seco Pgina 78
El lenguaje de programacin C# Tema 5: Clases El lenguaje de programacin C# Tema 5: Clases

c.F();
Al no incluirse override se ha perdido la capacidad de polimorfismo, y ello puede verse d.F();
}
en que la salida que ahora mostrara por pantalla el cdigo: }
Incrementada edad de persona
La salida por pantalla de este programa es:
Errores de este tipo son muy sutiles y podran ser difciles de detectar. Sin embargo, en B.F
C# es fcil hacerlo gracias a que el compilador emitir el mensaje de aviso ya visto por B.F
haber hecho la ocultacin sin new. Cuando el programador lo vea podr aadir new para D.F
suprimirlo si realmente lo que quera hacer era ocultar, pero si esa no era su intencin D.F
as sabr que tiene que corregir el cdigo (por ejemplo, aadiendo el override olvidado)
Aunque el verdadero tipo de los objetos a cuyo mtodo se llama en Main() es D, en las
Como su propio nombre indica, cuando se redefine un mtodo se cambia su definicin dos primeras llamadas se llama al F() de B. Esto se debe a que la redefinicin dada en B
original y por ello las llamadas al mismo ejecutaran dicha versin aunque se hagan a cambia la versin de F() en A por la suya propia, pero la ocultacin dada en C hace que
travs de variables de la clase padre que almacenen objetos de la clase hija donde se para la redefinicin que posteriormente se da en D se considere que la versin original
redefini. Sin embargo, cuando se oculta un mtodo no se cambia su definicin en la de F() es la dada en C y ello provoca que no modifique la versiones de dicho mtodo
clase padre sino slo en la clase hija, por lo que las llamadas al mismo realizadas a dadas en A y B (que, por la redefinicin dada en B, en ambos casos son la versin de B)
travs de variables de la clase padre ejecutarn la versin de dicha clase padre y las
realizadas mediante variables de la clase hija ejecutarn la versin de la clase hija. Un truco mnemotcnico que puede ser til para determinar a qu versin del mtodo se
llamar en casos complejos como el anterior consiste en considerar que el mecanismo
En realidad el polimorfismo y la ocultacin no son conceptos totalmente antagnicos, y de polimorfismo funciona como si buscase el verdadero tipo del objeto a cuyo mtodo
aunque no es vlido definir mtodos que simultneamente tengan los modificadores se llama descendiendo en la jerarqua de tipos desde el tipo de la variable sobre la que
override y new ya que un mtodo ocultador es como si fuese la primera versin que se
se aplica el mtodo y de manera que si durante dicho recorrido se llega a alguna versin
hace del mismo (luego no puede redefinirse algo no definido), s que es posible del mtodo con new se para la bsqueda y se queda con la versin del mismo incluida
combinar new y virtual para definir mtodos ocultadores redefinibles. Por ejemplo: en el tipo recorrido justo antes del que tena el mtodo ocultador.

using System; Hay que tener en cuenta que el grado de ocultacin que proporcione new depende del
nivel de accesibilidad del mtodo ocultador, de modo que si es privado slo ocultar
class A dentro de la clase donde est definido. Por ejemplo, dado:
{
public virtual void F() { Console.WriteLine("A.F"); } using System;
}
class B: A class A
{ {
public override void F() { Console.WriteLine("B.F"); } public virtual void F() // F() es un mtodo redefinible
} {
class C: B Console.WriteLine(F() de A);
{ }
new public virtual void F() { Console.WriteLine("C.F"); } }
}
class D: C class B: A
{ {
public override void F() { Console.WriteLine("D.F"); } new private void F() {} // Oculta la versin de F() de A slo dentro de B
} }
class Ocultacin class C: B
{ {
public static void Main() public override void F() // Vlido, pues aqu slo se ve el F() de A
{ {
A a = new D(); base.F();
B b = new D(); Console.WriteLine(F() de B);
C c = new D(); }
D d = new D();
public static void Main()
a.F(); {
b.F(); C obj = new C();

Jos Antonio Gonzlez Seco Pgina 79 Jos Antonio Gonzlez Seco Pgina 80
El lenguaje de programacin C# Tema 5: Clases El lenguaje de programacin C# Tema 5: Clases

obj.F(); Miembros de tipo


}
}
En realidad, dentro la definicin de un tipo de dato no tienen porqu incluirse slo
La salida de este programa por pantalla ser: definiciones de miembros comunes a todos sus objetos, sino tambin pueden definirse
miembros ligados al tipo como tal y no a los objetos del mismo. Para ello basta preceder
F() de A
F() de B
la definicin de ese miembro de la palabra reservada static, como muestra este ejemplo:

class A
Pese a todo lo comentado, hay que resaltar que la principal utilidad de poder indicar
{
explcitamente si se desea redefinir u ocultar cada miembro es que facilita enormemente int x;
la resolucin de problemas de versionado de tipos que puedan surgir si al derivar una static int y;
nueva clase de otra y aadirle miembros adicionales, posteriormente se la desea }
actualizar con una nueva versin de su clase padre pero sta contiene miembros que
entran en conflictos con los aadidos previamente a la clase hija cuando an no existan Los objetos de clase A slo van a disponer del campo x, mientras que el campo y va a
en la clase padre. En lenguajes donde implcitamente todos los miembros son virtuales, pertenecer a la clase A. Por esta razn se dice que los miembros con modificador static
como Java, esto da lugar a problemas muy graves debidos sobre todo a: son miembros de tipo y que los no lo tienen son miembros de objeto.

Que por sus nombres los nuevos miembros de la clase padre entre en conflictos con Para acceder a un miembro de clase ya no es vlida la sintaxis hasta ahora vista de
los aadidos a la clase hija cuando no existan. Por ejemplo, si la versin inicial de <objeto>.<miembro>, pues al no estar estos miembros ligados a ningn objeto no podra
de la clase padre no contiene ningn mtodo de nombre F(), a la clase hija se le ponerse nada en el campo <objeto>. La sintaxis a usar para acceder a estos miembros
aade void F() y luego en la nueva versin de la clase padre se incorporado int F(), se ser <nombreClase>.<miembro>, como muestra ejemplo donde se asigna el valor 2 al
producir un error por tenerse en la clase hija dos mtodos F() miembro y de la clase A definida ms arriba:
A.y = 2;
En Java para resolver este problema una posibilidad sera pedir al creador de la clase
padre que cambiase el nombre o parmetros de su mtodo, lo cual no es siempre
Ntese que la inclusin de miembros de clase rompe con la afirmacin indicada al
posible ni conveniente en tanto que ello podra trasladar el problema a que hubiesen
principio del tema en la que se deca que C# es un lenguaje orientado a objetos puro en
derivado de dicha clase antes de volverla a modificar. Otra posibilidad sera
el que todo con lo que se trabaja son objetos, ya que a los miembros de tipo no se les
modificar el nombre o parmetros del mtodo en la clase hija, lo que nuevamente
accede a travs de objetos sino nombres de tipos.
puede llevar a incompatibilidades si tambin se hubiese derivado de dicha clase hija.
Es importante matizar que si definimos una funcin como static, entonces el cdigo de
Que los nuevos miembros tengan los mismos nombres y tipos de parmetros que los
la misma slo podr acceder implcitamente (sin sintaxis <objeto>.<miembro>) a otros
incluidos en las clases hijas y sea obligatorio que toda redefinicin que se haga de
miembros static del tipo de dato al que pertenezca. O sea, no se podr acceder a ni a los
ellos siga un cierto esquema.
miembros de objeto del tipo en que est definido ni se podr usar this ya que el mtodo
no est asociado a ningn objeto. O sea, este cdigo sera invlido:
Esto es muy problemtico en lenguajes como Java donde toda definicin de mtodo
con igual nombre y parmetros que alguno de su clase padre es considerado int x;
implcitamente redefinicin de ste, ya que difcilmente en una clase hija escrita con static void Incrementa()
anterioridad a la nueva versin de la clase padre se habr seguido el esquema {
necesario. Por ello, para resolverlo habr que actualizar la clase hija para que lo siga x++; //ERROR: x es miembro de objeto e Incrementa() lo es de clase.
y de tal manera que los cambios que se le hagan no afecten a sus subclases, lo que }
ello puede ser ms o menos difcil segn las caractersticas del esquema a seguir.
Tambin hay que sealar que los mtodos estticos no entran dentro del mecanismo de
redefiniciones descrito en este mismo tema. Dicho mecanismo slo es aplicable a
Otra posibilidad sera sellar el mtodo en la clase hija, pero ello recorta la capacidad
mtodos de objetos, que son de quienes puede declararse variables y por tanto puede
de reutilizacin de dicha clase y slo tiene sentido si no fue redefinido en ninguna
actuar el polimorfismo. Por ello, incluir los modificadores virtual, override o abstract al
subclase suya.
definir un mtodo static es considerado errneo por el compilador. Eso no significa que
los miembros static no se hereden, sino tan slo que no tiene sentido redefinirlos.
En C# todos estos problemas son de fcil solucin ya que pueden resolverse con slo
ocultar los nuevos miembros en la clase hija y seguir trabajando como si no existiesen.
Encapsulacin

Jos Antonio Gonzlez Seco Pgina 81 Jos Antonio Gonzlez Seco Pgina 82
El lenguaje de programacin C# Tema 5: Clases El lenguaje de programacin C# Tema 5: Clases

Ya hemos visto que la herencia y el polimorfismo eran dos de los pilares fundamentales b.x = 1; // Ok
en los que es apoya la programacin orientada a objetos. Pues bien, el tercero y ltimo c.x = 1; // Ok
}
es la encapsulacin, que es un mecanismo que permite a los diseadores de tipos de }
datos determinar qu miembros de los tipos creen pueden ser utilizados por otros
programadores y cules no. Las principales ventajas que ello aporta son: public class C: B
{
static void F(A a, B b, C c)
Se facilita a los programadores que vaya a usar el tipo de dato (programadores
{
clientes) el aprendizaje de cmo trabajar con l, pues se le pueden ocultar todos los //a.x = 1; // Error, ha de accederse a traves de objetos tipo C
detalles relativos a su implementacin interna y slo dejarle visibles aquellos que //b.x = 1; // Error, ha de accederse a traves de objetos tipo C
puedan usar con seguridad. Adems, as se les evita que cometan errores por c.x = 1; // Ok
manipular inadecuadamente miembros que no deberan tocar. }
}
Se facilita al creador del tipo la posterior modificacin del mismo, pues si los Obviamente siempre que se herede de una clase se tendr total acceso en la clase
programadores clientes no pueden acceder a los miembros no visibles, sus hija e implcitamente sin necesidad de usar la sintaxis <objeto>.<miembro>- a los
aplicaciones no se vern afectadas si stos cambian o se eliminan. Gracias a esto es miembros que sta herede de su clase padre, como muestra el siguiente ejemplo:
posible crear inicialmente tipos de datos con un diseo sencillo aunque poco
eficiente, y si posteriormente es necesario modificarlos para aumentar su eficiencia, using System;
ello puede hacerse sin afectar al cdigo escrito en base a la no mejorada de tipo.
class A
{
La encapsulacin se consigue aadiendo modificadores de acceso en las definiciones protected int x=5;
de miembros y tipos de datos. Estos modificadores son partculas que se les colocan }
delante para indicar desde qu cdigos puede accederse a ellos, entendindose por
acceder el hecho de usar su nombre para cualquier cosa que no sea definirlo, como class B:A
llamarlo si es una funcin, leer o escribir su valor si es un campo, crear objetos o {
B()
heredar de l si es una clase, etc. {
Console.WriteLine(Heredado x={0} de clase A, x);
Por defecto se considera que los miembros de un tipo de dato slo son accesibles desde }
cdigo situado dentro de la definicin del mismo, aunque esto puede cambiarse
precedindolos de uno los siguientes modificadores (aunque algunos de ellos ya se han public static void Main()
{
explicado a lo largo del tema, aqu se recogen todos de manera detallada) al definirlos: new B();
}
public: Puede ser accedido desde cualquier cdigo. }
protected: Desde una clase slo puede accederse a miembros protected de
objetos de esa misma clase o de subclases suyas. As, en el siguiente cdigo las Como es de esperar, la salida por pantalla del programa de ejemplo ser:
instrucciones comentadas con // Error no son vlidas por lo escrito junto a ellas:
Heredado x=5 de clase A
public class A
{ A lo que no se podr acceder desde una clase hija es a los miembros protegidos
protected int x; de otros objetos de su clase padre, sino slo a los heredados. Es decir:
static void F(A a, B b, C c) using System;
{
a.x = 1; // Ok class A
b.x = 1; // Ok {
c.x = 1; // OK protected int x=5;
} }
}
class B:A
public class B: A {
{ B(A objeto)
static void F(A a, B b, C c) {
{ Console.WriteLine(Heredado x={0} de clase A, x);
//a.x = 1; // Error, ha de accederse a traves de objetos tipo B o C Console.WriteLine(objeto.x); // Error, no es el x heredado

Jos Antonio Gonzlez Seco Pgina 83 Jos Antonio Gonzlez Seco Pgina 84
El lenguaje de programacin C# Tema 5: Clases El lenguaje de programacin C# Tema 5: Clases

Ntese que dado que los tipos externos estn definidos dentro de su tipo externo, desde
} ellos es posible acceder a los miembros estticos privados de ste. Sin embargo, hay que
public static void Main()
sealar que no pueden acceder a los miembros no estticos de su tipo contenedor.
{
new B(new A());
}
}

private: Slo puede ser accedido desde el cdigo de la clase a la que pertenece.
Es lo considerado por defecto.
internal: Slo puede ser accedido desde cdigo perteneciente al ensamblado
en que se ha definido.
protected internal: Slo puede ser accedido desde cdigo perteneciente al
ensamblado en que se ha definido o desde clases que deriven de la clase donde
se ha definido.

Si se duda sobre el modificador de visibilidad a poner a un miembro, es mejor ponerle


inicialmente el que proporcione menos permisos de accesos, ya que si luego detecta que
necesita darle ms permisos siempre podr cambirselo por otro menos restringido. Sin
embargo, si se le da uno ms permisivo de lo necesario y luego se necesita cambiar por
otro menos permisivo, los cdigos que escrito en base a la versin ms permisiva que
dependiesen de dicho miembro podran dejar de funcionar por quedarse sin acceso a l.

Es importante recordar que toda redefinicin de un mtodo virtual o abstracto ha de


realizarse manteniendo los mismos modificadores que tuviese el mtodo original. Es
decir, no podemos redefinir un mtodo protegido cambiando su accesibilidad por
pblica, pues si el creador de la clase base lo defini as por algo sera.

Respecto a los tipos de datos, por defecto se considera que son accesibles slo desde el
mismo ensamblado en que ha sido definidos, aunque tambin es posible modificar esta
consideracin anteponiendo uno de los siguientes modificadores a su definicin:

public: Es posible acceder a la clase desde cualquier ensamblado.


internal: Slo es posible acceder a la clase desde el ensamblado donde se
declar. Es lo considerado por defecto.

Tambin pueden definirse tipos dentro de otros (tipos internos) En ese caso sern
considerados miembros del tipo contenedor dentro de la que se hayan definido, por lo
que les sern aplicables todos los modificadores vlidos para miembros y por defecto se
considerar que, como con cualquier miembro, son privados. Para acceder a estos tipos
desde cdigo externo a su tipo contenedor (ya sea para heredar de ellos, crear objetos
suyos o acceder a sus miembros estticos), adems de necesitarse los permisos de
acceso necesarios segn el modificador de accesibilidad al definirlos, hay que usar la
notacin <nombreTipoContendor>.<nombreTipoInterno>, como muestra en este ejemplo:

class A // No lleva modificador, luego se considera que es internal


{
public class AInterna {} // Si ahora no se pusiese public se considerara private
}

class B:A.AInterna // B deriva de la clase interna AInterna definida dentro de A. Es


{} // vlido porque A.AInterna es pblica

Jos Antonio Gonzlez Seco Pgina 85 Jos Antonio Gonzlez Seco Pgina 86
El lenguaje de programacin C# Tema 6: Espacios de nombres El lenguaje de programacin C# Tema 6: Espacios de nombres

en los ejemplos de temas previos- se considera que sta pertenece al llamado espacio de
TEMA 6: Espacios de nombres nombres global y su nombre completamente calificado coincidir con el identificador
que tras la palabra reservada class le demos en su definicin (nombre simple)

Concepto de espacio de nombres Aparte de definiciones de tipo, tambin es posible incluir como miembros de un espacio
de nombres a otros espacios de nombres. Es decir, como se muestra el siguiente ejemplo
es posible anidar espacios de nombres:
Del mismo modo que los ficheros se organizan en directorios, los tipos de datos se
organizan en espacio de nombres. namespace EspacioEjemplo
{
Por un lado, esto permite tenerlos ms organizados y facilita su localizacin. De hecho, namespace EspacioEjemplo2
as es como se halla organizada la BCL, de modo que todas las clases ms comnmente {
class ClaseEjemplo
usadas en cualquier aplicacin se hallan en el espacio de nombres llamado System, las {}
de acceso a bases de datos en System.Data, las de realizacin de operaciones de }
entrada/salida en System.IO, etc. }

Por otro lado, los espacios de nombres tambin permiten poder usar en un mismo Ahora ClaseEjemplo tendr EspacioEjemplo.EspacioEjemplo2.ClaseEjemplo como nombre
programa varias clases con igual nombre si pertenecen a espacios diferentes. La idea es completamente calificado. En realidad es posible compactar las definiciones de espacios
que cada fabricante defina sus tipos dentro de un espacio de nombres propio para que de nombres anidados usando esta sintaxis de calificacin completa para dar el nombre
as no haya conflictos si varios fabricantes definen clases con el mismo nombre y se del espacio de nombres a definir. Es decir, el ltimo ejemplo es equivalente a:
quieren usar a la vez en un mismo programa. Obviamente para que esto funcione no han
de coincidir los nombres los espacios de cada fabricante, y una forma de conseguirlo es namespace EspacioEjemplo.EspacioEjemplo2
dndoles el nombre de la empresa fabricante, o su nombre de dominio en Internet, etc. {
class ClaseEjemplo
{}
}
Definicin de espacios de nombres
En ambos casos lo que se ha definido es una clase ClaseEjemplo perteneciente al espacio
Para definir un espacio de nombres se utiliza la siguiente sintaxis: de nombres EspacioEjemplo2 que, a su vez, pertenece al espacio EspacioEjemplo.

namespace <nombreEspacio>
{ Importacin de espacios de nombres
<tipos>
}
Sentencia using
Los <tipos> as definidos pasarn a considerase miembros del espacio de nombres
llamado <nombreEspacio>. Como veremos ms adelante, aparte de clases estos tipos En principio, si desde cdigo perteneciente a una clase definida en un cierto espacio de
pueden ser tambin interfaces, estructuras, tipos enumerados y delegados. A nombres se desea hacer referencia a tipos definidos en otros espacios de nombres, se ha
continuacin se muestra un ejemplo en el que definimos una clase de nombre de referir a los mismos usando su nombre completamente calificado. Por ejemplo:
ClaseEjemplo perteneciente a un espacio de nombres llamado EspacioEjemplo:
namespace EspacioEjemplo.EspacioEjemplo2
namespace EspacioEjemplo {
{ class ClaseEjemplo
class ClaseEjemplo {}
{} }
}
class Principal // Pertenece al espacio de nombres global
{
El verdadero nombre de una clase, al que se denomina nombre completamente public static void Main ()
calificado, es el nombre que le demos al declararla prefijado por la concatenacin de {
todos los espacios de nombres a los que pertenece ordenados del ms externo al ms EspacioEjemplo.EspacioEjemplo2.ClaseEjemplo c = new
interno y seguido cada uno de ellos por un punto (carcter .) Por ejemplo, el verdadero EspacioEjemplo.EspacioEjemplo2.ClaseEjemplo();
nombre de la clase ClaseEjemplo antes definida es EspacioEjemplo.ClaseEjemplo. Si no
}
definimos una clase dentro de una definicin de espacio de nombres -como se ha hecho

Jos Antonio Gonzlez Seco Pgina 87 Jos Antonio Gonzlez Seco Pgina 88
El lenguaje de programacin C# Tema 6: Espacios de nombres El lenguaje de programacin C# Tema 6: Espacios de nombres

}
En este caso el using aparece antes que cualquier otra definicin de tipos dentro del
Como puede resultar muy pesado tener que escribir nombres tan largos cada vez que se espacio de nombres en que se incluye (Principal) Sin embargo, ahora la importacin
referencie a tipos as definidos, C# incluye un mecanismo de importacin de espacios de hecha con el using slo ser vlida dentro de cdigo incluido en ese mismo espacio de
nombres que simplifica la tarea y se basa en una sentencia que usa la siguiente sintaxis: nombres, mientras que en el caso anterior era vlida en todo el fichero al estar incluida
en el espacio de nombres global.
using <espacioNombres>;
Debe tenerse en cuenta que si una sentencia using importa miembros de igual nombre
Estas sentencias siempre han de aparecer en la definicin de espacio de nombres antes que miembros definidos en el espacio de nombres en que se incluye, no se produce error
que cualquier definicin de miembros de la misma. Permiten indicar cules sern los alguno pero se dar preferencia a los miembros no importados. Un ejemplo:
espacios de nombres que se usarn implcitamente dentro de ese espacio de nombres. A
los miembros de los espacios de nombres as importados se les podr referenciar sin namespace N1.N2
usar calificacin completa. As, aplicando esto al ejemplo anterior quedara: {
class A {}
using EspacioEjemplo.EspacioEjemplo2; class B {}
}
namespace EspacioEjemplo.EspacioEjemplo2
{ namespace N3
class ClaseEjemplo {
{} using N1.N2;
}
class A {}
// (1) class C: A {}
class Principal // Pertenece al espacio de nombres global
{ }
public static void ()
{ Aqu C deriva de N3.A en vez de N1.N2.A. Si queremos lo contrario tendremos que
// EspacioEjemplo.EspacioEjemplo2. est implcito referenciar a N1.N2.A por su nombre completo al definir C o, como se explica a
ClaseEjemplo c = new ClaseEjemplo();
}
continuacin, usar un alias.
}

Ntese que la sentencia using no podra haberse incluido en la zona marcada en el Especificacin de alias
cdigo como (1) porque entonces se violara la regla de que todo using ha aparecer en
un espacio de nombres antes que cualquier definicin de miembro (la definicin del An en el caso de que usemos espacios de nombres distintos para diferenciar clases con
espacio de nombres EspacioEjemplo.EspacioEjemplo2 es un miembro del espacio de igual nombre pero procedentes de distintos fabricantes, podran darse conflictos si
nombres global) Sin embargo, el siguiente cdigo s que sera vlido: usamos sentencias using para importar los espacios de nombres de dichos fabricantes ya
que entonces al hacerse referencia a una de las clases comunes con tan solo su nombre
namespace EspacioEjemplo.EspacioEjemplo2 simple el compilador no podr determinar a cual de ellas en concreto nos referimos.
{
class ClaseEjemplo
{}
Por ejemplo, si tenemos una clase de nombre completamente calificado A.Clase, otra de
} nombre B.Clase, y hacemos:

namespace Principal using A;


{ using B;
using EspacioEjemplo.EspacioEjemplo2;
class EjemploConflicto: Clase {}
class Principal // Pertenece al espacio de nombres global
{ Cmo sabr el compilador si lo que queremos es derivar de A.Clase o de B.Clase? Pues
public static void Main()
{
en realidad no podr determinarlo y producir un error informando de que existe una
ClaseEjemplo c = new ClaseEjemplo(); referencia ambigua a Clase en el cdigo.
}
} Para resolver ambigedades de este tipo podra hacerse referencia a los tipos en
} conflicto usando siempre sus nombres completamente calificados, pero ello puede llegar

Jos Antonio Gonzlez Seco Pgina 89 Jos Antonio Gonzlez Seco Pgina 90
El lenguaje de programacin C# Tema 6: Espacios de nombres El lenguaje de programacin C# Tema 6: Espacios de nombres

a ser muy fatigoso sobre todo si sus nombres son muy largos. Para solucionarlo sin Si hacemos varias definiciones de un espacio de nombres en un mismo o diferentes
escribir tanto, C# permite definir alias para cualquier tipo de dato, que son sinnimos ficheros y se compilan todas juntas, el compilador las fusionar en una sola definicin
que se les definen utilizando la siguiente sintaxis: cuyos miembros sern la concatenacin de los de definicin realizada. Por ejemplo:

using <alias> = <nombreCompletoTipo>; namespace A // (1)


{
Como cualquier otro using, las definiciones de alias slo pueden incluirse al principio class B1 {}
}
de las definiciones de espacios de nombres y slo tienen validez dentro de las mismas.
namespace A // (2)
Definiendo alias distintos para los tipos en conflictos se resuelven los problemas de {
ambigedades. Por ejemplo, el problema del ejemplo anterior podra resolverse as: class B2 {}
}
using A;
using B; Una definicin como la anterior es tratada por el compilador exactamente igual que:
using ClaseA = A.Clase;
namespace A
class EjemploConflicto: ClaseA {} // Heredamos de A.Clase {
class B1 {}
Los alias no tienen porqu ser slo referentes a tipos, sino que tambin es posible class B2 {}
escribir alias de espacios de nombres. Por ejemplo: }

namespace N1.N2 Y lo mismo ocurrira si las definiciones marcadas como (1) y (2) se hubiesen hecho en
{ ficheros separados que se compilasen conjuntamente.
class A {}
} Hay que tener en cuenta que las sentencias using, ya sean de importacin de espacios
namespace N3
{
de nombres o de definicin de alias, no son consideradas miembros de los espacios de
using R1 = N1; nombres y por tanto no participan en sus fusiones. As, el siguiente cdigo es invlido:
using R2 = N1.N2;
class B namespace A
{ {
N1.N2.A a; // Campo de nombre completamente calificado N1.N2.A class ClaseA {}
R1.N2.A b; // Campo de nombre completamente calificado N1.N2.A }
R2.A c; // Campo de nombre completamente calificado N1.N2.A
} namespace B
} {
using A;
Al definir alias hay que tener cuidado con no definir en un mismo espacio de nombres }
varios con igual nombre o cuyos nombres coincidan con los de miembros de dicho namespace B
espacio de nombres. Tambin hay que tener en cuenta que no se pueden definir unos {
alias en funcin de otro, por lo que cdigos como el siguiente son incorrectos: // using A;
class Principal: ClaseA {}
namespace N1.N2 {} }
namespace N3
{ Este cdigo no es correcto porque aunque se importa el espacio de nombres A al
using R1 = N1; principio de una definicin del espacio de nombres donde se ha definido Principal, no se
using R2 = N1.N2;
using R3 = R1.N2; // ERROR: No se puede definir R3 en funcin de R1
importa en la misma definicin donde se deriva Principal de A.ClaseA. Para que el cdigo
} compilase habra que descomentar la lnea comentada.

Espacio de nombres distribuidos

Jos Antonio Gonzlez Seco Pgina 91 Jos Antonio Gonzlez Seco Pgina 92
El lenguaje de programacin C# Tema 7: Variables y tipos de datos El lenguaje de programacin C# Tema 7: Variables y tipos de datos

TEMA 7: Variables y tipos de datos Sin embargo, C# tambin proporciona una sintaxis ms sencilla con la que podremos
asignar un objeto a una variable en el mismo momento se define. Para ello se la ha de
definir usando esta otra notacin:
Definicin de variables <tipoVariable> <nombreVariable> = <valorInicial>;

Una variable puede verse simplemente como un hueco en el que se puede almacenar un As por ejemplo, la anterior asignacin de valor a la variable p podra rescribirse de esta
objeto de un determinado tipo al que se le da un cierto nombre. Para poderla utilizar otra forma ms compacta:
slo hay que definirla indicando cual ser su nombre y cual ser el tipo de datos que
podr almacenar, lo que se hace siguiendo la siguiente sintaxis: Persona p = new Persona(Jos, 22, 76543876-A);

<tipoVariable> <nombreVariable>; La especificacin de un valor inicial tambin combinarse con la definicin de mltiples
variables separadas por comas en una misma lnea. Por ejemplo, las siguientes
Una variable puede ser definida dentro de una definicin de clase, en cuyo caso se definiciones son vlidas:
correspondera con el tipo de miembro que hasta ahora hemos denominado campo.
Tambin puede definirse como un variable local a un mtodo, que es una variable Persona p1 = new Persona(Jos, 22, 76543876-A), p2 = new Persona(Juan, 21,
definida dentro del cdigo del mtodo a la que slo puede accederse desde dentro de 87654212-S);
dicho cdigo. Otra posibilidad es definirla como parmetro de un mtodo, que son
variables que almacenan los valores de llamada al mtodo y que, al igual que las Y son tratadas por el compilador de forma completamente equivalentes a haberlas
variables locales, slo puede ser accedidas desde cdigo ubicado dentro del mtodo. El declarado como:
siguiente ejemplo muestra como definir variables de todos estos casos:
Persona p1 = new Persona(Jos, 22, 76543876-A);
class A Persona p2 = new Persona(Juan, 21, 87654212-S);
{
int x, z;
int y; Tipos de datos bsicos
void F(string a, string b)
{ Los tipos de datos bsicos son ciertos tipos de datos tan comnmente utilizados en la
Persona p; escritura de aplicaciones que en C# se ha incluido una sintaxis especial para tratarlos.
} Por ejemplo, para representar nmeros enteros de 32 bits con signo se utiliza el tipo de
}
dato System.Int32 definido en la BCL, aunque a la hora de crear un objeto a de este tipo
que represente el valor 2 se usa la siguiente sintaxis:
En este ejemplo las variables x, z e y son campos de tipo int, mientras que p es una
variable local de tipo Persona y a y b son parmetros de tipo string. Como se muestra en System.Int32 a = 2;
el ejemplo, si un mtodo toma varios parmetros las definiciones de stos se separan
mediante comas (carcter ,), y si queremos definir varios campos o variables locales (no Como se ve, no se utiliza el operador new para crear objeto System.Int32, sino que
vlido para parmetros) de un mismo tipo podemos incluirlos en una misma definicin directamente se indica el literal que representa el valor a crear, con lo que la sintaxis
incluyendo en <nombreVariable> sus nombres separados por comas. necesaria para crear entero de este tipo se reduce considerablemente. Es ms, dado lo
frecuente que es el uso de este tipo tambin se ha predefinido en C# el alias int para el
Con la sintaxis de definicin de variables anteriormente dada simplemente definimos mismo, por lo que la definicin de variable anterior queda as de compacta:
variables pero no almacenamos ningn objeto inicial en ellas. El compilador dar un
valor por defecto a los campos para los que no se indique explcitamente ningn valor int a = 2;
segn se explica en el siguiente apartado. Sin embargo, a la variables locales no les da
ningn valor inicial, pero detecta cualquier intento de leerlas antes de darles valor y System.Int32 no es el nico tipo de dato bsico incluido en C#. En el espacio de
produce errores de compilacin en esos casos. nombres System se han incluido todos estos:

Ya hemos visto que para crear objetos se utiliza el operador new. Por tanto, una forma Tipo Descripcin Bits Rango de valores Alias
de asignar un valor a la variable p del ejemplo anterior sera as: SByte Bytes con signo 8 [-128, 127] sbyte
Byte Bytes sin signo 8 [0, 255] byte
Persona p; Int16 Enteros cortos con signo 16 [-32.768, 32.767] short
p = new Persona(Jos, 22, 76543876-A );

Jos Antonio Gonzlez Seco Pgina 93 Jos Antonio Gonzlez Seco Pgina 94
El lenguaje de programacin C# Tema 7: Variables y tipos de datos El lenguaje de programacin C# Tema 7: Variables y tipos de datos

UInt16 Enteros cortos sin signo 16 [0, 65.535] ushort Por su parte, en la Tabla 7 se indican los sufijos que admiten los literales reales son:
Int32 Enteros normales 32 [-2.147.483.648, 2.147.483.647] int
UInt32 Enteros normales sin signo 32 [0, 4.294.967.295] uint Sufijo Tipo del literal real
Int64 [-9.223.372.036.854.775.808, Ff float
Enteros largos 64 long
9.223.372.036.854.775.807] ninguno, D d double
UInt64 Enteros largos sin signo 64 [0-18.446.744.073.709.551.615] ulong Mm decimal
Single Reales con 7 dgitos de precisin 32 [1,510-45 - 3,41038] float
Tabla 7: Sufijos de literales reales
Double Reales de 15-16 dgitos de precisin 64 [5,010-324 - 1,710308] double
Decimal Reales de 28-29 dgitos de precisin 128 [1,010-28 - 7,91028] decimal
Boolean Valores lgicos 32 true, false bool Tablas
Char Caracteres Unicode 16 [\u0000, \uFFFF] char
String Cadenas de caracteres Variable El permitido por la memoria string Tablas unidimensionales
Object Cualquier objeto Variable Cualquier objeto object
Tabla 5: Tipos de datos bsicos Una tabla unidimensional o vector es un tipo especial de variable que es capaz de
almacenar en su interior y de manera ordenada uno o varios datos de un determinado
Pese a su sintaxis especial, en C# los tipos bsicos son tipos del mismo nivel que
tipo. Para declarar tablas se usa la siguiente sintaxis:
cualquier otro tipo del lenguaje. Es decir, heredan de System.Object y pueden ser
tratados como objetos de dicha clase por cualquier mtodo que espere un <tipoDatos>[] <nombreTabla>;
System.Object, lo que es muy til para el diseo de rutinas genricas que admitan
parmetros de cualquier tipo y es una ventaja importante de C# frente a lenguajes Por ejemplo, una tabla que pueda almacenar objetos de tipo int se declara as:
similares como Java donde los tipos bsicos no son considerados objetos.
int[] tabla;
El valor que por defecto se da a los campos de tipos bsicos consiste en poner a cero
todo el rea de memoria que ocupen. Esto se traduce en que los campos de tipos bsicos Con esto la tabla creada no almacenara ningn objeto, sino que valdra null. Si se desea
numricos se inicializan por defecto con el valor 0, los de tipo bool lo hacen con false, que verdaderamente almacene objetos hay que indicar cul es el nmero de objetos que
los de tipo char con \u0000, y los de tipo string y object con null. podr almacenar, lo que puede hacerse usando la siguiente sintaxis al declararla:

Ahora que sabemos cules son los tipos bsicos, es el momento de comentar cules son <tipoDatos>[] <nombreTabla> = new <tipoDatos>[<nmeroDatos>];
los sufijos que admiten los literales numricos para indicar al compilador cul es el tipo
que se ha de considerar que tiene. Por ejemplo, si tenemos en una clase los mtodos: Por ejemplo, una tabla que pueda almacenar 100 objetos de tipo int se declara as:
public static void F(int x) int[] tabla = new int[100];
{...}
public static void F(long x)
{...}
Aunque tambin sera posible definir el tamao de la tabla de forma separada a su
declaracin de este modo:
Ante una llamada como F(100), a cul de los mtodos se llamara? Pues bien, en
principio se considera que el tipo de un literal entero es el correspondiente al primero de int[] tabla;
tabla = new int[100];
estos tipos bsicos que permitan almacenarlo: int, uint, long, ulong, por lo que en el caso
anterior se llamara al primer F() Para llamar al otro podra aadirse el sufijo L al literal
Con esta ltima sintaxis es posible cambiar dinmicamente el nmero de elementos de
y hacer la llamada con F(100L) En la Tabla 6 se resumen los posibles sufijos vlidos:
una variable tabla sin ms que irle asignando nuevas tablas. Ello no significa que una
tabla se pueda redimensionar conservando los elementos que tuviese antes del cambio
Sufijo Tipo del literal entero
de tamao, sino que ocurre todo lo contrario: cuando a una variable tabla se le asigna
ninguno Primero de: int, uint, long, ulong una tabla de otro tamao, sus elementos antiguos son sobreescritos por los nuevos.
9
Ll Primero de: long, ulong
Uu Primero de: int, uint Si se crea una tabla con la sintaxis hasta ahora explicada todos sus elementos tendran el
UL, Ul, uL, ul, LU, Lu, lU lu ulong
valor por defecto de su tipo de dato. Si queremos darles otros valores al declarar la
Tabla 6: Sufijos de literales enteros tabla, hemos de indicarlos entre llaves usando esta sintaxis:

<tipoDatos>[] <nombreTabla> = new <tipoDatos>[] {<valores>};


9
No se recomienda usar el sufijo l, pues se parece mucho al nmero uno. De hecho, el compilador
produce un mensaje de aviso si se usa y puede que en versiones futuras genere un error.

Jos Antonio Gonzlez Seco Pgina 95 Jos Antonio Gonzlez Seco Pgina 96
El lenguaje de programacin C# Tema 7: Variables y tipos de datos El lenguaje de programacin C# Tema 7: Variables y tipos de datos

Han de especificarse tantos <valores> como nmero de elementos se desee que tenga la Tablas dentadas
tabla, y si son ms de uno se han de separar entre s mediante comas (,) Ntese que
ahora no es necesario indicar el nmero de elementos de la tabla (aunque puede hacerse Una tabla dentada no es ms que una tabla cuyos elementos son a su vez tablas,
si se desea), pues el compilador puede deducirlo del nmero de valores especificados. pudindose as anidar cualquier nmero de tablas. Para declarar tablas de este tipo se
Por ejemplo, para declarar una tabla de cuatro elementos de tipo int con valores 5,1,4,0 usa una sintaxis muy similar a la explicada para las tablas unidimensionales, slo que
se podra hacer lo siguiente: ahora se indican tantos corchetes como nivel de anidacin se desee. Por ejemplo, para
crear una tabla de tablas de elementos de tipo int formada por dos elementos, uno de los
int[] tabla = new int[] {5,1,4,0};
cuales fuese una tabla de elementos de tipo int formada por los elementos de valores 1,2
y el otro fuese una tabla de elementos de tipo int y valores 3,4,5, se puede hacer:
Incluso se puede compactar an ms la sintaxis declarando la tabla as:
int[][] tablaDentada = new int[2][] {new int[] {1,2}, new int[] {3,4,5}};
int[] tabla = {5,1,4,0};
Como se indica explcitamente cules son los elementos de la tabla declarada no hace
Tambin podemos crear tablas cuyo tamao se pueda establecer dinmicamente a partir
falta indicar el tamao de la tabla, por lo que la declaracin anterior es equivalente a:
del valor de cualquier expresin que produzca un valor de tipo entero. Por ejemplo, para
crear una tabla cuyo tamao sea el valor indicado por una variable de tipo int (luego su int[][] tablaDentada = new int[][] {new int[] {1,2}, new int[] {3,4,5}};
valor ser de tipo entero) se hara:
Es ms, igual que como se vi con las tablas unidimensionales tambin es vlido hacer:
int i = 5;
...
int[][] tablaDentada = {new int[] {1,2}, new int[] {3,4,5}};
int[] tablaDinmica = new int[i];

A la hora de acceder a los elementos almacenados en una tabla basta indicar entre Si no quisisemos indicar cules son los elementos de las tablas componentes, entonces
corchetes, y a continuacin de la referencia a la misma, la posicin que ocupe en la tabla tendramos que indicar al menos cul es el nmero de elementos que podrn almacenar
el elemento al que acceder. Cuando se haga hay que tener en cuenta que en C# las tablas (se inicializarn con valores por defecto) quedando:
se indexan desde 0, lo que significa que el primer elemento de la tabla ocupar su
int[][] tablaDentada = {new int[2], new int[3]};
posicin 0, el segundo ocupar la posicin 1, y as sucesivamente para el resto de
elementos. Por ejemplo, aunque es ms ineficiente, la tabla declarada en el ltimo
Si no queremos crear las tablas componentes en el momento de crear la tabla dentada,
fragmento de cdigo de ejemplo tambin podra haberse definido as:
entonces tendremos que indicar por lo menos cul es el nmero de tablas componentes
int[] tabla = new int[4];
posibles (cada una valdra null), con lo que quedara:

tabla[0] = 5; int[][] tablaDentada = new int[2][];


tabla[1]++; // Por defecto se inicializ a 0, luego ahora el valor de tabla[1] pasa a ser 1
tabla[2] = tabla[0] tabla[1]; // tabla[2] pasa a valer 4, pues 5-4 = 1 Es importante sealar que no es posible especificar todas las dimensiones de una tabla
dentada en su definicin si no se indica explcitamente el valor inicial de stas entre
// El contenido de la tabla ser {5,1,4,0}, pues tabla[3] se inicializ por defecto a 0.
llaves. Es decir, esta declaracin es incorrecta:
Hay que tener cuidado a la hora de acceder a los elementos de una tabla ya que si se int[][] tablaDentada = new int[2][5];
especifica una posicin superior al nmero de elementos que pueda almacenar la tabla
se producir una excepcin de tipo System.OutOfBoundsException. En el Tema 16: Esto se debe a que el tamao de cada tabla componente puede ser distinto y con la
Instrucciones se explica qu son las excepciones, pero por ahora basta considerar que sintaxis anterior no se puede decir cul es el tamao de cada una. Una opcin hubiese
son objetos que informan de situaciones excepcionales (generalmente errores) sido considerar que es 5 para todas como se hace en Java, pero ello no se ha
producidas durante la ejecucin de una aplicacin. Para evitar este tipo de excepciones implementado en C# y habra que declarar la tabla de, por ejemplo, esta manera:
puede consultar el valor del campo10 de slo lectura Length que est asociado a toda
tabla y contiene el nmero de elementos de la misma. Por ejemplo, para asignar un 7 al int[][] tablaDentada = {new int[5], new int[5]);
ltimo elemento de la tabla anterior se hara:
Finalmente, si slo queremos declarar una variable tabla dentada pero no queremos
tabla[tabla.Length 1] = 7; // Se resta 1 porque tabla.Length devuelve 4 pero el ltimo indicar su nmero de elementos, (luego la variable valdra null), entonces basta poner:
// elemento de la tabla es tabla[3]
int[][] tablaDentada;
10
Length es en realidad una propiedad, pero por ahora podemos considerar que es campo.

Jos Antonio Gonzlez Seco Pgina 97 Jos Antonio Gonzlez Seco Pgina 98
El lenguaje de programacin C# Tema 7: Variables y tipos de datos El lenguaje de programacin C# Tema 7: Variables y tipos de datos

Hay que precisar que aunque en los ejemplos hasta ahora presentes se han escrito
ejemplos basados en tablas dentadas de slo dos niveles de anidacin, tambin es int[,] tablaMultidimensional = new int[3,4];
posible crear tablas dentadas de cualquier nmero de niveles de anidacin. Por ejemplo,
para una tabla de tablas de tablas de enteros de 2 elementos en la que el primero fuese Tambin podemos no especificar ni siquiera el nmero de elementos de la tabla de esta
una tabla dentada formada por dos tablas de 5 enteros y el segundo elemento fuese una forma (tablaMultidimensional contendra ahora null):
tabla dentada formada por una tabla de 4 enteros y otra de 3 se podra definir as:
int[,] tablaMultidimensional;
int[][][] tablaDentada = new int[][][] { new int[][] {new int[5], new int[5]},
new int[][] {new int[4], new int[3]}}; Aunque los ejemplos de tablas multidimensionales hasta ahora mostrados son de tablas
de dos dimensiones, en general tambin es posible crear tablas de cualquier nmero de
A la hora de acceder a los elementos de una tabla dentada lo nico que hay que hacer es dimensiones. Por ejemplo, una tabla que almacene 24 elementos de tipo int y valor 0 en
indicar entre corchetes cul es el elemento exacto de las tablas componentes al que se una estructura tridimensional 3x4x2 se declarara as:
desea acceder, indicndose un elemento de cada nivel de anidacin entre unos corchetes
diferentes pero colocndose todas las parejas de corchetes juntas y ordenadas de la tabla int[,,] tablaMultidimensional = new int[3,4,2];
ms externa a la ms interna. Por ejemplo, para asignar el valor 10 al elemento cuarto de
la tabla que es elemento primero de la tabla que es elemento segundo de la tabla dentada El acceso a los elementos de una tabla multidimensional es muy sencillo: slo hay que
declarada en ltimo lugar se hara: indicar los ndices de la posicin que ocupe en la estructura multidimensional el
elemento al que se desee acceder. Por ejemplo, para incrementar en una unidad el
tablaDentada[1][0][3] = 10; elemento que ocupe la posicin (1,3,2) de la tabla anterior se hara (se indiza desde 0):

tablaMultidimensional[0,2,1]++;
Tablas multidimensionales
Ntese que tanto las tablas dentadas como las tablas multidimensionales pueden ser
Una tabla multidimensional o matriz es aquella cuyos elementos se encuentran utilizadas tanto para representar estructuras matriciales como para, en general,
organizados en una estructura de varias dimensiones. Para definirlas se utiliza una representar cualquier estructura de varias dimensiones. La diferencia entre ambas son:
sintaxis similar a la usada para declarar tablas unidimensionales pero separando las
diferentes dimensiones mediante comas (,) Por ejemplo, una tabla multidimensional de Como las tablas dentadas son tablas de tablas, cada uno de sus elementos puede
elementos de tipo int que conste de 12 elementos puede tener sus elementos distribuidos ser una tabla de un tamao diferente. As, con las tablas dentadas podemos
en dos dimensiones formando una estructura 3x4 similar a una matriz de la forma: representar matrices en las que cada columna tenga un tamao distinto (por el
aspecto aserrado de este tipo de matrices es por lo que se les llama tablas
1 2 3 4 dentadas), mientras que usando tablas multidimensionales slo es posible crear
5 6 7 8 matrices rectangulares o cuadradas. Las estructuras aserradas pueden simularse
9 10 11 12 usando matrices multidimensionales con todas sus columnas del tamao de la
columna ms grande necesaria, aunque ello implica desperdiciar mucha
Esta tabla se podra declarar as: memoria sobre todo si los tamaos de cada columna son muy diferentes y la
tabla es grande. De todos modos, las estructuras ms comunes que se usan en la
int[,] tablaMultidimensional = new int[3,4] {{1,2,3,4}, {5,6,7,8}, {9,10,11,12}}; mayora de aplicaciones suelen ser rectangulares o cuadradas.

En realidad no es necesario indicar el nmero de elementos de cada dimensin de la Los tiempos que se tardan en crear y destruir tablas dentadas son superiores a
tabla ya que pueden deducirse de los valores explcitamente indicados entre llaves, por los que se tardan en crear y destruir tablas multidimensionales. Esto se debe a
lo que la definicin anterior es similar a esta: que las primeras son tablas de tablas mientras que las segundas son una nica
tabla, Por ejemplo, para crear una tabla dentada [100][100] hay que crear 101
int[,] tablaMultidimensional = new int[,] {{1,2,3,4}, {5,6,7,8}, {9,10,11,12}}; tablas (la tabla dentada ms las 100 tablas que contiene), mientras que para
crear una crear una tabla bidimensional [100,100] hay que crear una nica tabla.
Incluso puede reducirse an ms la sintaxis necesaria quedando tan slo:
Las tablas dentadas no forman parte del CLS, por lo que no todos los lenguajes
int[,] tablaMultidimensional = {{1,2,3,4}, {5,6,7,8}, {9,10,11,12}}; gestionados los tienen porqu admitir. Por ejemplo Visual Basic.NET no las
admite, por lo que al usarlas en miembros pblicos equivale a perder
Si no queremos indicar explcitamente los elementos de la tabla al declararla, podemos interoperabilidad con estos lenguajes.
obviarlos pero an as indicar el tamao de cada dimensin de la tabla (a los elementos
se les dara el valor por defecto de su tipo de dato) as:

Jos Antonio Gonzlez Seco Pgina 99 Jos Antonio Gonzlez Seco Pgina 100
El lenguaje de programacin C# Tema 7: Variables y tipos de datos El lenguaje de programacin C# Tema 7: Variables y tipos de datos

Tablas mixtas
int[] tabla = {1,2,3,4};
int[][] tabla2 = {new int[] {1,2}, new int[] {3,4,5}};
Una tabla mixta es simplemente una tabla formada por tablas multidimensionales y int[,] tabla3 = {{1,2},{3,4,5,6}};
dentadas combinadas entre s de cualquier manera. Para declarar una tabla de este tipo
basta con tan solo combinar las notaciones ya vistas para las multidimensionales y Console.WriteLine(tabla.Rank); //Imprime 1
dentadas. Por ejemplo, para declarar una tabla de tablas multidimensionales cuyos Console.WriteLine(tabla2.Rank); //Imprime 1
Console.WriteLine(tabla3.Rank); //Imprime 2
elementos sean tablas unidimensionales de enteros se hara lo siguiente:

int[][,][] tablaMixta; int GetLength(int dimensin): Mtodo que devuelve el nmero de elementos de
la dimensin especificada. Las dimensiones se indican empezando a contar
desde cero, por lo que si quiere obtenerse el nmero de elementos de la primera
Covarianza de tablas dimensin habr que usar GetLength(0), si se quiere obtener los de la segunda
habr que usar GetLength(1), etc. Por ejemplo:
La covarianza de tablas es el resultado de llevar el polimorfismo al mundo de las
int[,] tabla = {{1,2}, {3,4,5,6}};
tablas. Es decir, es la capacidad de toda tabla de poder almacenar elementos de clases
hijas de la clase de elementos que pueda almacenar. Por ejemplo, en tanto que todas Console.WriteLine(tabla.GetLength(0)); // Imprime 2
clases son hijas de System.Object, la siguiente asignacin es vlida: Console.WriteLine(<gtabla.GetLength(1)); // Imprime 4

string[] tablaCadenas = {Manolo, Paco, Pepe}; void CopyTo(Array destino, int posicin): Copia todos los elementos de la tabla
object[] tablaObjetos = tablaCadenas; sobre la que se aplica en la tabla destino a partir de la posicin de sta indicada.
Por ejemplo:
Hay que tener en cuenta que la covarianza de tablas slo se aplica a objetos de tipos
referencia y no a objetos de tipos valor Por ejemplo, la siguiente asignacin no sera int[] tabla1 = {1,2,3,4};
vlida en tanto que int es un tipo por valor: int[] tabla2 = {5,6,7,8, 9};

int[] tablaEnteros = {1, 2, 3}; tabla1.CopyTo(tabla2,0); // A partir de ahora, tabla2 contendr {5,1,2,3,4}
object[] tablaObjetos = tablaEnteros;
Ambas tablas deben ser unidimensionales, la tabla de destino hade ser de un tipo
que pueda almacenar los objetos de la tabla origen, el ndice especificado ha de
La clase System.Array ser vlido (mayor o igual que cero y menor que el tamao de la tabla de destino)
y no ha de valer null ninguna de las tablas. Si no fuese as, saltaran excepciones
En realidad, todas las tablas que definamos, sea cual sea el tipo de elementos que de diversos tipos informando del error cometido (en la documentacin del SDK
contengan, son objetos que derivan de System.Array. Es decir, van a disponer de todos puede ver cules son en concreto)
los miembros que se han definido para esta clase, entre los que son destacables:
Aparte de los miembros aqu sealados, System.Array tambin cuenta con muchos otros
Length: Campo
11
de slo lectura que informa del nmero total de elementos que que facilitan realizar tareas tan frecuentes como bsquedas de elementos, ordenaciones,
contiene la tabla. Si la tabla tiene ms de una dimensin o nivel de anidacin etc. Para ms informacin sobre ellos puede consultarse la documentacin del SDK.
indica el nmero de elementos de todas sus dimensiones y niveles. Por ejemplo:

int[] tabla = {1,2,3,4}; Cadenas de texto


int[][] tabla2 = {new int[] {1,2}, new int[] {3,4,5}};
int[,] tabla3 = {{1,2},{3,4,5,6}};
Una cadena de texto no es ms que una secuencia de caracteres. .NET las representa
Console.WriteLine(tabla.Length); //Imprime 4 internamente en formato Unicode, y C# las representan externamente como objetos de
Console.WriteLine(tabla2.Length); //Imprime 5 un tipo de dato string, que no es ms que un alias del tipo System.String de la BCL.
Console.WriteLine(tabla3.Length); //Imprime 6
Las cadenas de texto suelen crearse a partir literales de cadena o de otras cadenas
Rank: Campo de slo lectura que almacena el nmero de dimensiones de la previamente creadas. Ejemplos de ambos casos se muestran a continuacin:
tabla. Obviamente si la tabla no es multidimensional valdr 1. Por ejemplo:
string cadena1 = Jos Antonio;
11 string cadena2 = cadena1;
En realidad todos los campos descritos en este apartado no son en realidad campos, sino propiedades.
Aunque son conceptos diferentes, por ahora puede considerarlos como iguales.

Jos Antonio Gonzlez Seco Pgina 101 Jos Antonio Gonzlez Seco Pgina 102
El lenguaje de programacin C# Tema 7: Variables y tipos de datos El lenguaje de programacin C# Tema 7: Variables y tipos de datos

En el primer caso se ha creado un objeto string que representa a la cadena formada por
la secuencia de caracteres Jos Antonio indicada literalmente (ntese que las comillas using System;
dobles entre las que se encierran los literales de cadena no forman parte del contenido public class IgualdadCadenas2
de la cadena que representan sino que slo se usan como delimitadores de la misma) En {
el segundo caso la variable cadena2 creada se genera a partir de la variable cadena1 ya public static void Main()
existente, por lo que ambas variables apuntarn al mismo objeto en memoria. {
string cadena1 = Jos Antonio;
string cadena2 = Jos Antonio;
Hay que tener en cuenta que el tipo string es un tipo referencia, por lo que en principio
la comparacin entre objetos de este tipo debera comparar sus direcciones de memoria Console.WriteLine(Object.ReferenceEquals(cadena1, cadena2));
como pasa con cualquier tipo referencia. Sin embargo, si ejecutamos el siguiente cdigo Console.WriteLine( ((object) cadena1) == ((object) cadena2));
veremos que esto no ocurre en el caso de las cadenas: }
}
using System;
Si lo ejecutamos veremos que la salida obtenida es justamente la contraria:
public class IgualdadCadenas
{ True
public static void Main() True
{
string cadena1 = Jos Antonio; Esto se debe a que el compilador ha detectado que ambos literales de cadena son
string cadena2 = String.Copy(cadena1); lexicogrficamente equivalentes y ha decidido que para ahorra memoria lo mejor es
almacenar en memoria una nica copia de la cadena que representan y hacer que ambas
Console.WriteLine(cadena1==cadena2);
} variables apunten a esa copia comn. Esto va a afectar a la forma en que es posible
} manipular las cadenas como se explicar ms adelante.

El mtodo Copy() de la clase String usado devuelve una copia del objeto que se le pasa Al igual que el significado del operador == ha sido especialmente modificado para
como parmetro. Por tanto, al ser objetos diferentes se almacenarn en posiciones trabajar con cadenas, lo mismo ocurre con el operador binario +. En este caso, cuando se
distintas de memoria y al compararlos debera devolverse false como pasa con cualquier aplica entre dos cadenas o una cadena y un carcter lo que hace es devolver una nueva
tipo referencia. Sin embargo, si ejecuta el programa ver que lo que se obtiene es cadena con el resultado de concatenar sus operandos. As por ejemplo, en el siguiente
precisamente lo contrario: true. Esto se debe a que para hacer para hacer ms intuitivo el cdigo las dos variables creadas almacenarn la cadena Hola Mundo:
trabajo con cadenas, en C# se ha modificado el operador de igualdad para que cuando se
aplique entre cadenas se considere que sus operandos son iguales slo si son public class Concatenacin
lexicogrficamente equivalentes y no si referencian al mismo objeto en memoria. {
public static void Main()
Adems, esta comparacin se hace teniendo en cuenta la capitalizacin usada, por lo {
que Hola==HOLA Hola==hola devolvern false ya que contienen las mismas letras string cadena = Hola + Mundo;
pero con distinta capitalizacin. string cadena2 = Hola Mund + o;
}
Si se quisiese comparar cadenas por referencia habra que optar por una de estas dos }
opciones: compararlas con Object.ReferenceEquals() o convertirlas en objects y luego
Por otro lado, el acceso a las cadenas se hace de manera similar a como si de tablas de
compararlas con == Por ejemplo:
caracteres se tratase: su campo Length almacenar el nmero de caracteres que la
Console.WriteLine(Object.ReferecenceEquals(cadena1, cadena2)); forman y para acceder a sus elementos se utiliza el operador []. Por ejemplo, el siguiente
Console.WriteLine( (object) cadena1 == (object) cadena2); cdigo muestra por pantalla cada carcter de la cadena Hola en una lnea diferente:

Ahora s que lo que se comparan son las direcciones de los objetos que representan a las using System;
cadenas en memoria, por lo que la salida que se mostrar por pantalla es:
public class AccesoCadenas
False {
False public static void Main()
{
string cadena = Hola;
Hay que sealar una cosa, y es que aunque en principio el siguiente cdigo debera
mostrar la misma salida por pantalla que el anterior ya que las cadenas comparadas se Console.WriteLine(cadena[0]);
deberan corresponder a objetos que aunque sean lexicogrficamente equivalentes se Console.WriteLine(cadena[1]);
almacenan en posiciones diferentes en memoria: Console.WriteLine(cadena[2]);

Jos Antonio Gonzlez Seco Pgina 103 Jos Antonio Gonzlez Seco Pgina 104
El lenguaje de programacin C# Tema 7: Variables y tipos de datos El lenguaje de programacin C# Tema 7: Variables y tipos de datos

Console.WriteLine(cadena[3]); la cadena sobre la que se busca, pero pasando un tercer parmetro opcional de tipo
} int es posible indicar algn ndice anterior donde terminarla.
}

Sin embargo, hay que sealar una diferencia importante respecto a la forma en que se Ntese que es un mtodo muy til para saber si una cadena contiene o no alguna
accede a las tablas: las cadenas son inmutables, lo que significa que no es posible subcadena determinada, pues slo si no la encuentra devuelve un 1.
modificar los caracteres que las forman. Esto se debe a que el compilador comparte en
memoria las referencias a literales de cadena lexicogrficamente equivalentes para as int LastIndexOf(string subcadena): Funciona de forma similar a IndexOf() slo que
ahorrar memoria, y si se permitiese modificarlos los cambios que se hiciesen a travs de devuelve la posicin de la ltima aparicin de la subcadena buscada en lugar de
una variable a una cadena compartida afectaran al resto de variables que la compartan, devolver la de la primera.
lo que podra causar errores difciles de detectar. Por tanto, hacer esto es incorrecto:
string Insert(int posicin, string subcadena): Devuelve la cadena resultante de
string cadena = Hola; insertar la subcadena indicada en la posicin especificada de la cadena sobre la que
cadena[0]=A; //Error: No se pueden modificar las cadenas se aplica.
Sin embargo, el hecho de que no se puedan modificar las cadenas no significa que no se
string Remove(int posicin, int nmero): Devuelve la cadena resultante de eliminar
puedan cambiar los objetos almacenados en las variables de tipo string.Por ejemplo, el
el nmero de caracteres indicado que hubiese en la cadena sobre al que se aplica a
siguiente cdigo es vlido:
partir de la posicin especificada.
String cad = Hola;
cad = Adios; // Correcto, pues no se modifica la cadena almacenada en cad string Replace(string aSustituir, string sustituta): Devuelve la cadena resultante de
// sino que se hace que cad pase a almacenar otra cadena distinta.. sustituir en la cadena sobre la que se aplica toda aparicin de la cadena aSustituir
indicada por la cadena sustituta especificada como segundo parmetro.
Si se desea trabajar con cadenas modificables puede usarse Sytem.Text.StringBuilder,
que funciona de manera similar a string pero permite la modificacin de sus cadenas en
string Substring(int posicin, int nmero): Devuelve la subcadena de la cadena sobre
tanto que estas no se comparten en memoria. Para crear objetos de este tipo basta pasar
la que se aplica que comienza en la posicin indicada y tiene el nmero de caracteres
como parmetro de su constructor el objeto string que contiene la cadena a representar
especificados. Si no se indica dicho nmero se devuelve la subcadena que va desde
mediante un StringBuilder, y para convertir un StringBuilder en String siempre puede
la posicin indicada hasta el final de la cadena.
usarse su mtodo ToString() heredado de System.Object. Por ejemplo:
using System.Text; string ToUpper() y string ToLower(): Devuelven, respectivamente, la cadena que
using System; resulte de convertir a maysculas o minsculas la cadena sobre la que se aplican.
public class ModificacinCadenas Es preciso incidir en que aunque hayan mtodos de insercin, reemplazo o eliminacin
{
public static void Main() de caracteres que puedan dar la sensacin de que es posible modificar el contenido de
{ una cadena, en realidad las cadenas son inmutables y dicho mtodos lo que hacen es
StringBuilder cadena = new StringBuilder(Pelas); devolver una nueva cadena con el contenido correspondiente a haber efectuado las
String cadenaInmutable; operaciones de modificacin solicitadas sobre la cadena a la que se aplican. Por ello, las
cadenas sobre las que se aplican quedan intactas como muestra el siguiente ejemplo:
cadena[0] = V;
Console.WriteLine(cadena); // Muestra Velas
using System;
cadenaInmutable = cadena.ToString();
Console.WriteLine(cadenaInmutable); // Muestra Velas
public class EjemploInmutabilidad
}
{
}
public static void Main()
{
Aparte de los mtodos ya vistos, en la clase System.String se definen muchos otros string cadena1=Hola;
mtodos aplicables a cualquier cadena y que permiten manipularla. Los principales son: string cadena2=cadena1.Remove(0,1);

int IndexOf(string subcadena): Indica cul es el ndice de la primera aparicin de la Console.WriteLine(cadena1);


Console.WriteLine(cadena2);
subcadena indicada dentro de la cadena sobre la que se aplica. La bsqueda de dicha }
subcadena se realiza desde el principio de la cadena, pero es posible indicar en un }
segundo parmetro opcional de tipo int cul es el ndice de la misma a partir del que
se desea empezar a buscar. Del mismo modo, la bsqueda acaba al llegar al final de La salida por pantalla de este ejemplo demuestra lo antes dicho, pues es:

Jos Antonio Gonzlez Seco Pgina 105 Jos Antonio Gonzlez Seco Pgina 106
El lenguaje de programacin C# Tema 7: Variables y tipos de datos El lenguaje de programacin C# Tema 7: Variables y tipos de datos

Hola
ola

Como se ve, tras el Remove() la cadena1 permanece intacta y el contenido de cadena2 es


Variables de slo lectura
el que debera tener cadena1 si se le hubiese eliminado su primer carcter.
Dado que hay ciertos casos en los que resulta interesante disponer de la capacidad de
slo lectura que tienen las constantes pero no es posible usarlas debido a las
Constantes restricciones que hay impuestas sobre su uso, en C# tambin se da la posibilidad de
definir variables que slo puedan ser ledas. Para ello se usa la siguiente sintaxis:
Una constante es una variable cuyo valor puede determinar el compilador durante la readonly <tipoConstante> <nombreConstante> = <valor>;
compilacin y puede aplicar optimizaciones derivadas de ello. Para que esto sea posible
se ha de cumplir que el valor de una constante no pueda cambiar durante la ejecucin, Estas variables superan la mayora de las limitaciones de las constantes. Por ejemplo:
por lo que el compilador informar con un error de todo intento de modificar el valor
inicial de una constante. Las constantes se definen como variables normales pero
No es obligatorio darles un valor al definirlas, sino que puede drseles en el
precediendo el nombre de su tipo del modificador const y dndoles siempre un valor
constructor. Ahora bien, una vez dado un valor a una variable readonly ya no es
inicial al declararlas. O sea, con esta sintaxis: posible volverlo a modificar. Si no se le da ningn valor ni en su constructor ni
const <tipoConstante> <nombreConstante> = <valor>;
en su definicin tomar el valor por defecto correspondiente a su tipo de dato.

As, ejemplos de definicin de constantes es el siguiente: No tienen porqu almacenar valores constantes, sino que el valor que almacenen
puede calcularse durante la ejecucin de la aplicacin.
const int a = 123;
const int b = a + 125; No tienen porqu definirse como estticas, aunque si se desea puede hacerse.

Dadas estas definiciones de constantes, lo que har el compilador ser sustituir en el Su valor se determina durante la ejecucin de la aplicacin, lo que permite la
cdigo generado todas las referencias a las constantes a y b por los valores 123 y 248 actualizacin de cdigos cliente sin necesidad de recompilar. Por ejemplo, dado:
respectivamente, por lo que el cdigo generado ser ms eficiente ya que no incluir el
acceso y clculo de los valores de a y b. Ntese que puede hacer esto porque en el namespace Programa1
cdigo se indica explcitamente cual es el valor que siempre tendr a y, al ser este un {
valor fijo, puede deducir cul ser el valor que siempre tendr b. Para que el compilador public class Utilidad
{
pueda hacer estos clculos se ha de cumplir que el valor que se asigne a las constantes public static readonly int X = 1;
en su declaracin sea una expresin constante. Por ejemplo, el siguiente cdigo no es }
vlido en tanto que el valor de x no es constante: }
namespace Programa2
int x = 123; // x es una variable normal, no una constante {
const int y = x +123; // Error: x no tiene porqu tener valor constante (aunque aqu lo tenga) class Test
{
public static void Main() {
Debido a la necesidad de que el valor dado a una constante sea precisamente constante, System.Console.WriteLine(Programa1.Utilidad.X);
no tiene mucho sentido crear constantes de tipos de datos no bsicos, pues a no ser que }
valgan null sus valores no se pueden determinar durante la compilacin sino nicamente }
tras la ejecucin de su constructor. La nica excepcin a esta regla son los tipos }
enumerados, cuyos valores se pueden determinar al compilar como se explicar cuando
los veamos en el Tema 14: Enumeraciones En principio, la ejecucin de este programa producir el valor 1. Sin embargo, si
cada espacio de nombres se compilan en mdulos de cdigo separados que luego
Todas las constantes son implcitamente estticas, por lo se considera errneo incluir el se enlazan dinmicamente y cambiamos el valor de X, slo tendremos que
modificador static en su definicin al no tener sentido hacerlo. De hecho, para leer su recompilar el mdulo donde est definido Programa1.Utilidad y Programa2.Test
valor desde cdigos externos a la definicin de la clase donde est definida la constante, podr ejecutarse usando el nuevo valor de X sin necesidad de recompilarlo.
habr que usar la sintaxis <nombreClase>.<nombreConstante> tpica de los campos static.
Sin embargo, pese a las ventajas que las variables de slo lectura ofrecen
Por ltimo, hay que tener en cuenta que una variable slo puede ser definida como respecto a las constantes, tienen dos inconvenientes respecto a stas: slo
constante si es una variable local o un campo, pero no si es un parmetro.

Jos Antonio Gonzlez Seco Pgina 107 Jos Antonio Gonzlez Seco Pgina 108
El lenguaje de programacin C# Tema 7: Variables y tipos de datos El lenguaje de programacin C# Tema 7: Variables y tipos de datos

pueden definirse como campos (no como variables locales) y con ellas no es Esto slo es posible hacerlo al definir campos estticos y no entre campos no estticas o
posible realizar las optimizaciones de cdigo comentadas para las constantes. variables locales, ya que no se puede inicializar campos no estticos en funcin del
valor de otros miembros no estticos del mismo objeto porque el objeto an no estara
inicializado, y no se pueden inicializar variables locales en funcin del valor de otras
Orden de inicializacin de variables variables locales definidas ms adelante porque no se pueden leer variables no
inicializadas. Adems, aunque las constantes sean implcitamente estticas tampoco
Para deducir el orden en que se inicializarn las variables de un tipo de dato basta saber puede hacerse definiciones cclicas entre constantes.
cul es el momento en que se inicializa cada una y cuando se llama a los constructores:
En primer lugar, hay que sealar que escribir un cdigo como el del ejemplo anterior no
Los campos estticos slo se inicializan la primera vez que se accede al tipo al es un buen hbito de programacin ya que dificulta innecesariamente la legibilidad del
que pertenecen, pero no en sucesivos accesos. Estos accesos pueden ser tanto programa. An as, C# admite este tipo de cdigos y para determinar el valor con que se
para crear objetos de dicho tipo como para acceder a sus miembros estticos. La inicializarn basta tener en cuenta que siempre se inicializan primero todos los campos
inicializacin se hace de modo que en primer lugar se d a cada variable el valor con sus valores por defecto y luego se inicializan aquellos que tengan valores iniciales
por defecto correspondiente a su tipo, luego se d a cada una el valor inicial con dichos valores iniciales y en el mismo orden en que aparezcan en el cdigo fuente.
especificado al definirlas, y por ltimo se llame al constructor del tipo. Un De este modo, la salida del programa de ejemplo anterior ser:
constructor de tipo es similar a un constructor normal slo que en su cdigo
a = 1, b = 2
nicamente puede accederse a miembros static (se ver en el Tema 8: Mtodos)
Ntese que lo que se ha hecho es inicializar primero a y b con sus valores por defecto (0
en este caso), luego calcular el valor final de a y luego calcular el valor final de b. Como
Los campos no estticos se inicializan cada vez que se crea un objeto del tipo
b vale 0 cuando se calcula el valor final de a, entonces el valor final de a es 1; y como a
de dato al que pertenecen. La inicializacin se hace del mismo modo que en el
vale 1 cuando se calcula el valor final de b, entonces el valor final de b es 2.
caso de los campos estticos, y una vez terminada se pasa a ejecutar el cdigo
del constructor especificado al crear el objeto. En caso de que la creacin del
objeto sea el primer acceso que se haga al tipo de dato del mismo, entonces
primero se inicializarn los campos estticos y luego los no estticos.

Los parmetros se inicializan en cada llamada al mtodo al que pertenecen con


los valores especificados al llamarlo.

Las variables locales se inicializan en cada llamada al mtodo al cual


pertenecen pero tras haberse inicializado los parmetros definidos para el
mismo. Si no se les da valor inicial no toman ninguno por defecto,
considerndose errneo todo acceso de lectura que se haga a las mismas
mientras no se les escriba algn valor.

Hay que tener en cuenta que al definirse campos estticos pueden hacerse definiciones
cclicas en las que el valor de unos campos dependa del de otros y el valor de los
segundos dependa del de los primeros. Por ejemplo:

class ReferenciasCruzadas
{
static int a = b + 1;
static int b = a + 1;

public static void Main()


{
System.Console.WriteLine("a = {0}, b = {1}", a, b);
}
}

Jos Antonio Gonzlez Seco Pgina 109 Jos Antonio Gonzlez Seco Pgina 110
El lenguaje de programacin C# Tema 8: Mtodos El lenguaje de programacin C# Tema 8: Mtodos

El <cuerpo> del mtodo tambin es opcional, pero si el mtodo retorna algn tipo de
TEMA 8: Mtodos objeto entonces ha de incluir al menos una instruccin return que indique cul objeto.

La sintaxis anteriormente vista no es la que se usa para definir mtodos abstractos.


Concepto de mtodo Como ya se vio en el Tema 5: Clases, en esos casos lo que se hace es sustituir el cuerpo
del mtodo y las llaves que lo encierran por un simple punto y coma (;) Ms adelante en
este tema veremos que eso es tambin lo que se hace para definir mtodos externos.
Un mtodo es un conjunto de instrucciones a las que se les da un determinado nombre
de tal manera que sea posible ejecutarlas en cualquier momento sin tenerlas que A continuacin se muestra un ejemplo de cmo definir un mtodo de nombre Saluda
rescribir sino usando slo su nombre. A estas instrucciones se les denomina cuerpo del cuyo cuerpo consista en escribir en la consola el mensaje Hola Mundo y que devuelva
mtodo, y a su ejecucin a travs de su nombre se le denomina llamada al mtodo. un objeto int de valor 1:
La ejecucin de las instrucciones de un mtodo puede producir como resultado un int Saluda()
objeto de cualquier tipo. A este objeto se le llama valor de retorno del mtodo y es {
completamente opcional, pudindose escribir mtodos que no devuelvan ninguno. Console.WriteLine(Hola Mundo);
return 1;
}
La ejecucin de las instrucciones de un mtodo puede depender del valor de unas
variables especiales denominadas parmetros del mtodo, de manera que en funcin
del valor que se d a estas variables en cada llamada la ejecucin del mtodo se pueda
realizar de una u otra forma y podr producir uno u otro valor de retorno.
Llamada a mtodos

Al conjunto formado por el nombre de un mtodo y el nmero y tipo de sus parmetros La forma en que se puede llamar a un mtodo depende del tipo de mtodo del que se
se le conoce como signatura del mtodo. La signatura de un mtodo es lo que trate. Si es un mtodo de objeto (mtodo no esttico) se ha de usar la notacin:
verdaderamente lo identifica, de modo que es posible definir en un mismo tipo varios
mtodos con idntico nombre siempre y cuando tengan distintos parmetros. Cuando <objeto>.<nombreMtodo>(<valoresParmetros>)
esto ocurre se dice que el mtodo que tiene ese nombre est sobrecargado.
El <objeto> indicado puede ser directamente una variable del tipo de datos al que
pertenezca el mtodo o puede ser una expresin que produzca como resultado una
Definicin de mtodos variable de ese tipo (recordemos que, debido a la herencia, el tipo del <objeto> puede ser
un subtipo del tipo donde realmente se haya definido el mtodo); pero si desde cdigo
de algn mtodo de un objeto se desea llamar a otro mtodo de ese mismo objeto,
Para definir un mtodo hay que indicar tanto cules son las instrucciones que forman su entonces se ha de dar el valor this a <objeto>.
cuerpo como cul es el nombre que se le dar, cul es el tipo de objeto que puede
devolver y cules son los parmetros que puede tomar. Esto se indica definindolo as: En caso de que sea un mtodo de tipo (mtodo esttico), entones se ha de usar:
<tipoRetorno> <nombreMtodo>(<parmetros>) <tipo>.<nombreMtodo>(<valoresParmetros>)
{
<cuerpo>
} Ahora en <tipo> ha de indicarse el tipo donde se haya definido el mtodo o algn subtipo
suyo. Sin embargo, si el mtodo pertenece al mismo tipo que el cdigo que lo llama
En <tipoRetorno> se indica cul es el tipo de dato del objeto que el mtodo devuelve, y si entonces se puede usar la notacin abreviada:
no devuelve ninguno se ha de escribir void en su lugar.
<nombreMtodo>(<valoresParmetros>)
Como nombre del mtodo se puede poner en <nombreMtodo> cualquier identificador
vlido. Como se ver ms adelante en el Tema 15: Interfaces, tambin es posible incluir El formato en que se pasen los valores a cada parmetro en <valoresParmetros> a
en <nombreMtodo> informacin de explicitacin de implementacin de interfaz, pero aquellos mtodos que tomen parmetros depende del tipo de parmetro que sea. Esto se
por ahora podemos considerar que siempre ser un identificador. explica en el siguiente apartado.

Aunque es posible escribir mtodos que no tomen parmetros, si un mtodo los toma se
ha de indicar en <parmetros> cul es el nombre y tipo de cada uno, separndolos con Tipos de parmetros. Sintaxis de definicin
comas si son ms de uno y siguiendo la sintaxis que ms adelante se explica.

Jos Antonio Gonzlez Seco Pgina 111 Jos Antonio Gonzlez Seco Pgina 112
El lenguaje de programacin C# Tema 8: Mtodos El lenguaje de programacin C# Tema 8: Mtodos

La forma en que se define cada parmetro de un mtodo depende del tipo de parmetro public static void Main()
del que se trate. En C# se admiten cuatro tipos de parmetros: parmetros de entrada, {
int obj1 = 0;
parmetros de salida, parmetros por referencia y parmetros de nmero indefinido. ParmetrosEntrada obj2 = new ParmetrosEntrada();

G(obj1);
Parmetros de entrada F(obj2);

Console.WriteLine({0}, {1}, obj1, obj2.a);


Un parmetro de entrada recibe una copia del valor que almacenara una variable del }
tipo del objeto que se le pase. Por tanto, si el objeto es de un tipo valor se le pasar una
copia del objeto y cualquier modificacin que se haga al parmetro dentro del cuerpo }
del mtodo no afectar al objeto original sino a su copia; mientras que si el objeto es de
un tipo referencia entonces se le pasar una copia de la referencia al mismo y cualquier Este programa muestra la siguiente salida por pantalla:
modificacin que se haga al parmetro dentro del mtodo tambin afectar al objeto
original ya que en realidad el parmetro referencia a ese mismo objeto original. 0, 2

Para definir un parmetro de entrada basta indicar cul el nombre que se le desea dar y Como se ve, la llamada al mtodo G() no modifica el valor que tena obj1 antes de
el cul es tipo de dato que podr almacenar. Para ello se sigue la siguiente sintaxis: llamarlo ya que obj1 es de un tipo valor (int) Sin embargo, como obj2 es de un tipo
referencia (ParmetrosLlamadas) los cambios que se le hacen dentro de F() al pasrselo
<tipoParmetro> <nombreParmetro> como parmetro s que le afectan.

Por ejemplo, el siguiente cdigo define un mtodo llamado Suma que toma dos
parmetros de entrada de tipo int llamados par1 y par2 y devuelve un int con su suma: Parmetros de salida

int Suma(int par1, int par2) Un parmetro de salida se diferencia de uno de entrada en que todo cambio que se le
{ realice en el cdigo del mtodo al que pertenece afectar al objeto que se le pase al
return par1+par2;
}
llamar dicho mtodo tanto si ste es de un tipo por valor como si es de un tipo
referencia. Esto se debe a que lo que a estos parmetros se les pasa es siempre una
Como se ve, se usa la instruccin return para indicar cul es el valor que ha de devolver referencia al valor que almacenara una variable del tipo del objeto que se les pase.
el mtodo. Este valor es el resultado de ejecutar la expresin par1+par2; es decir, es la
suma de los valores pasados a sus parmetros par1 y par2 al llamarlo. Cualquier parmetro de salida de un mtodo siempre ha de modificarse dentro del
cuerpo del mtodo y adems dicha modificacin ha de hacerse antes que cualquier
En las llamadas a mtodos se expresan los valores que se deseen dar a este tipo de lectura de su valor. Si esto no se hiciese as el compilador lo detectara e informara de
parmetros indicando simplemente el valor deseado. Por ejemplo, para llamar al mtodo ello con un error. Por esta razn es posible pasar parmetros de salida que sean variables
anterior con los valores 2 y 5 se hara <objeto>.Suma(2,5), lo que devolvera el valor 7. no inicializadas, pues se garantiza que en el mtodo se inicializarn antes de leerlas.
Adems, tras la llamada a un mtodo se considera que las variables que se le pasaron
Todo esto se resume con el siguiente ejemplo: como parmetros de salida ya estarn inicializadas, pues dentro del mtodo seguro que
se las inicializa.
using System;
Ntese que este tipo de parmetros permiten disear mtodos que devuelvan mltiples
class ParmetrosEntrada objetos: un objeto se devolvera como valor de retorno y los dems se devolveran
{ escribindolos en los parmetros de salida.
public int a = 1;

public static void F(ParametrosEntrada p) Los parmetros de salida se definen de forma parecida a los parmetros de entrada pero
{ se les ha de aadir la palabra reservada out. O sea, se definen as:
p.a++;
} out <tipoParmetro> <nombreParmetro>

public static void G(int p)


{
Al llamar a un mtodo que tome parmetros de este tipo tambin se ha preceder el valor
p++; especificado para estos parmetros del modificador out. Una utilidad de esto es facilitar
} la legibilidad de las llamadas a mtodos. Por ejemplo, dada una llamada de la forma:

Jos Antonio Gonzlez Seco Pgina 113 Jos Antonio Gonzlez Seco Pgina 114
El lenguaje de programacin C# Tema 8: Mtodos El lenguaje de programacin C# Tema 8: Mtodos

a.f(x, out z) static void F(int x, int y)


{}
Es fcil determinar que lo que se hace es llamar al mtodo f() del objeto a pasndole x
como parmetro de entrada y z como parmetro de salida. Adems, tambin se puede Cuando se hiciese una llamada como F(3,2) se llamara a esta ltima versin del mtodo,
deducir que el valor de z cambiar tras la llamada. ya que aunque la del params es tambin aplicable, se considera que es menos prioritaria.

Sin embargo, la verdadera utilidad de forzar a explicitar en las llamadas el tipo de paso
de cada parmetro es que permite evitar errores derivados de que un programador pase Sobrecarga de tipos de parmetros
una variable a un mtodo y no sepa que el mtodo la puede modificar. Tenindola que
explicitar se asegura que el programador sea consciente de lo que hace. En realidad los modificadores ref y out de los parmetros de un mtodo tambin forman
parte de lo que se conoce como signatura del mtodo, por lo que esta clase es vlida:

Parmetros por referencia class Sobrecarga


{
public void f(int x)
Un parmetro por referencia es similar a un parmetro de salida slo que no es {}
obligatorio modificarlo dentro del mtodo al que pertenece, por lo que ser obligatorio public void f(out int x)
pasarle una variable inicializada ya que no se garantiza su inicializacin en el mtodo. {}
}
Los parmetros por referencia se definen igual que los parmetros de salida pero
sustituyendo el modificador out por el modificador ref. Del mismo modo, al pasar Ntese que esta clase es correcta porque cada uno de sus mtodos tiene una signatura
valores a parmetros por referencia tambin hay que precederlos del ref. distinta: el parmetro es de entrada en el primero y de salida en el segundo.

Sin embargo, hay una restriccin: no puede ocurrir que la nica diferencia entre la
Parmetros de nmero indefinido signatura de dos mtodos sea que en uno un determinado parmetro lleve el modificador
ref y en el otro lleve el modificador out. Por ejemplo, no es vlido:

C# permite disear mtodos que puedan tomar cualquier nmero de parmetros. Para class SobrecargaInvlida
ello hay que indicar como ltimo parmetro del mtodo un parmetro de algn tipo de {
tabla unidimensional o dentada precedido de la palabra reservada params. Por ejemplo: public void f(ref int x)
{}
static void F(int x, params object[] extras) public void f(out int x)
{} {}
}
Todos los parmetros de nmero indefinido que se pasan al mtodo al llamarlo han de
ser del mismo tipo que la tabla. Ntese que en el ejemplo ese tipo es la clase primigenia
object, con lo que se consigue que gracias al polimorfismo el mtodo pueda tomar Mtodos externos
cualquier nmero de parmetros de cualquier tipo. Ejemplos de llamadas vlidas seran:
Un mtodo externo es aqul cuya implementacin no se da en el fichero fuente en que
F(4); // Pueden pasarse 0 parmetros indefinidos es declarado. Estos mtodos se declaran precediendo su declaracin del modificador
F(3,2); extern. Como su cdigo se da externamente, en el fuente se sustituyen las llaves donde
F(1, 2, Hola, 3.0, new Persona());
debera escribirse su cuerpo por un punto y coma (;), quedando una sintaxis de la forma:
F(1, new object[] {2,Hola, 3.0, new Persona});
extern <nombreMtodo>(<parmetros>);
El primer ejemplo demuestra que el nmero de parmetros indefinidos que se pasen
tambin puede ser 0. Por su parte, los dos ltimos ejemplos son totalmente equivalentes, La forma en que se asocie el cdigo externo al mtodo no est definida en la
pues precisamente la utilidad de palabra reservada params es indicar que se desea que la especificacin de C# sino que depende de la implementacin que se haga del lenguaje.
creacin de la tabla object[] se haga implcitamente. El nico requisito es que no pueda definirse un mtodo como abstracto y externo a la
vez, pero por todo lo dems puede combinarse con los dems modificadores, incluso
Es importante sealar que la prioridad de un mtodo que incluya el params es inferior a pudindose definir mtodos virtuales externos.
la de cualquier otra sobrecarga del mismo. Es decir, si se hubiese definido una
sobrecarga del mtodo anterior como la siguiente:

Jos Antonio Gonzlez Seco Pgina 115 Jos Antonio Gonzlez Seco Pgina 116
El lenguaje de programacin C# Tema 8: Mtodos El lenguaje de programacin C# Tema 8: Mtodos

La forma ms habitual de asociar cdigo externo consiste en preceder la declaracin del


mtodo de un atributo de tipo System.Runtime.InteropServices.DllImport que indique
en cul librera de enlace dinmico (DLL) se ha implementado. Este atributo requiere Definicin de constructores
que el mtodo externo que le siga sea esttico, y un ejemplo de su uso es:
La sintaxis bsica de definicin de constructores consiste en definirlos como cualquier
using System.Runtime.InteropServices; // Aqu est definido DllImport otro mtodo pero dndoles el mismo nombre que el tipo de dato al que pertenecen y no
public class Externo
{
indicando el tipo de valor de retorno debido a que nunca pueden devolver nada. Es
[DllImport(kernel32)] decir, se usa la sintaxis:
public static extern void CopyFile(string fuente, string destino);
<modificadores> <nombreTipo>(<parmetros>)
public static void Main() {
{ <cdigo>
CopyFile(fuente.dat, destino.dat); }
}
} Un constructor nunca puede devolver ningn tipo de objeto porque, como ya se ha
visto, slo se usa junto al operador new, que devuelve una referencia al objeto recin
El concepto de atributo se explica detalladamente en el Tema 14:Atributos. Por ahora creado. Por ello, es absurdo que devuelva algn valor ya que nunca podra ser capturado
basta saber que los atributos se usan de forman similar a los mtodos slo que no estn en tanto que new nunca lo devolvera. Por esta razn el compilador considera errneo
asociados a ningn objeto ni tipo y se indican entre corchetes ([]) antes de declaraciones indicar algn tipo de retorno en su definicin, incluso aunque se indique void.
de elementos del lenguaje. En el caso concreto de DllImport lo que indica el parmetro
que se le pasa es cul es el fichero (por defecto se considera que su extensin es .dll)
donde se encuentra la implementacin del mtodo externo a continuacin definido. Llamada al constructor
Lo que el cdigo del ejemplo anterior hace es simplemente definir un mtodo de
Al constructor de una clase se le llama en el momento en que se crea algn objeto de la
nombre CopyFile() cuyo cdigo se corresponda con el de la funcin CopyFile() del
misma usando el operador new. De hecho, la forma de uso de este operador es:
fichero kernel32.dll del API Win32. Este mtodo es llamado en Main() para copiar el
fichero de nombre fuente.dat en otro de nombre destino.dat. Ntese que dado que new <llamadaConstructor>
CopyFile() se ha declarado como static y se le llama desde la misma clase donde se ha
declarado, no es necesario precederlo de la notacin <nombreClase>. para llamarlo. Por ejemplo, el siguiente programa demuestra cmo al crearse un objeto se ejecutan las
instrucciones de su constructor:
Como se ve, la utilidad principal de los mtodos externos es permitir hacer llamadas a
cdigo nativo desde cdigo gestionado, lo que puede ser til por razones de eficiencia o class Prueba
para reutilizar cdigo antiguamente escrito pero reduce la portabilidad de la aplicacin. {
Prueba(int x)
{
System.Console.Write(Creado objeto Prueba con x={0},x);
Constructores }

Concepto de constructores public static void Main()


{
Prueba p = new Prueba(5);
Los constructores de un tipo de datos son mtodos especiales que se definen como }
miembros de ste y que contienen cdigo a ejecutar cada vez que se cree un objeto de }
ese tipo. ste cdigo suele usarse para labores de inicializacin de los campos del objeto
a crear, sobre todo cuando el valor de stos no es constante o incluye acciones ms all La salida por pantalla de este programa demuestra que se ha llamado al constructor del
de una asignacin de valor (aperturas de ficheros, accesos a redes, etc.) objeto de clase Prueba creado en Main(), pues es:

Hay que tener en cuenta que la ejecucin del constructor siempre se realiza despus de Creado objeto Prueba con x=5;
haberse inicializado todos los campos del objeto, ya sea con los valores iniciales que se
hubiesen especificado en su definicin o dejndolos con el valor por defecto de su tipo.
Llamadas entre constructores
Aparte de su especial sintaxis de definicin, los constructores y los mtodos normales
tienen una diferencia muy importante: los constructores no se heredan.

Jos Antonio Gonzlez Seco Pgina 117 Jos Antonio Gonzlez Seco Pgina 118
El lenguaje de programacin C# Tema 8: Mtodos El lenguaje de programacin C# Tema 8: Mtodos

Al igual que ocurre con cualquier otro mtodo, tambin es posible sobrecargar los }
constructores. Es decir, se pueden definir varios constructores siempre y cuando stos
tomen diferentes nmeros o tipos de parmetros. Adems, desde el cdigo de un En ambos casos, los valores pasados como parmetros en el inicializador no pueden
constructor puede llamarse a otros constructores del mismo tipo de dato antes de contener referencias a campos del objeto que se est creando, ya que se considera que
ejecutar las instrucciones del cuerpo del primero. Para ello se aade un inicializador un objeto no est creado hasta que no se ejecute su constructor y, por tanto, al llamar al
this al constructor, que es estructura que precede a la llave de apertura de su cuerpo tal y inicializador an no est creado. Sin embargo, lo que s pueden incluir son referencias a
como se muestra en el siguiente ejemplo: los parmetros con los que se llam al constructor. Por ejemplo, sera vlido hacer:

class A A(int x, int y): this(x+y)


{ {}
int total;

A(int valor): this(valor, 2); // (1) Constructor por defecto


{
}
Todo tipo de datos ha de disponer de al menos un constructor. Cuando se define un tipo
A(int valor, int peso) // (2) sin especificar ninguno el compilador considera que implcitamente se ha definido uno
{ sin cuerpo ni parmetros de la siguiente forma:
total = valor*peso;
}
} public <nombreClase>(): base()
{}
El this incluido hace que la llamada al constructor (1) de la clase A provoque una
llamada al constructor (2) de esa misma clase en la que se le pase como primer En el caso de que el tipo sea una clase abstracta, entonces el constructor por defecto
parmetro el valor originalmente pasado al constructor (1) y como segundo parmetro el introducido es el que se muestra a continuacin, ya que el anterior no sera vlido
valor 2. Es importante sealar que la llamada al constructor (2) en (1) se hace antes de porque permitira crear objetos de la clase a la que pertenece:
ejecutar cualquier instruccin de (1)
protected <nombreClase>(): base()
{}
Ntese que la sobrecarga de constructores -y de cualquier mtodo en general- es un
buen modo de definir versiones ms compactas de mtodos de uso frecuente en las que En el momento en se defina explcitamente algn constructor el compilador dejar de
se tomen valores por defecto para parmetros de otras versiones menos compactas del introducir implcitamente el anterior. Hay que tener especial cuidado con la llamada que
mismo mtodo. La implementacin de estas versiones compactas consistira en hacer este constructor por defecto realiza en su inicializador, pues pueden producirse errores
una llamada a la versin menos compacta del mtodo en la que se le pasen esos valores como el del siguiente ejemplo:
por defecto (a travs del this en el caso de los constructores) y si acaso luego (y/o antes,
si no es un constructor) se hagan labores especficas en el cuerpo del mtodo compacto. class A
{
Del mismo modo que en la definicin de un constructor de un tipo de datos es posible public A(int x)
llamar a otros constructores del mismo tipo de datos, tambin es posible hacer llamadas {}
}
a constructores de su tipo padre sustituyendo en su inicializador la palabra reservada
this por base. Por ejemplo: class B:A
{
class A public static void Main()
{ {
int total; B b = new B(); // Error: No hay constructor base
}
A(int valor, int peso) }
{
total = valor*peso; En este caso, la creacin del objeto de clase B en Main() no es posible debido a que el
}
}
constructor que por defecto el compilador crea para la clase B llama al constructor sin
parmetros de su clase base A, pero A carece de dicho constructor porque no se le ha
class B:A definido explcitamente ninguno con esas caractersticas pero se le ha definido otro que
{ ha hecho que el compilador no le defina implcitamente el primero.
B(int valor):base(valor,2)
{}

Jos Antonio Gonzlez Seco Pgina 119 Jos Antonio Gonzlez Seco Pgina 120
El lenguaje de programacin C# Tema 8: Mtodos El lenguaje de programacin C# Tema 8: Mtodos

Otro error que podra darse consistira en que aunque el tipo padre tuviese un Constructor de Derivada
constructor sin parmetros, ste fuese privado y por tanto inaccesible para el tipo hijo.
Lo que ha ocurrido es lo siguiente: Al crearse el objeto Derivada se ha llamado a su
Tambin es importante sealar que an en el caso de que definamos nuestras propios constructor sin parmetros, que como no tiene inicializador implcitamente llama al
constructores, si no especificamos un inicializador el compilador introducir por constructor sin parmetros de su clase base. El constructor de Base realiza una llamada
nosotros uno de la forma :base() Por tanto, en estos casos tambin hay que asegurarse de al mtodo virtual F(), y como el verdadero tipo del objeto que se est construyendo es
que el tipo donde se haya definido el constructor herede de otro que tenga un Derivada, entonces la versin del mtodo virtual ejecutada es la redefinicin del mismo
constructor sin parmetros no privado. incluida en dicha clase. Por ltimo, se termina llamando al constructor de Derivada y
finaliza la construccin del objeto.

Llamadas polimrficas en constructores Ntese que se ha ejecutado el mtodo F() de Derivada antes que el cdigo del constructor
de dicha clase, por lo que si ese mtodo manipulase campos definidos en Derivada que
se inicializasen a travs de constructor, se habra accedido a ellos antes de inicializarlos
Es conveniente evitar en la medida de lo posible la realizacin de llamadas a mtodos
y ello seguramente provocara errores de causas difciles de averiguar.
virtuales dentro de los constructores, ya que ello puede provocar errores muy difciles
de detectar debido a que se ejecuten mtodos cuando la parte del objeto que manipulan
an no se ha sido inicializado. Un ejemplo de esto es el siguiente: Constructor de tipo
using System;
Todo tipo puede tener opcionalmente un constructor de tipo, que es un mtodo
public class Base especial que funciona de forma similar a los constructores ordinarios slo que para lo
{ que se usa es para inicializar los campos static del tipo donde se ha definido.
public Base()
{
Console.WriteLine("Constructor de Base"); Cada tipo de dato slo puede tener un constructor de tipo. ste constructor es llamado
this.F(); automticamente por el compilador la primera vez que se accede al tipo, ya sea para
} crear objetos del mismo o para acceder a sus campos estticos. Esta llamada se hace
justo despus de inicializar los campos estticos del tipo con los valores iniciales
public virtual void F()
{
especificados al definirlos (o, en su ausencia, con los valores por defecto de sus tipos de
Console.WriteLine("Base.F"); dato), por lo que el programador no tiene forma de controlar la forma en que se le llama
} y, por tanto, no puede pasarle parmetros que condicionen su ejecucin.
}
Como cada tipo slo puede tener un constructor de tipo no tiene sentido poder usar this
public class Derivada:Base
{
en su inicializador para llamar a otro. Y adems, tampoco tiene sentido usar base
Derivada() debido a que ste siempre har referencia al constructor de tipo sin parmetros de su
{ clase base. O sea, un constructor de tipo no puede tener inicializador.
Console.WriteLine("Constructor de Derivada");
} Adems, no tiene sentido darle modificadores de acceso ya que el programador nunca lo
public override void F()
podr llamar sino que slo ser llamado automticamente y slo al accederse al tipo por
{ primera vez. Como es absurdo, el compilador considera un error drselos.
Console.WriteLine("Derivada.F()");
} La forma en que se define el constructor de tipo es similar a la de los constructores
normales, slo que ahora la definicin ha de ir prefijada del modificador static y no
public static void Main()
{
puede contar con parmetros ni inicializador. O sea, se define de la siguiente manera:
Base b = new Derivada();
} static <nombreTipo>()
{
} <cdigo>
}
La salida por pantalla mostrada por este programa al ejecutarse es la siguiente:
En la especificacin de C# no se ha recogido cul ha de ser el orden exacto de las
Constructor de Base llamadas a los constructores de tipos cuando se combinan con herencia, aunque lo que s
Derivada.F()

Jos Antonio Gonzlez Seco Pgina 121 Jos Antonio Gonzlez Seco Pgina 122
El lenguaje de programacin C# Tema 8: Mtodos El lenguaje de programacin C# Tema 8: Mtodos

se indica es que se ha de asegurar de que no se accede a un campo esttico sin haberse para liberar recursos tales como los ficheros o las conexiones de redes abiertas que el
ejecutado antes su constructor de tipo. Todo esto puede verse ms claro con un ejemplo: objeto a destruir estuviese acaparando en el momento en que se fuese a destruir.

using System; La destruccin de un objeto es realizada por el recolector de basura cuando realiza una
recoleccin de basura y detecta que no existen referencias a ese objeto ni en pila, ni en
class A
{
registros ni desde otros objetos s referenciados. Las recolecciones se inician
public static X; automticamente cuando el recolector detecta que queda poca memoria libre o que se va
a finalizar la ejecucin de la aplicacin, aunque tambin puede forzarse llamando al
static A() mtodo Collect() de la clase System.GC
{
Console.WriteLine(Constructor de A);
X=1;
La sintaxis que se usa para definir un destructor es la siguiente:
}
} ~<nombreTipo>()
{
class B:A <cdigo>
{ }
static B()
{ Tras la ejecucin del destructor de un objeto de un determinado tipo siempre se llama al
Console.WriteLine(Constructor de B); destructor de su tipo padre, formndose as una cadena de llamadas a destructores que
X=2;
}
acaba al llegarse al destructor de object. ste ltimo destructor no contiene cdigo
public static void Main() alguno, y dado que object no tiene padre, tampoco llama a ningn otro destructor.
{
B b = new B(); Los destructores no se heredan. Sin embargo, para asegurar que la cadena de llamadas a
Console.WriteLine(B.X); destructores funcione correctamente si no incluimos ninguna definicin de destructor en
}
}
un tipo, el compilador introducir en esos casos una por nosotros de la siguiente forma:

~<nombreTipo>()
La salida que muestra por pantalla la ejecucin de este programa es la siguiente: {}
Inicializada clase B
Inicializada clase A El siguiente ejemplo muestra como se definen destructores y cmo funciona la cadena
2 de llamada a destructores:

En principio la salida de este programa puede resultar confusa debido a que los primeros using System;
dos mensajes parecen dar la sensacin de que la creacin del objeto b provoc que se
class A
ejecutase el constructor de la clase hija antes que al de la clase padre, pero el ltimo {
mensaje se corresponde con una ejecucin en el orden opuesto. Pues bien, lo que ha ~A()
ocurrido es lo siguiente: como el orden de llamada a constructores de tipo no est {
establecido, el compilador de Microsoft ha llamado antes al de la clase hija y por ello el Console.WriteLine(Destruido objeto de clase A);
primer mensaje mostrado es Inicializada clase B. Sin embargo, cuando en este }
}
constructor se va a acceder al campo X se detecta que la clase donde se defini an no
est inicializada y entonces se llama a su constructor de tipo, lo que hace que se muestre class B:A
el mensaje Incializada clase A. Tras esta llamada se machaca el valor que el {
constructor de A di a X (valor 1) por el valor que el constructor de B le da (valor 2) ~B()
Finalmente, el ltimo WriteLine() muestra un 2, que es el ltimo valor escrito en X. {
Console.WriteLine(Destruido objeto de clase B);
}
public static void Main()
Destructores {
new B();
}
Al igual que es posible definir mtodos constructores que incluyan cdigo que gestione }
la creacin de objetos de un tipo de dato, tambin es posible definir un destructor que
gestione cmo se destruyen los objetos de ese tipo de dato. Este mtodo suele ser til

Jos Antonio Gonzlez Seco Pgina 123 Jos Antonio Gonzlez Seco Pgina 124
El lenguaje de programacin C# Tema 8: Mtodos El lenguaje de programacin C# Tema 8: Mtodos

El cdigo del mtodo Main() de este programa crea un objeto de clase B pero no Derivada.F()
almacena ninguna referencia al mismo. Luego finaliza la ejecucin del programa, lo que
provoca la actuacin del recolector de basura y la destruccin del objeto creado Como se ve, aunque el objeto creado se almacene en una variable de tipo Base, su
llamando antes a su destructor. La salida que ofrece por pantalla el programa demuestra verdadero tipo es Derivada y por ello se llama al destructor de esta clase al destruirlo.
que tras llamar al destructor de B se llama al de su clase padre, ya que es: Tras ejecutarse dicho destructor se llama al destructor de su clase padre siguindose la
cadena de llamadas a destructores. En este constructor padre hay una llamada al mtodo
Destruido objeto de clase B virtual F(), que como nuevamente el objeto que se est destruyendo es de tipo Derivada,
Destruido objeto de clase A la versin de F() a la que se llamar es a la de la dicha clase.

Ntese que aunque no se haya guardado ninguna referencia al objeto de tipo B creado y Ntese que una llamada a un mtodo virtual dentro de un destructor como la que se hace
por tanto sea inaccesible para el programador, al recolector de basura no le pasa lo en el ejemplo anterior puede dar lugar a errores difciles de detectar, pues cuando se
mismo y siempre tiene acceso a los objetos, aunque sean intiles para el programador. llama al mtodo virtual ya se ha destruido la parte del objeto correspondiente al tipo
donde se defini el mtodo ejecutado. As, en el ejemplo anterior se ha ejecutado
Es importante recalcar que no es vlido incluir ningn modificador en la definicin de Derivada.F() tras Derivada.~F(), por lo que si en Derivada.F() se usase algn campo
un destructor, ni siquiera modificadores de acceso, ya que como nunca se le puede destruido en Derivada.~F() podran producirse errores difciles de detectar.
llamar explcitamente no tiene ningn nivel de acceso para el programador. Sin
embargo, ello no implica que cuando se les llame no se tenga en cuenta el verdadero
tipo de los objetos a destruir, como demuestra el siguiente ejemplo:
using System;

public class Base


{
public virtual void F()
{
Console.WriteLine("Base.F");
}

~Base()
{
Console.WriteLine("Destructor de Base");
this.F();
}
}

public class Derivada:Base


{
~Derivada()
{
Console.WriteLine("Destructor de Derivada");
}

public override void F()


{
Console.WriteLine("Derivada.F()");
}

public static void Main()


{
Base b = new Derivada();
}
}

La salida mostrada que muestra por pantalla este programa al ejecutarlo es:
Destructor de Derivada
Destructor de Base

Jos Antonio Gonzlez Seco Pgina 125 Jos Antonio Gonzlez Seco Pgina 126
El lenguaje de programacin C# Tema 9: Propiedades El lenguaje de programacin C# Tema 9: Propiedades

TEMA 9: Propiedades Las propiedades participan del mecanismo de polimorfismo igual que los mtodos,
siendo incluso posible definir propiedades cuyos bloques de cdigo get o set sean
abstractos. Esto se hara prefijando el bloque apropiado con un modificador abstract y
Concepto de propiedad sustituyendo la definicin de su cdigo por un punto y coma. Por ejemplo:

using System;
Una propiedad es una mezcla entre el concepto de campo y el concepto de mtodo.
Externamente es accedida como si de un campo normal se tratase, pero internamente es abstract class A
posible asociar cdigo a ejecutar en cada asignacin o lectura de su valor. ste cdigo {
public abstract int PropiedadEjemplo
puede usarse para comprobar que no se asignen valores invlidos, para calcular su valor {
slo al solicitar su lectura, etc. set;
get;
Una propiedad no almacena datos, sino slo se utiliza como si los almacenase. En la }
prctica lo que se suele hacer escribir como cdigo a ejecutar cuando se le asigne un }
valor, cdigo que controle que ese valor sea correcto y que lo almacene en un campo class B:A
privado si lo es; y como cdigo a ejecutar cuando se lea su valor, cdigo que devuelva {
el valor almacenado en ese campo pblico. As se simula que se tiene un campo pblico private int valor;
sin los inconvenientes que estos presentan por no poderse controlar el acceso a ellos.
public override int PropiedadEjemplo
{
get
Definicin de propiedades {
Console.WriteLine(Ledo {0} de PropiedadEjemplo, valor);
return valor;
Para definir una propiedad se usa la siguiente sintaxis:
}
set
<tipoPropiedad> <nombrePropiedad> {
{ valor = value;
set Console.WriteLine(Escrito {0} en PropiedadEjemplo, valor);
{
<cdigoEscritura> }
} }
}
get
{
<cdigoLectura> En este ejemplo se ve cmo se definen y redefinen propiedades abstractas. Al igual que
} abstract y override, tambin es posible usar cualquiera de los modificadores relativos a
} herencia y polimorfismo ya vistos: virtual, new y sealed.

Una propiedad as definida sera accedida como si de un campo de tipo <tipoPropiedad> Ntese que aunque en el ejemplo se ha optado por asociar un campo privado valor a la
se tratase, pero en cada lectura de su valor se ejecutara el <cdigoLectura> y en cada propiedad PropiedadEjemplo, en realidad nada obliga a que ello se haga y es posible
escritura de un valor en ella se ejecutara <cdigoEscritura> definir propiedades que no tengan campos asociados. Es decir, una propiedad no se
tiene porqu corresponder con un almacn de datos.
Al escribir los bloques de cdigo get y set hay que tener en cuenta que dentro del
cdigo set se puede hacer referencia al valor que se solicita asignar a travs de un
parmetro especial del mismo tipo de dato que la propiedad llamado value (luego Acceso a propiedades
nosotros no podemos definir uno con ese nombre en <cdigoEscritura>); y que dentro del
cdigo get se ha de devolver siempre un objeto del tipo de dato de la propiedad. La forma de acceder a una propiedad, ya sea para lectura o escritura, es exactamente la
misma que la que se usara para acceder a un campo de su mismo tipo. Por ejemplo, se
En realidad el orden en que aparezcan los bloques de cdigo set y get es irrelevante. podra acceder a la propiedad de un objeto de la clase B del ejemplo anterior con:
Adems, es posible definir propiedades que slo tengan el bloque get (propiedades de
slo lectura) o que slo tengan el bloque set (propiedades de slo escritura) Lo que B obj = new B();
no es vlido es definir propiedades que no incluyan ninguno de los dos bloques. obj.PropiedadEjemplo++;

Jos Antonio Gonzlez Seco Pgina 127 Jos Antonio Gonzlez Seco Pgina 128
El lenguaje de programacin C# Tema 9: Propiedades El lenguaje de programacin C# Tema 10: Indizadores

El resultado que por pantalla se mostrara al hacer una asignacin como la anterior sera: TEMA 10: Indizadores
Ledo 0 de PropiedadEjemplo;
Escrito 1 en PropiedadEjemplo;
Concepto de indizador
Ntese que en el primer mensaje se muestra que el valor ledo es 0 porque lo que
devuelve el bloque get de la propiedad es el valor por defecto del campo privado valor, Un indizador es una definicin de cmo se puede aplicar el operador de acceso a tablas
que como es de tipo int tiene como valor por defecto 0. ([ ]) a los objetos de un tipo de dato. Esto es especialmente til para hacer ms clara la
sintaxis de acceso a elementos de objetos que puedan contener colecciones de
elementos, pues permite tratarlos como si fuesen tablas normales.
Implementacin interna de propiedades
Los indizadores permiten definir cdigo a ejecutar cada vez que se acceda a un objeto
En realidad la definicin de una propiedad con la sintaxis antes vista es convertida por del tipo del que son miembros usando la sintaxis propia de las tablas, ya sea para leer o
el compilador en la definicin de un par de mtodos de la siguiente forma: escribir. A diferencia de las tablas, los ndices que se les pase entre corchetes no tiene
porqu ser enteros, pudindose definir varios indizadores en un mismo tipo siempre y
<tipoPropiedad> get_<nombrePropiedad>() cuando cada uno tome un nmero o tipo de ndices diferente.
{ // Mtodo en que se convierte en bloque get
<cdigoLectura>
}
Definicin de indizador
void set_<nombrePropiedad> (<tipoPropiedad> value)
{ // Mtodo en que se convierte en bloque set A la hora de definir un indizador se usa una sintaxis parecida a la de las propiedades:
<cdigoEscritura>
} <tipoIndizador> this[<ndices>]
{
Esto se hace para que desde lenguajes que no soporten las propiedades se pueda acceder set
tambin a ellas. Si una propiedad es de slo lectura slo se generar el mtodo get_X(), y {
si es de slo escritura slo se generar el set_X() Ahora bien, en cualquier caso hay que <cdigoEscritura>
}
tener cuidado con no definir en un mismo tipo de dato mtodos con signaturas como get
estas si se van a generar internamente debido a la definicin de una propiedad, ya que {
ello provocara un error de definicin mltiple de mtodo. <cdigoLectura>
}
Teniendo en cuenta la implementacin interna de las propiedades, es fcil ver que el }
ltimo ejemplo de acceso a propiedad es equivalente a:
Las nicas diferencias entre esta sintaxis y la de las propiedades son:
B b = new B();
obj.set_PropiedadEjemplo(obj.get_Propiedad_Ejemplo()++); El nombre dado a un indizador siempre ha de ser this, pues carece de sentido
poder darle cualquiera en tanto que a un indizador no se accede por su nombre
Como se ve, gracias a las propiedades se tiene una sintaxis mucho ms compacta y clara sino aplicando el operador [ ] a un objeto. Por ello, lo que diferenciar a unos
para acceder a campos de manera controlada. Se podra pensar que la contrapartida de indizadores de otros ser el nmero y tipo de sus <ndices>.
esto es que el tiempo de acceso al campo aumenta considerablemente por perderse
tiempo en hacer las llamada a mtodos set/get. Pues bien, esto no tiene porqu ser as ya En <ndices> se indica cules son los ndices que se pueden usar al acceder al
que el compilador de C# elimina llamadas haciendo inlining (sustitucin de la llamada indizador. Para ello la sintaxis usada es casi la misma que la que se usa para
por su cuerpo) en los accesos a bloques get/set no virtuales y de cdigos pequeos, que especificar los parmetros de un mtodo, slo que no se admite la inclusin de
son los ms habituales. modificadores ref, out o params y que siempre ha de definirse al menos un
parmetro. Obviamente, el nombre que se d a cada ndice ser el nombre con el
Ntese que de la forma en que se definen los mtodos generados por el compilador se que luego se podr acceder al mismo en los bloques set/get.
puede deducir el porqu del hecho de que en el bloque set se pueda acceder a travs de
value al valor asignado y de que el objeto devuelto por el cdigo de un bloque get tenga No se pueden definir indizadores estticos, sino slo indizadores de objetos.
que ser del mismo tipo de dato que la propiedad a la que pertenece.

Jos Antonio Gonzlez Seco Pgina 129 Jos Antonio Gonzlez Seco Pgina 130
El lenguaje de programacin C# Tema 10: Indizadores El lenguaje de programacin C# Tema 10: Indizadores

Por todo lo dems, la sintaxis de definicin de los indizadores es la misma que la de las Implementacin interna de indizadores
propiedades: pueden ser de slo lectura o de slo escritura, da igual el orden en que se
definan sus bloques set/get, dentro del bloque set se puede acceder al valor a escribir a Al igual que las propiedades, para facilitar la interoperabilidad entre lenguajes los
travs del parmetro especial value del tipo del indizador, el cdigo del bloque get ha de indizadores son tambin convertidos por el compilador en llamadas a mtodos cuya
devolver un objeto de dicho tipo, etc. definicin se deduce de la definicin del indizador. Ahora los mtodos son de la forma:
A continuacin se muestra un ejemplo de definicin de una clase que consta de dos <tipoIndizador> get_Item(<ndices>)
indizadores: ambos permiten almacenar elementos de tipo entero, pero uno toma como {
ndice un entero y el otro toma dos cadenas: <cdigoLectura>
}
using System;
void set_Item(<ndices>, <tipoIndizador> value)
public class A {
{ <cdigoEscritura>
public int this[int ndice] }
{
set Nuevamente, hay que tener cuidado con la signatura de los mtodos que se definan en
{ una clase ya que como la de alguno coincida con la generada automticamente por el
Console.WriteLine(Escrito {0} en posicin {1}, value, ndice); compilador para los indizadores se producir un error de ambigedad.
}
get
{
Console.WriteLine(Ledo 1 de posicin {0}, ndice);
return 1;
}
}

public int this[string cad1, string cad2]


{
set
{
Console.WriteLine(Escrito {0} en posicin ({1},{2}), value, cad1, cad2);
}
get
{
Console.WriteLine(Ledo 2 de posicin ({0},{1}), cad1, cad2);
return 2;
}
}
}

Acceso a indizadores

Para acceder a un indizador se utiliza exactamente la misma sintaxis que para acceder a
una tabla, slo que los ndices no tienen porqu ser enteros sino que pueden ser de
cualquier tipo de dato que se haya especificado en su definicin. Por ejemplo, accesos
vlidos a los indizadores de un objeto de la clase A definida en el epgrafe anterior son:

A obj = new A();


obj[100] = obj[barco, coche];

La ejecucin de la asignacin de este ejemplo producir esta salida por pantalla:

Ledo 2 de posicin (barco, coche)


Escrito 2 en posicin 100

Jos Antonio Gonzlez Seco Pgina 131 Jos Antonio Gonzlez Seco Pgina 132
El lenguaje de programacin C# Tema 11: Redefinicin de operadores El lenguaje de programacin C# Tema 11: Redefinicin de operadores

Definicin de redefiniciones de operadores


TEMA 11: Redefinicin de operadores
Sintaxis general de redefinicin de operador
Concepto de redefinicin de operador
La forma en que se redefine un operador depende del tipo de operador del que se trate,
Un operador en C# no es ms que un smbolo formado por uno o ms caracteres que ya que no es lo mismo definir un operador unario que uno binario. Sin embargo, como
permite realizar una determinada operacin entre uno o ms datos y produce un regla general podemos considerar que se hace definiendo un mtodo pblico y esttico
resultado. En el Tema 4: Aspectos Lxicos ya hemos visto que C# cuenta con un buen cuyo nombre sea el smbolo del operador a redefinir y venga precedido de la palabra
nmero de operadores que permiten realizar con una sintaxis clara e intuitiva las reservada operator. Es decir, se sigue una sintaxis de la forma:
operaciones comunes a la mayora de lenguajes (aritmtica, lgica, etc.) as como otras
operaciones ms particulares de C# (operador is, operador stackalloc, etc.) public static <tipoDevuelto> operator <smbolo>(<operandos>)
{
<cuerpo>
En C# viene predefinido el comportamiento de sus operadores cuando se aplican a }
ciertos tipos de datos. Por ejemplo, si se aplica el operador + entre dos objetos int
devuelve su suma, y si se aplica entre dos objetos string devuelve su concatenacin. Sin Los modificadores public y static pueden permutarse si se desea, lo que es importante es
embargo, tambin se permite que el programador pueda definir el significado la mayora que siempre aparezcan en toda redefinicin de operador. Se pueden redefinir tanto
de estos operadores cuando se apliquen a objetos de tipos que l haya definido, y esto es operadores unarios como binarios, y en <operandos> se ha de incluir tantos parmetros
a lo que se le conoce como redefinicin de operador. como operandos pueda tomar el operador a redefinir, ya que cada uno representar a
uno de sus operandos. Por ltimo, en <cuerpo> se han de escribir las instrucciones a
Ntese que en realidad la posibilidad de redefinir un operador no aporta ninguna nueva ejecutar cada vez que se aplique la operacin cuyo operador es <smbolo> a operandos
funcionalidad al lenguaje y slo se ha incluido en C# para facilitar la legibilidad del de los tipos indicados en <operandos>.
cdigo. Por ejemplo, si tenemos una clase Complejo que representa nmeros complejos
podramos definir una funcin Sumar() para sus objetos de modo que a travs de ella se <tipoDevuelto> no puede ser void, pues por definicin toda operacin tiene un resultado,
pudiese conseguir la suma de dos objetos de esta clase como muestra este ejemplo: por lo que todo operador ha de devolver algo. Adems, permitirlo complicara
innecesariamente el compilador y ste tendra que admitir instrucciones poco intuitivas
Complejo c1 = new Complejo(3,2); // c1 = 3 + 2i (como a+b; si el + estuviese redefinido con valor de retorno void para los tipos de a y b)
Complejo c2 = new Complejo(5,2); // c2 = 5 + 2i
Complejo c3 = c1.Sumar(c2); // c3 = 8 + 4i
Adems, los operadores no pueden redefinirse con total libertad ya que ello tambin
Sin embargo, el cdigo sera mucho ms legible e intuitivo si en vez de tenerse que usar dificultara sin necesidad la legibilidad del cdigo, por lo que se han introducido las
el mtodo Sumar() se redefiniese el significado del operador + para que al aplicarlo entre siguientes restricciones al redefinirlos:
objetos Complejo devolviese su suma. Con ello, el cdigo anterior quedara as:
Al menos uno de los operandos ha de ser del mismo tipo de dato del que sea
Complejo c1 = new Complejo(3,2); // c1 = 3 + 2i miembro la redefinicin del operador. Como puede deducirse, ello implica que
Complejo c2 = new Complejo(5,2); // c2 = 5 + 2i aunque puedan sobrecargarse los operadores binarios nunca podr hacerse lo mismo
Complejo c3 = c1 + c2; // c3 = 8 + 4i con los unarios ya que su nico parmetro slo puede ser de un nico tipo (el tipo
dentro del que se defina) Adems, ello tambin provoca que no pueden redefinirse
sta es precisamente la utilidad de la redefinicin de operadores: hacer ms claro y las conversiones ya incluidas en la BCL porque al menos uno de los operandos
legible el cdigo, no hacerlo ms corto. Por tanto, cuando se redefina un operador es siempre habr de ser de algn nuevo tipo definido por el usuario.
importante que se le d un significado intuitivo ya que si no se ira contra de la filosofa
de la redefinicin de operadores. Por ejemplo, aunque sera posible redefinir el operador No puede alterarse sus reglas de precedencia, asociatividad, ubicacin y nmero de
* para que cuando se aplicase entre objetos de tipo Complejo devuelva su suma o operandos, pues si ya de por s es difcil para muchos recordarlas cuando son fijas,
imprimiese los valores de sus operandos en la ventana de consola, sera absurdo hacerlo mucho ms lo sera si pudiesen modificarse segn los tipos de sus operandos.
ya que ms que clarificar el cdigo lo que hara sera dificultar su comprensin.
No puede definirse nuevos operadores ni combinaciones de los ya existentes con
De todas formas, suele ser buena idea que cada vez que se redefina un operador en un nuevos significados (por ejemplo ** para representar exponenciacin), pues ello
tipo de dato tambin se d una definicin de un mtodo que funcione de forma complicara innecesariamente el compilador, el lenguaje y la legibilidad del cdigo
equivalente al operador. As desde lenguajes que no soporten la redefinicin de cuando en realidad es algo que puede simularse definiendo mtodos.
operadores tambin podr realizarse la operacin y el tipo ser ms reutilizable.

Jos Antonio Gonzlez Seco Pgina 133 Jos Antonio Gonzlez Seco Pgina 134
El lenguaje de programacin C# Tema 11: Redefinicin de operadores El lenguaje de programacin C# Tema 11: Redefinicin de operadores

No todos los operadores incluidos en el lenguaje pueden redefinirse, pues muchos return 1;
de ellos (como ., new, =, etc.) son bsicos para el lenguaje y su redefinicin es }
inviable, poco til o dificultara innecesariamente la legibilidad del cdigo. Adems, public static void Main()
no todos los redefinibles se redefinen usando la sintaxis general hasta ahora vista, {
aunque en su momento se irn explicando cules son los redefinibles y cules son A o1 = new A();
las peculiaridades de aquellos que requieran una redefinicin especial. B o2 = new B();

Console.WriteLine(o1+o2={0}, o1+o2);
A continuacin se muestra cmo se redefinira el significado del operador + para los }
objetos Complejo del ejemplo anterior: }

class Complejo; Sin embargo, ms que una ocultacin de operadores lo que se tiene es un problema de
{
public float ParteReal;
ambigedad en la definicin del operador + entre objetos de tipos A y B, de la que se
public float ParteImaginaria; informar al compilar ya que el compilador no sabr cul versin del operador debe usar
para traducir o1+o2 a cdigo binario.
public Complejo (float parteReal, float parteImaginaria)
{
this.ParteReal = parteReal; Redefinicin de operadores unarios
this.ParteImaginaria = parteImaginaria;
}
Los nicos operadores unarios redefinibles son: !, +, -, ~, ++, --, true y false, y toda
public static Complejo operator +(Complejo op1, Complejo op2) redefinicin de un operador unario ha de tomar un nico parmetro que ha de ser del
{
Complejo resultado = new Complejo();
mismo tipo que el tipo de dato al que pertenezca la redefinicin.

resultado.ParteReal = op1.ParteReal + op2.ParteReal; Los operadores ++ y -- siempre ha de redefinirse de manera que el tipo de dato del
resultado.ParteImaginaria = op1.ParteImaginaria + op2.ParteImaginaria; objeto devuelto sea el mismo que el tipo de dato donde se definen. Cuando se usen de
return resultado; forma prefija se devolver ese objeto, y cuando se usen de forma postifja el compilador
}
}
lo que har ser devolver el objeto original que se les pas como parmetro en lugar del
indicado en el return. Por ello es importante no modificar dicho parmetro si es de un
Es fcil ver que lo que en el ejemplo se ha redefinido es el significado del operador + tipo referencia y queremos que estos operadores tengan su significado tradicional. Un
para que cuando se aplique entre dos objetos de clase Complejo devuelva un nuevo ejemplo de cmo hacerlo es la siguiente redefinicin de ++ para el tipo Complejo:
objeto Complejo cuyas partes real e imaginaria sea la suma de las de sus operandos.
public static Complejo operator ++ (Complejo op)
{
Se considera errneo incluir la palabra reservada new en la redefinicin de un operador, Complejo resultado = new Complejo(op.ParteReal + 1, op.ParteImaginaria);
ya que no pueden ocultarse redefiniciones de operadores en tanto que estos no se
aplican utilizando el nombre del tipo en que estn definidos. Las nicas posibles return resultado;
coincidencias se daran en situaciones como la del siguiente ejemplo: }

using System; Ntese que si hubisemos redefinido el ++ de esta otra forma:

class A public static Complejo operator ++ (Complejo op)


{ {
public static int operator +(A obj1, B obj2) op.ParteReal++;
{
Console.WriteLine(Aplicado + de A); return op;
return 1; }
}
} Entonces el resultado devuelto al aplicrselo a un objeto siempre sera el mismo tanto si
class B:A
fue aplicado de forma prefija como si lo fue de forma postifija, ya que en ambos casos
{ el objeto devuelto sera el mismo. Sin embargo, eso no ocurrira si Complejo fuese una
public static int operator +(A obj1, B obj2) estructura, ya que entonces op no sera el objeto original sino una copia de ste y los
{ cambios que se le hiciesen en el cuerpo de la redefinicin de ++ no afectaran al objeto
Console.WriteLine(Aplicado + de B); original, que es el que se devuelve cuando se usa ++ de manera postfija.

Jos Antonio Gonzlez Seco Pgina 135 Jos Antonio Gonzlez Seco Pgina 136
El lenguaje de programacin C# Tema 11: Redefinicin de operadores El lenguaje de programacin C# Tema 11: Redefinicin de operadores

redefinicin redefiniendo los operadores unarios true y false, los operadores binarios &
Respecto a los operadores true y false, estos indican respectivamente, cuando se ha de y | y teniendo en cuenta que && y || se evalan as:
considerar que un objeto representa el valor lgico cierto y cuando se ha de considerar
que representa el valor lgico falso, por lo que sus redefiniciones siempre han de &&: Si tenemos una expresin de la forma x && y, se aplica primero el operador
devolver un objeto de tipo bool que indique dicha situacin. Adems, si se redefine uno false a x. Si devuelve false, entonces x && y devuelve el resultado de evaluar x; y
es obligatorio redefinir tambin el otro, pues siempre es posible usar indistintamente si no, entonces devuelve el resultado de evaluar x & y
uno u otro para determinar el valor lgico que un objeto de ese tipo represente.
||: Si tenemos una expresin de la forma x || y, se aplica primero el operador true
En realidad los operadores true y false no pueden usarse directamente en el cdigo a x. Si devuelve true, se devuelve el resultado de evaluar x; y si no, se devuelve
fuente, sino que redefinirlos para un tipo de dato es til porque permiten utilizar objetos el de evaluar x | y.
de ese tipo en expresiones condicionales tal y como si de un valor lgico se tratase. Por
ejemplo, podemos redefinir estos operadores en el tipo Complejo de modo que
consideren cierto a todo complejo distinto de 0 + 0i y falso a 0 + 0i: Redefiniciones de operadores de conversin
public static bool operator true(Complejo op)
{ En el Tema 4: Aspectos Lxicos ya vimos que para convertir objetos de un tipo de dato
return (op.ParteReal != 0 || op.ParteImaginaria != 0); en otro se puede usar un operador de conversin que tiene la siguiente sintaxis:
}
(<tipoDestino>) <expresin>
public static bool operator false(Complejo op)
{ Lo que este operador hace es devolver el objeto resultante de convertir al tipo de dato de
return (op.ParteReal == 0 && op.ParteImaginaria == 0);
} nombre <tipoDestino> el objeto resultante de evaluar <expresin> Para que la conversin
pueda aplicarse es preciso que exista alguna definicin de cmo se ha de convertir a
Con estas redefiniciones, un cdigo como el que sigue mostrara por pantalla el mensaje <tipoDestino> los objetos del tipo resultante de evaluar <expresin> Esto puede indicarse
Es cierto: introduciendo como miembro del tipo de esos objetos o del tipo <tipoDestino> una
redefinicin del operador de conversin que indique cmo hacer la conversin del tipo
Complejo c1 = new Complejo(1, 0); // c1 = 1 + 0i del resultado de evaluar <expresin> a <tipoDestino>
if (c1)
System.Console.WriteLine(Es cierto); Las redefiniciones de operadores de conversin pueden ser de dos tipos:

Explcitas: La conversin slo se realiza cuando se usen explcitamente los


Redefinicin de operadores binarios operadores de conversin antes comentado.

Los operadores binarios redefinibles son +, -, *, /, %, &, |, ^, <<, >>, ==, !=, >, <, >= y <= Implcitas: La conversin tambin se realiza automticamente cada vez que se
Toda redefinicin que se haga de ellos ha de tomar dos parmetros tales que al menos asigne un objeto de ese tipo de dato a un objeto del tipo <tipoDestino>. Estas
uno sea del mismo tipo que el tipo de dato del que es miembro la redefinicin. conversiones son ms cmodas que las explcitas pero tambin ms peligrosas
ya que pueden ocurrir sin que el programador se d cuenta. Por ello, slo
Hay que tener en cuenta que aquellos de estos operadores que tengan complementario deberan definirse como implcitas las conversiones seguras en las que no se
siempre han de redefinirse junto con ste. Es decir, siempre que se redefina en un tipo el puedan producir excepciones ni perderse informacin al realizarlas.
operador > tambin ha de redefinirse en l el operador <, siempre que se redefina >= ha
de redefinirse <=, y siempre que se redefina == ha de redefinirse !=. En un mismo tipo de dato pueden definirse mltiples conversiones siempre y cuando el
tipo origen de las mismas sea diferente. Por tanto, no es vlido definir a la vez en un
Tambin hay que sealar que, como puede deducirse de la lista de operadores binarios mismo tipo una versin implcita de una cierta conversin y otra explcita.
redefinibles dada, no es redefinir directamente ni el operador de asignacin = ni los
operadores compuestos (+=, -=, etc.) Sin embargo, en el caso de estos ltimos dicha La sintaxis que se usa para hacer redefinir una operador de conversin es parecida a la
redefinicin ocurre de manera automtica al redefinir su parte no = Es decir, al usada para cualquier otro operador slo que no hay que darle nombre, toma un nico
redefinir + quedar redefinido consecuentemente +=, al redefinir * lo har *=, etc. parmetro y hay que preceder la palabra reservada operator con las palabras reservadas
explicit o implicit segn se defina la conversin como explcita o implcita. Por ejemplo,
Por otra parte, tambin cabe sealar que no es posible redefinir directamente los para definir una conversin implcita de Complejo a float podra hacerse:
operadores && y ||. Esto se debe a que el compilador los trata de una manera especial
que consiste en evaluarlos perezosamente. Sin embargo, es posible simular su public static implicit operator float(Complejo op)

Jos Antonio Gonzlez Seco Pgina 137 Jos Antonio Gonzlez Seco Pgina 138
El lenguaje de programacin C# Tema 11: Redefinicin de operadores El lenguaje de programacin C# Tema 11: Redefinicin de operadores

{ {
return op.ParteReal; return new A();
} }
}
Ntese que el tipo del parmetro usado al definir la conversin se corresponde con el
class B
tipo de dato del objeto al que se puede aplicar la conversin (tipo origen), mientras que {
el tipo del valor devuelto ser el tipo al que se realice la conversin (tipo destino) Con public static implicit operator A(B obj)
esta definicin podran escribirse cdigos como el siguiente: {
return new A();
Complejo c1 = new Complejo(5,2); // c1 = 5 + 2i }
float f = c1; // f = 5 }

Ntese que en la conversin de Complejo a float se pierde informacin (la parte El problema de este tipo de errores es que puede resulta difcil descubrir sus causas en
imaginaria), por lo que sera mejor definir la conversin como explcita sustituyendo en tanto que el mensaje que el compilador emite indica que no se pueden convertir los
su definicin la palabra reservada implicit por explicit. En ese caso, el cdigo anterior objetos A en objetos B pero no aclara que ello se deba a una ambigedad.
habra de cambiarse por:
Otro error con el que hay que tener cuidado es con el hecho de que puede ocurrir que al
Complejo c1 = new Complejo(5,2); // c1 = 5 + 2i mezclar redefiniciones implcitas con mtodos sobrecargados puedan haber
float f = (float) c1; // f = 5 ambigedades al determinar a qu versin del mtodo se ha de llamar. Por ejemplo,
dado el cdigo:
Por otro lado, si lo que hacemos es redefinir la conversin de float a Complejo con:
using System;
public static implicit operator Complejo(float op)
{ class A
return (new Complejo(op, 0)); {
}
public static implicit operator A(B obj)
{
Entonces se podra crear objetos Complejo as: return new A();
}
Complejo c2 = 5; // c2 = 5 + 0i
public static void MtodoSobrecargado(A o)
Vase que en este caso nunca se perder informacin y la conversin nunca fallar, por {
lo que es perfectamente vlido definirla como implcita. Adems, ntese como Console.WriteLine("Versin que toma A");
redefiniendo conversiones implcitas puede conseguirse que los tipos definidos por el }
usuario puedan inicializarse directamente a partir de valores literales tal y como si
public static void MtodoSobrecargado(C o)
fuesen tipos bsicos del lenguaje. {
Console.WriteLine("Versin que toma C");
En realidad, cuando se definan conversiones no tiene porqus siempre ocurrir que el }
tipo destino indicado sea el tipo del que sea miembro la redefinicin, sino que slo ha
de cumplirse que o el tipo destino o el tipo origen sean de dicho tipo. O sea, dentro de static void Main(string[] args)
{
un tipo de dato slo pueden definirse conversiones de ese tipo a otro o de otro tipo a ese. MtodoSobrecargado(new B());
Sin embargo, al permitirse conversiones en ambos sentidos hay que tener cuidado }
porque ello puede producir problemas si se solicitan conversiones para las que exista }
una definicin de cmo realizarlas en el tipo fuente y otra en el tipo destino. Por
ejemplo, el siguiente cdigo provoca un error al compilar debido a ello: class B
{
class A public static implicit operator C(B obj)
{ {
static void Main(string[] args) return new C();
{ }
A obj = new B(); // Error: Conversin de B en A ambigua
} }

public static implicit operator A(B obj) class C


{}

Jos Antonio Gonzlez Seco Pgina 139 Jos Antonio Gonzlez Seco Pgina 140
El lenguaje de programacin C# Tema 11: Redefinicin de operadores El lenguaje de programacin C# Tema 11: Redefinicin de operadores

vlidas. Es decir, no pueden definirse conversiones entre un tipo y sus antecesores (por
Al compilarlo se producir un error debido a que en la llamada a MtodoSobrecargado() el polimorfismo ya existen), ni entre un tipo y l mismo, ni entre tipos e interfaces por
el compilador no puede deducir a qu versin del mtodo se desea llamar ya que existen ellos implementadas (las interfaces se explicarn en el Tema 15: Interfaces)
conversiones implcitas de objetos de tipo B en cualquiera de los tipos admitidos por sus
distintas versiones. Para resolverlo lo mejor especificar explcitamente en la llamada la
conversin a aplicar usando el operador () Por ejemplo, para usar usar la versin del
mtodo que toma como parmetro un objeto de tipo A se podra hacer:
MtodoSobrecargado ( (A) new B());

Sin embargo, hay que tener cuidado ya que si en vez del cdigo anterior se tuviese:
class A
{

public static implicit operator A(B obj)


{
return new A();
}

public static void MtodoSobrecargado(A o)


{
Console.WriteLine("Versin que toma A");
}

public static void MtodoSobrecargado(C o)


{
Console.WriteLine("Versin que toma C");
}

static void Main(string[] args)


{
MtodoSobrecargado(new B());
}
}

class B
{
public static implicit operator A(B obj)
{
return new A();
}

public static implicit operator C(B obj)


{
return new C();
}
}

class C
{}

Entonces el fuente compilara con normalidad y al ejecutarlo se mostrara el siguiente


mensaje que demuestra que se ha usado la versin del mtodo que toma un objeto C.

Finalmente, hay que sealar que no es posible definir cualquier tipo de conversin, sino
que aquellas para las que ya exista un mecanismo predefinido en el lenguaje no son

Jos Antonio Gonzlez Seco Pgina 141 Jos Antonio Gonzlez Seco Pgina 142
El lenguaje de programacin C# Tema 12: Delegados y eventos El lenguaje de programacin C# Tema 12: Delegados y eventos

TEMA 12: Delegados y eventos Cualquier intento de almacenar en este delegado mtodos que no tomen slo un int
como parmetro o no devuelvan un string producir un error de compilacin o, si no
pudiese detectarse al compilar, una excepcin de tipo System.ArgumentNullException
Concepto de delegado en tiempo de ejecucin. Esto puede verse con el siguiente programa de ejemplo:
using System;
Un delegado es un tipo especial de clase cuyos objetos pueden almacenar referencias a using System.Reflection;
uno o ms mtodos de tal manera que a travs del objeto sea posible solicitar la
public delegate void D();
ejecucin en cadena de todos ellos.
public class ComprobacinDelegados
Los delegados son muy tiles ya que permiten disponer de objetos cuyos mtodos {
puedan ser modificados dinmicamente durante la ejecucin de un programa. De hecho, public static void Main()
son el mecanismo bsico en el que se basa la escritura de aplicaciones de ventanas en la {
Type t = typeof(ComprobacinDelegados);
plataforma .NET. Por ejemplo, si en los objetos de una clase Button que represente a los MethodInfo m = t.GetMethod(Mtodo1);
botones estndar de Windows definimos un campo de tipo delegado, podemos D obj = (D) Delegate.CreateDelegate(typeof(D), m);
conseguir que cada botn que se cree ejecute un cdigo diferente al ser pulsado sin ms obj();
que almacenar el cdigo a ejecutar por cada botn en su campo de tipo delegado y luego }
solicitar la ejecucin todo este cdigo almacenado cada vez que se pulse el botn.
public static void Mtodo1()
{ Console.WriteLine(Ejecutado Mtodo1); }
Sin embargo, tambin son tiles para muchsimas otras cosas tales como asociacin de
cdigo a la carga y descarga de ensamblados, a cambios en bases de datos, a cambios en public static void Mtodo2(string s)
el sistema de archivos, a la finalizacin de operaciones asncronas, la ordenacin de { Console.WriteLine(Ejecutado Mtodo2); }
conjuntos de elementos, etc. En general, son tiles en todos aquellos casos en que }
interese pasar mtodos como parmetros de otros mtodos.
Lo que se hace en el mtodo Main() de este programa es crear a partir del objeto Type
que representa al tipo ComprobacinDelegados un objeto System.Reflection.MethodInfo
Adems, los delegados proporcionan un mecanismo mediante el cual unos objetos
que representa a su mtodo Mtodo1. Como se ve, para crear el objeto Type se utiliza el
pueden solicitar a otros que se les notifique cuando ocurran ciertos sucesos. Para ello,
operador typeof ya estudiado, y para obtener el objeto MethodInfo se usa su mtodo
bastara seguir el patrn consistente en hacer que los objetos notificadores dispongan de
GetMethod() que toma como parmetro una cadena con el nombre del mtodo cuyo
algn campo de tipo delegado y hacer que los objetos interesados almacenen mtodos
MethodInfo desee obtenerse. Una vez conseguido, se crea un objeto delegado de tipo D
suyos en dichos campos de modo que cuando ocurra el suceso apropiado el objeto
que almacene una referencia al mtodo por l representado a travs del mtodo
notificador simule la notificacin ejecutando todos los mtodos as asociados a l.
CreateDelegate() de la clase Delegate y se llama dicho objeto, lo que muestra el mensaje:

Ejecutado Mtodo1
Definicin de delegados
Aunque en vez de obtener el MethodInfo que representa al Mtodo1 se hubiese obtenido
Un delegado no es ms que un tipo especial de subclase System.MulticastDelegate. Sin el que representa al Mtodo2 el compilador no detectara nada raro al compilar ya que no
embargo, para definir estas clases no se puede utilizar el mecanismo de herencia normal es lo bastante inteligente como para saber que dicho objeto no representa a un mtodo
sino que ha de seguirse la siguiente sintaxis especial: almacenable en objetos delegados de tipo D. Sin embargo, al ejecutarse la aplicacin el
CLR s que lo detectara y ello provocara una ArgumentNullException
<modificadores> delegate <tipoRetorno> <nombreDelegado> (<parmetros>);
Esto es un diferencia importante de los delegados respecto a los punteros a funcin de
<nombreDelegado> ser el nombre de la clase delegado que se define, mientras que C/C++ (que tambin pueden almacenar referencias a mtodos), ya que con estos ltimos
<tipoRetorno> y <parmetros> se correspondern, respectivamente, con el tipo del valor no se realizan dichas comprobaciones en tiempo de ejecucin y puede terminar
de retorno y la lista de parmetros de los mtodos cuyos cdigos puede almacenar en su ocurriendo que un puntero a funcin apunte a un mtodo cuya signatura o valor de
interior los objetos de ese tipo delegado (objetos delegados) retorno no se correspondan con los indicados en su definicin, lo que puede ocasionar
que el programa falle por causas difciles de detectar.
Un ejemplo de cmo definir un delegado de nombre Deleg cuyos objetos puedan
almacenar mtodos que devuelvan un string y tomen como parmetro un int es: Las definiciones de delegados tambin pueden incluir cualquiera de los modificadores
de accesibilidad vlidos para una clase, ya que al fin y al cabo los delegados son clases.
delegate void Deleg(int valor);

Jos Antonio Gonzlez Seco Pgina 143 Jos Antonio Gonzlez Seco Pgina 144
El lenguaje de programacin C# Tema 12: Delegados y eventos El lenguaje de programacin C# Tema 12: Delegados y eventos

Es decir, todos pueden incluir los modificadores public e internal, y los se definan llamar desde un tipo a mtodos privados de otros tipos que estn almacenados en un
dentro de otro tipo tambin pueden incluir protected, private y protected internal. delegado por accesible desde el primero tal y como muestra el siguiente ejemplo:
using System;
Manipulacin de objetos delegados public delegate void D();

Un objeto de un tipo delegado se crea exactamente igual que un objeto de cualquier class A
{
clase slo que en su constructor ha de pasrsele el nombre del mtodo cuyo cdigo
public static D obj;
almacenar. Este mtodo puede tanto ser un mtodo esttico como uno no esttico. En
el primer caso se indicara su nombre con la sintaxis <nombreTipo>.<nombreMtodo>, y public static void Main()
en el segundo se indicara con <objeto>.<nombreMtodo> {
B.AlmacenaPrivado();
obj();
Para llamar al cdigo almacenado en el delegado se usa una sintaxis similar a la de las
}
llamadas a mtodos, slo que no hay que prefijar el objeto delegado de ningn nombre }
de tipo o de objeto y se usa simplemente <objetoDelegado>(<valoresParmetros>)
class B
El siguiente ejemplo muestra cmo crear un objeto delegado de tipo D, asociarle el {
private static void Privado()
cdigo de un mtodo llamado F y ejecutar dicho cdigo a travs del objeto delegado:
{ Console.WriteLine(Llamado a mtodo privado); }
using System; public static void AlmacenaPrivado()
{ A.obj += new D(Privado); }
delegate void D(int valor); }
class EjemploDelegado
La llamada a AlmacenaPrivado en el mtodo Main() de la clase A provoca que en el campo
{
public static void Main() delegado obj de dicha clase se almacene una referencia al mtodo privado Privado() de la
{ clase B, y la instruccin siguiente provoca la llamada a dicho mtodo privado desde una
D objDelegado = new D(F); clase externa a la de su definicin como demuestra la salida del programa:
objDelegado(3);
} Llamado a mtodo privado

public static void F(int x) Para aadir nuevos mtodos a un objeto delegado se le aplica el operador += pasndole
{ como operando derecho un objeto delegado de su mismo tipo (no vale de otro aunque
Console.WriteLine( Pasado valor {0} a F(), x);
} admita los mismos tipos de parmetros y valor de retorno) que contenga los mtodos a
} aadirle, y para quitrselos se hace lo mismo pero con el operador -=. Por ejemplo, el
siguiente cdigo muestra los efectos de ambos operadores:
La ejecucin de este programa producir la siguiente salida por pantalla:
using System;
Pasado valor 3 a F()
delegate void D(int valor);
Ntese que para asociar el cdigo de F() al delegado no se ha indicado el nombre de este
mtodo esttico con la sintaxis <nombreTipo>.<nombreMtodo> antes comentada. Esto se class EjemploDelegado
{
debe a que no es necesario incluir el <nombreTipo>. cuando el mtodo a asociar a un
public string Nombre;
delegado es esttico y est definido en el mismo tipo que el cdigo donde es asociado
EjemploDelegado(string nombre)
En realidad un objeto delegado puede almacenar cdigos de mltiples mtodos tanto {
estticos como no estticos de manera que una llamada a travs suya produzca la Nombre = nombre;
}
ejecucin en cadena de todos ellos en el mismo orden en que se almacenaron en l.
Ntese que si los mtodos devuelven algn valor, tras la ejecucin de la cadena de public static void Main()
llamadas slo se devolver el valor de retorno de la ltima llamada. {
EjemploDelegado obj1 += new EjemploDelegado(obj1);
Adems, cuando se realiza una llamada a travs de un objeto delegado no se tienen en D objDelegado = new D(f);
cuenta los modificadores de visibilidad de los mtodos que se ejecutarn, lo que permite

Jos Antonio Gonzlez Seco Pgina 145 Jos Antonio Gonzlez Seco Pgina 146
El lenguaje de programacin C# Tema 12: Delegados y eventos El lenguaje de programacin C# Tema 12: Delegados y eventos

objDelegado += new D(obj1.g); Pasado valor 3 a g() en objeto obj1


objDelegado(3); Pasado valor 5 a f()
objDelegado -= new D(obj1.g); Pasado valor 6 a f()
objDelegado(5);
}

public void g(int x)


La clase System.MulticastDelegate
{
Console.WriteLine(Pasado valor {0} a g() en objeto {1}, x, Nombre); Ya se ha dicho que la sintaxis especial de definicin de delegados no es ms que una
} forma especial definir subclases de System.MulticastDelegate. Esta clase a su vez deriva
public static void f(int x) de System.Delegate, que representa a objetos delegados que slo puede almacenar un
{ nico mtodo. Por tanto, todos los objetos delegado que se definan contarn con los
Console.WriteLine( Pasado valor {0} a f(), x); siguientes miembros comunes heredados de estas clases:
}
}
object Target: Propiedad de slo lectura que almacena el objeto al que pertenece
el ltimo mtodo aadido al objeto delegado. Si es un mtodo de clase vale null.
La salida producida por pantalla por este programa ser:
Pasado valor 3 a f() MethodInfo Method: Propiedad de slo lectura que almacena un objeto
Pasado valor 3 a g() en objeto obj1 System.Reflection.MethodInfo con informacin sobre el ltimo mtodo aadido
Pasado valor 5 a f() al objeto (nombre, modificadores, etc.) Para saber cmo acceder a estos datos
puede consultar la documentacin incluida en el SDK sobre la clase MethodInfo
Como se ve, cuando ahora se hace la llamada objDelegado(3) se ejecutan los cdigos de
los dos mtodos almacenados en objDelegado, y al quitrsele luego uno de estos cdigos
Delegate[] getInvocationList(): Permite acceder a todos los mtodos almacenados
la siguiente llamada slo ejecuta el cdigo del que queda. Ntese adems en el ejemplo
en un delegado, ya que devuelve una tabla cuyos elementos son delegados cada
como la redefinicin de + realizada para los delegados permite que se pueda inicializar
uno de los cuales almacenan uno, y slo uno, de los mtodos del original. Estos
objDelegado usando += en vez de =. Es decir, si uno de los operandos de + vale null no
delegados se encuentran ordenados en la tabla en el mismo orden en que sus
se produce ninguna excepcin, sino que tan slo no se aade ningn mtodo al otro.
mtodos fueron fue almacenados en el objeto delegado original.
Hay que sealar que un objeto delegado vale null si no tiene ningn mtodo asociado,
Este mtodo es especialmente til porque a travs de la tabla que retorna se
ya sea porque no se ha llamado an a su constructor o porque los que tuviese asociado
pueden hacer cosas tales como ejecutar los mtodos del delegado en un orden
se le hayan quitado con -=. As, si al Main() del ejemplo anterior le aadimos al final:
diferente al de su almacenamiento, procesar los valores de retorno de todas las
objDelegado -= new D(f);
llamadas a los mtodos del delegado original, evitar que una excepcin en la
objDelegado(6); ejecucin de uno de los mtodos impida la ejecucin de los dems, etc.

Se producir al ejecutarlo una excepcin de tipo System.NullReferenceException Aparte de estos mtodos de objeto, la clase System.MulticastDelegate tambin cuenta
indicando que se ha intentado acceder a una referencia nula. con los siguientes mtodos de tipo de uso frecuente:

Tambin hay que sealar que para que el operador -= funcione se le ha de pasar como static Delegate Combine(Delegate fuente, Delegate destino): Devuelve un nuevo
operador derecho un objeto delegado que almacene algn mtodo exactamente igual al objeto delegado que almacena la concatenacin de los mtodos de fuente con los
mtodo que se le quiera quitar al objeto delegado de su lado izquierdo. Por ejemplo, si de destino. Por tanto, ntese que estas tres instrucciones son equivalentes:
se le quiere quitar un mtodo de un cierto objeto, se le ha de pasar un objeto delegado
que almacene ese mtodo de ese mismo objeto, y no vale que almacene ese mtodo pero objDelegado += new D(obj1.g);
objDelegado = objDelegado + new D(obj1.g);
de otro objeto de su mismo tipo. Por ejemplo, si al Main() anterior le aadimos al final: objDelegado = (D) MulticastDelegate.Combine(objDelegado, new D(obj1.g);

objDelegado -= new g(obj1.g); Es ms, en realidad el compilador de C# lo que hace es convertir toda aplicacin
objDelegado(6);
del operador + entre delegados en una llamada a Combine() como la mostrada.
Entonces no se producir ninguna excepcin ya que el -= no eliminar ningn mtodo
Hay que tener cuidado con los tipos de los delegados a combinar ya que han de
de objDelegado debido a que ese objeto delegado no contiene ningn mtodo g()
ser exactamente los mismos o si no se lanza una System.ArgumentException, y
procedente del objeto obj1. Es ms, la salida que se producir por pantalla ser:
ello ocurre an en el caso de que dichos slo se diferencien en su nombre y no
Pasado valor 3 a f() en sus tipos de parmetros y valor de retorno.

Jos Antonio Gonzlez Seco Pgina 147 Jos Antonio Gonzlez Seco Pgina 148
El lenguaje de programacin C# Tema 12: Delegados y eventos El lenguaje de programacin C# Tema 12: Delegados y eventos

BeginInvoke() crea un hilo que ejecutar los mtodos almacenados en el objeto delegado
static Delegate Combine(Delegate[] tabla): Devuelve un nuevo delegado cuyos sobre el que se aplica con los parmetros indicados en <parmetros> y devuelve un
mtodos almacenados son la concatenacin de todos los de la lista que se le pasa objeto IAsyncResult que almacenar informacin relativa a ese hilo (por ejemplo, a
como parmetro y en el orden en que apareciesen en ella. Es una buena forma de travs de su propiedad de slo lectura bool IsComplete puede consultarse si ha
crear delegados con muchos mtodos sin tener que aplicar += varias veces. terminado su labor) Slo tiene sentido llamarlo si el objeto delegado sobre el que se
Todos los objetos delegados de la tabla han de ser del mismo tipo, pues si no se aplica almacena un nico mtodo, pues si no se lanza una System.ArgumentException.
producira una System.ArgumentException.
El parmetro cb de BeginInvoke() es un objeto de tipo delegado que puede almacenar
static Delegate Remove(Delegate original, Delegate aBorrar): Devuelve un nuevo mtodos a ejecutar cuando el hilo antes comentado finalice su trabajo. A estos mtodos
delegado cuyos mtodos almacenados son el resultado de eliminar de original los el CLR les pasar automticamente como parmetro el IAsyncResult devuelto por
que tuviese aBorrar. Por tanto, estas instrucciones son equivalentes: BeginInvoke(), estando as definido el delegado destinado a almacenarlos:

objDelegado -= new D(obj1.g); public delegate void ASyncCallback(IASyncResult obj);


objDelegado - objDelegado - new D(obj1.g);
objDelegado = (D) MulticastDelegate.Remove(objDelegado, new D(obj1.g); Por su parte, el parmetro o de BeginInvoke puede usarse para almacenar cualquier
informacin adicional que se considere oportuna. Es posible acceder a l a travs de la
Nuevamente, lo que hace el compilador de C# es convertir toda aplicacin del propiedad object AsyncState del objeto IAsyncResult devuelto por BeginInvoke()
operador - entre delegados en una llamada a Remove() como la mostrada. Por
tanto, al igual que con -=, para borrar mtodos de objeto se ha de especificar en En caso de que no se desee ejecutar ningn cdigo especial al finalizar el hilo de
aBorrar un objeto delegado que contenga referencias a mtodos asociados a
ejecucin asncrona o no desee usar informacin adicional, puede darse sin ningn tipo
exactamente los mismos objetos que los almacenados en original. de problema el valor null a los ltimos parmetros de BeginInvoke() segn corresponda.
static Delegate CreateDelegate (Type tipo, MehodInfo mtodo): Ya se us este Finalmente, EndInvoke() se usa para recoger los resultados de la ejecucin asncrona de
mtodo en el ejemplo de comprobacin de tipos del epgrafe Definicin de los mtodos iniciada a travs BeginInvoke() Por ello, su valor de retorno es del mismo
delegados de este mismo tema. Como recordar permite crear dinmicamente tipo que los mtodos almacenables en el objeto delegado al que pertenece y en
objetos delegados, ya que devuelve un objeto delegado del tipo indicado que <parmetrosRefOut> se indican los parmetros de salida y por referencia de dichos
almacena una referencia al mtodo representado por su segundo parmetro. mtodos. Su tercer parmetro es el IAsyncResult devuelto por el BeginInvoke() que cre
el hilo cuyos resultados se solicita recoger y se usa precisamente para identificarlo. Si
ese hilo no hubiese terminado an de realizar las llamadas, se esperar a que lo haga.
Llamadas asncronas
Para ilustrar mejor el concepto de llamadas asncronas, el siguiente ejemplo muestra
La forma de llamar a mtodos que hasta ahora se ha explicado realiza la llamada de cmo encapsular en un objeto delegado un mtodo F() para ejecutarlo asncronamente:
manera sncrona, lo que significa que la instruccin siguiente a la llamada no se ejecuta
hasta que no finalice el mtodo llamado. Sin embargo, a todo mtodo almacenado en un D objDelegado = new D (F);
objeto delegado tambin es posible llamar de manera asncrona a travs de los mtodos IAsyncResult hilo = objDelegado.BeginInvoke(3, new AsyncCallback(M), prueba);
// ... Hacer cosas
del mismo, lo que consiste en que no se espera a que acabe de ejecutarse para pasar a la objDelegado.EndInvoke(hilo);
instruccin siguiente a su llamada sino que su ejecucin se deja en manos de un hilo
aparte que se ir ejecutndolo en paralelo con el hilo llamante. Donde el mtodo M ha sido definido en la misma clase que este cdigo as:
Por tanto los delegados proporcionan un cmodo mecanismo para ejecutar cualquier public static void M(IAsyncResult obj)
mtodo asncronamente, pues para ello basta introducirlo en un objeto delegado del tipo {
apropiado. Sin embargo, este mecanismo de llamada asncrona tiene una limitacin, y es Console.WriteLine(Llamado a M() con {0}, obj.AsyncState);
que slo es vlido para objetos delegados que almacenen un nico mtodo. }

Si entre el BeginInvoke() y el EndInvoke() no hubiese habido ninguna escritura en


Para hacer posible la realizacin de llamadas asncronas, aparte de los mtodos
pantalla, la salida del fragmento de cdigo anterior sera:
heredados de System.MulticastDelegate todo delegado cuenta con estos otros dos que el
compilador define a su medida en la clase en que traduce la definicin de su tipo: Pasado valor 3 a F()
Llamado a M() con prueba
IAsyncResult BeginInvoke(<parmetros>, AsyncCallback cb, Object o)
<tipoRetorno> EndInvoke(<parmetrosRefOut>, IASyncResult ar) La llamada a BeginInvoke() lanzar un hilo que ejecutar el mtodo F() almacenado en
objDelegado, pero mientras tanto tambin seguir ejecutndose el cdigo del hilo desde

Jos Antonio Gonzlez Seco Pgina 149 Jos Antonio Gonzlez Seco Pgina 150
El lenguaje de programacin C# Tema 12: Delegados y eventos El lenguaje de programacin C# Tema 12: Delegados y eventos

donde se llam a BeginInvoke() Slo tras llamar a EndInvoke() se puede asegurar que se llam, aunque se muestra a continuacin antes de acceder a ella hay que
habr ejecutado el cdigo de F(), pues mientras tanto la evolucin de ambos hilos es convertir el parmetro IAsyncResult de ese mtodo en un AsyncResult:
prcticamente indeterminable ya que depende del cmo acte el planificador de hilos.
public static void M(IAsyncResult iar)
{
An si el hilo llamador modifica el valor de alguno de los parmetros de salida o por D objetoDelegado = (D) ((AsyncResult iar)).AsyncDelegate;
referencia de tipos valor, el valor actualizado de stos no ser visible para el hilo
llamante hasta no llamar a EndInvoke() Sin embargo, el valor de los parmetros de tipos // A partir de aqu podra llamarse a EndInvoke() a travs de objetoDelegado
referencia s que podra serlo. Por ejemplo, dado un cdigo como: }

int x=0;
Persona p = new Persona(Josan, 7361928-E, 22); Implementacin interna de los delegados
IAsyncResult res = objetoDelegado.BeginInvoke(ref x, p, null, null);
// Hacer cosas... Cuando hacemos una definicin de delegado de la forma:
objetoDelegado.EndInvoke(ref x, res);
<modificadores> delegate <tipoRetorno> <nombre>(<parmetros>);
Si en un punto del cdigo comentado con // Hacer cosas..., donde el hilo asncrono ya
hubiese modificado los contenidos de x y p, se intentase leer los valores de estas El compilador internamente la transforma en una definicin de clase de la forma:
variables, slo se leera el valor actualizado de p. El de x no se vera hasta despus de la
llamada a EndInvoke() <modificadores> class <nombre>:System.MulticastDelegate
{
Por otro lado, hay que sealar que si durante la ejecucin asncrona de un mtodo se private object _target;
private int _methodPtr;
produce alguna excepcin, sta no sera notificada pero provocara que el hilo asncrono
private MulticastDelegate _prev;
abortase. Si posteriormente se llamase a EndInvoke() con el IAsyncResult asociado a
dicho hilo, se relanzara la excepcin que produjo el aborto y entonces podra tratarse. public <nombre>(object objetivo, int punteroMtodo)
{...}
Para optimizar las llamadas asncronas es recomendable marcar con el atributo OneWay
public virtual <tipoRetorno> Invoke(<parmetros>)
definido en System.Runtime.Remoting.Messaging los mtodos cuyo valor de retorno y
{...}
valores de parmetros de salida no nos importen, pues ello indica a la infraestructura
encargada de hacer las llamadas asncronas que no ha de considerar. Por ejemplo: public virtual IAsyncResult BeginInvoke(<parmetros>, AsyncCallback cb, Object o)
{...}
[OneWay] public void Mtodo()
{}
public virtual <tipoRetorno> EndInvoke(<parmetrosRefOut>, IASyncResult ar)
{...}
Ahora bien, hay que tener en cuenta que hacer esto implica perder toda posibilidad de }
tratar las excepciones que pudiese producirse al ejecutar asncronamente el mtodo
atribuido, pues con ello llamar a EndInvoke() dejara de relanzar la excepcin producida. Lo primero que llama la atencin al leer la definicin de esta clase es que su constructor
no se parece en absoluto al que hemos estado usando hasta ahora para crear objetos
Por ltimo, a modo de resumen a continuacin se indican cules son los patrones que delegado. Esto se debe a que en realidad, a partir de los datos especificados en la forma
pueden seguirse para recoger los resultados de una llamada asncrona: de usar el constructor que el programador utiliza, el compilador es capaz de determinar
los valores apropiados para los parmetros del verdadero constructor, que son:
1. Detectar si la llamada asncrona ha finalizado mirando el valor de la propiedad
IsComplete del objeto IAsyncResult devuelto por BeginInvoke() Cuando sea as,
object objetivo contiene el objeto al cual pertenece el mtodo especificado, y su
con EndInvoke() puede recogerse sus resultados. valor se guarda en el campo _target. Si es un mtodo esttico almacena null.
2. Pasar un objeto delegado en el penltimo parmetro de BeginInvoke() con el
int punteroMtodo contiene un entero que permite al compilador determinar cul
mtodo a ejecutar cuando finalice el hilo asncrono, lo que liberara al hilo es el mtodo del objeto al que se desea llamar, y su valor se guarda en el campo
llamante de la tarea de tener que andar mirando si ha finalizado o no. _methodPtr. Segn donde se haya definido dicho mtodo, el valor de este
parmetro proceder de las tablas MethodDef o MethodRef de los metadatos.
Si desde dicho mtodo se necesitase acceder a los resultados del mtodo llamado
podra accederse a ellos a travs de la propiedad AsyncDelegate del objeto El campo privado _prev de un delegado almacena una referencia al delegado previo al
IAsyncResult que recibe. Esta propiedad contiene el objeto delegado al que se
mismo en la cadena de mtodos. En realidad, en un objeto delegado con mltiples

Jos Antonio Gonzlez Seco Pgina 151 Jos Antonio Gonzlez Seco Pgina 152
El lenguaje de programacin C# Tema 12: Delegados y eventos El lenguaje de programacin C# Tema 12: Delegados y eventos

mtodos lo que se tiene es una cadena de objetos delegados cada uno de los cuales Eventos
contiene uno de los mtodos y una referencia (en _prev) a otro objeto delegado que
contendr otro de los mtodos de la cadena. Concepto de evento
Cuando se crea un objeto delegado con new se da el valor null a su campo _prev para as
Un evento es una variante de las propiedades para los campos cuyos tipos sean
indicar que no pertenece a una cadena sino que slo contiene un mtodo. Cuando se
delegados. Es decir, permiten controlar la forman en que se accede a los campos
combinen dos objetos delegados (con + o Delegate.Combine()) el campo _prev del nuevo
delegados y dan la posibilidad de asociar cdigo a ejecutar cada vez que se aada o
objeto delegado creado enlazar a los dos originales; y cuando se eliminen mtodos de
elimine un mtodo de un campo delegado.
la cadena (con o Delegate.Remove()) se actualizarn los campos _prev de la cadena
para que salten a los objetos delegados que contenan los mtodos eliminados.
Cuando se solicita la ejecucin de los mtodos almacenados en un delegado de manera
Sintaxis bsica de definicin de eventos
asncrona lo que se hace es llamar al mtodo Invoke() del mismo. Por ejemplo, una
llamada como esta:
La sintaxis bsica de definicin de un evento consiste en definirlo como cualquier otro
objDelegado(49); campo con la nica peculiaridad de que se le ha de anteponer la palabra reservada event
al nombre de su tipo (que ser un delegado) O sea, se sigue la sintaxis:
Es convertida por el compilador en:
<modificadores> event <tipoDelegado> <nombreEvento>;
objDelegado.Invoke(49);
Por ejemplo, para definir un evento de nombre Prueba y tipo delegado D se hara:
Aunque Invoke() es un mtodo pblico, C# no permite que el programador lo llame
explcitamente. Sin embargo, otros lenguajes gestionados s que podran permitirlo. public event D Prueba;

El mtodo Invoke() se sirve de la informacin almacenada en _target, _methodPtr y Tambin pueden definirse mltiples eventos en una misma lnea separando sus nombres
_prev, para determinar a cul mtodo se ha de llamar y en qu orden se le ha de llamar. mediante comas. Por ejemplo:
As, la implementacin de Invoke() ser de la forma:
public event D Prueba1, Prueba2;
public virtual <tipoRetorno> Invoke(<parmetros>)
{ Desde cdigo ubicado dentro del mismo tipo de dato donde se haya definido el evento
if (_prev!=null) se puede usar el evento tal y como si de un campo delegado normal se tratase. Sin
_prev.Invoke(<parmetros>); embargo, desde cdigo ubicado externamente se imponen una serie de restricciones que
return _target._methodPtr(<parmetros>); permiten controlar la forma en que se accede al mismo:
}
No se le puede aplicar los mtodos heredados de System.MulticastDelegate.
Obviamente la sintaxis _target._methodPtr no es vlida en C#, ya que _methodPtr no
es un mtodo sino un campo. Sin embargo, se ha escrito as para poner de manifiesto Slo se le puede aplicar dos operaciones: aadido de mtodos con += y
que lo que el compilador hace es generar el cdigo apropiado para llamar al mtodo eliminacin de mtodos con -=. De este modo se evita que se use sin querer = en
perteneciente al objeto indicado en _target e identificado con el valor de _methodPtr vez de += -= y se sustituyan todos los mtodos de la lista de mtodos del
campo delegado por otro que en realidad se le quera aadir o quitar (si ese otro
Ntese que la instruccin if incluida se usa para asegurar que las llamadas a los mtodos valiese null, ello incluso podra provocar una System.NullReferenceException)
de la cadena se hagan en orden: si el objeto delegado no es el ltimo de la cadena.
(_prev!=null) se llamar antes al mtodo Invoke() de su predecesor. No es posible llamar a los mtodos almacenados en un campo delegado a travs
del mismo. Esto permite controlar la forma en que se les llama, ya que obliga a
Por ltimo, slo sealar que, como es lgico, en caso de que los mtodos que el objeto que la llamada tenga que hacerse a travs de algn mtodo pblico definido en la
delegado pueda almacenar no tengan valor de retorno (ste sea void), el cuerpo de definicin del tipo de dato donde el evento fue definido.
Invoke() slo vara en que la palabra reservada return es eliminada del mismo.

Sintaxis completa de definicin de eventos

Jos Antonio Gonzlez Seco Pgina 153 Jos Antonio Gonzlez Seco Pgina 154
El lenguaje de programacin C# Tema 12: Delegados y eventos El lenguaje de programacin C# Tema 12: Delegados y eventos

La verdadera utilidad de un evento es que permite controlar la forma en que se asocian y traduce toda llamada al evento en una llamada al campo delegado. Como este es
quitan mtodos de los objetos delegados con += y -=. Para ello se han de definir con la privado, por eso slo pueda accederse a l desde cdigo de su propio tipo de dato.
siguiente sintaxis avanzada:
En realidad, el compilador internamente traduce las secciones add y remove de la
<modificadores> event <tipoDelegado> <nombreEvento> definicin de un evento en mtodos de la forma:
{
add void add_<nombreEvento>(<tipoDelegado> value)
{ void remove_<nombreEvento>(<tipoDelegado> value)
<cdigoAdd>
}
remove Toda aplicacin de += y -= a un evento no es convertida en una llamada al campo
{ privado sino en una llamada al mtodo add/remove apropiado, como se puede observar
<cdigoRemove> analizando el MSIL de cualquier fuente donde se usen += y -= sobre eventos. Adems,
} como estos mtodos devuelven void se ser el tipo del valor devuelto al aplicar += -=
}
(y no el objeto asignado), lo que evitar que cdigo externo al tipo donde se haya
definido el evento pueda acceder directamente al campo delegado privado.
Con esta sintaxis no pueden definirse varios eventos en una misma lnea como ocurra
con la bsica. Su significado es el siguiente: cuando se asocie un mtodo con += al
Si en vez de la sintaxis bsica usamos la completa no se definir automticamente un
evento se ejecutar el <cdigoAdd>, y cuando se le quite alguno con = se ejecutar el
campo delegado por cada evento que se defina, por lo que tampoco ser posible hacer
<cdigoRemove>. Esta sintaxis es similar a la de los bloques set/get de las propiedades
referencia al mismo desde cdigo ubicado en la misma clase donde se ha definido. Sin
pero con una importante diferencia: aunque pueden permutarse las secciones add y
embargo ello permite que el programador pueda determinar, a travs de secciones add y
remove, es obligatorio incluir siempre a ambas.
remove, cmo se almacenarn los mtodos. Por ejemplo, para ahorrar memoria se puede
optar por usar un diccionario donde almacenar los mtodos asociados a varios eventos
La sintaxis bsica es en realidad una forma abreviada de usar la avanzada. As, la
de un mismo objeto en lugar de usar un objeto delegado por cada uno.
definicin public event D Prueba(int valor); la interpretara el compilador como:

private D prueba
Dado que las secciones add y remove se traducen como mtodos, los eventos tambin
podrn participar en el mecanismo de herencia y redefiniciones tpico de los mtodos.
public event D Prueba Es decir, en <modificadores> aparte de modificadores de acceso y el modificador static,
{ tambin se podrn incluir los modificadores relativos a herencia. En este sentido hay
[MethodImpl(MethodImlOptions.Synchronized)] que precisar algo: un evento definido como abstract ha de definirse siempre con la
add
{
sintaxis bsica (no incluir secciones add o remove)
prueba = (D) Delegate.Combine(prueba, value);
}

[MethodImpl(MethodImlOptions.Synchronized)]
remove
{
prueba = (D) Delegate.Remove(prueba, value);
}
}

Es decir, el compilador definir un campo delegado privado y cdigos para add y


remove que hagan que el uso de += y -= sobre el evento tenga el efecto que normalmente
tendran si se aplicasen directamente sobre el campo privado. Como se ve, dentro de
estos mtodos se puede usar value para hacer referencia al operando derecho de los
operadores += y -=. El atributo System.Runtime.InteropServices.MethodImpl que precede
a los bloques add y remove slo se incluye para asegurar que un cambio de hilo no
pueda interrumpir la ejecucin de sus cdigos asociados.

Las restricciones de uso de eventos desde cdigos externos al tipo donde se han
definido se deben a que en realidad stos no son objetos delegados sino que el objeto
delegado es el campo privado que internamente define el compilador. El compilador

Jos Antonio Gonzlez Seco Pgina 155 Jos Antonio Gonzlez Seco Pgina 156
El lenguaje de programacin C# Tema 13: Estructuras El lenguaje de programacin C# Tema 13: Estructuras

Si usamos este tipo en un cdigo como el siguiente:


TEMA 13: Estructuras
Punto p = new Punto(10,10);
Punto p2 = p;
p2.x = 100;
Concepto de estructura Console.WriteLine(p.x);

Una estructura es un tipo especial de clase pensada para representar objetos ligeros. Es Lo que se mostrar por pantalla ser 10. Esto se debe a que el valor de x modificado es
decir, que ocupen poca memoria y deban ser manipulados con velocidad, como objetos el de p2, que como es una copia de p los cambios que se le hagan no afectarn a p. Sin
que representen puntos, fechas, etc. Ejemplos de estructuras incluidas en la BCL son la embargo, si Punto hubiese sido definido como una clase entonces s que se hubiese
mayora de los tipos bsicos (excepto string y object), y de hecho las estructuras junto mostrado por pantalla 100, ya que en ese caso lo que se habra copiado en p2 habra sido
con la redefinicin de operadores son la forma ideal de definir nuevos tipos bsicos a una referencia a la misma direccin de memoria dinmica referenciada por p, por lo que
los que se apliquen las mismas optimizaciones que a los predefinidos. cualquier cambio que se haga en esa zona a travs de p2 tambin afectar a p.

De lo anterior se deduce que la asignacin entre objetos de tipos estructuras es mucho


Diferencias entre clases y estructuras ms lenta que la asignacin entre objetos de clases, ya que se ha de copiar un objeto
completo y no solo una referencia. Para aliviar esto al pasar objetos de tipos estructura
como parmetros, se da la posibilidad de pasarlos como parmetros por referencia
A diferencia de una clase y fiel a su espritu de ligereza, una estructura no puede
(modificador ref) o parmetros de salida (out) en vez de como parmetros de entrada.
derivar de ningn tipo y ningn tipo puede derivar de ella. Por estas razones sus
miembros no pueden incluir modificadores relativos a herencia, aunque con una
Todas las estructuras derivan implcitamente del tipo System.ValueType, que a su vez
excepcin: pueden incluir override para redefinir los miembros de System.Object.
deriva de la clase primigenia System.Object. ValueType tiene los mismos miembros que
su padre, y la nica diferencia sealable entre ambos es que en ValueType se ha
Otra diferencia entre las estructuras y las clases es que sus variables no almacenan
redefinido Equals() de modo que devuelva true si los objetos comparados tienen el
referencias a zonas de memoria dinmica donde se encuentran almacenados objetos sino
mismo valor en todos sus campos y false si no. Es decir, la comparacin entre
directamente referencian a objetos. Por ello se dice que las clases son tipos referencia y
estructuras con Equals() se realiza por valor.
las estructuras son tipos valor, siendo posible tanto encontrar objetos de estructuras en
pila (no son campos de clases) como en memoria dinmica (son campos de clases)
Respecto a la implementacin de la igualdad en los tipos definidos como estructuras,
tambin es importante tener muy en cuenta que el operador == no es en principio
Una primera consecuencia de esto es que los accesos a miembros de objetos de tipos
aplicable a las estructuras que defina el programador. Si se desea que lo tenga ha de
valor son mucho ms rpidos que los accesos a miembros de pilas, ya que es necesario
drsele explcitamente una redefinicin al definir dichas estructuras.
pasar por una referencia menos a la hora de acceder a ellos. Adems, el tiempo de
creacin y destruccin de estructuras tambin es inferior. De hecho, la destruccin de
los objetos almacenados en pila es prcticamente inapreciable ya que se realiza con un
Boxing y unboxing
simple decremento del puntero de pila y no interviene en ella el recolector de basura.

Otra consecuencia de lo anterior es que cuando se realicen asignaciones entre variables Dado que toda estructura deriva de System.Object, ha de ser posible a travs del
de tipos valor, lo que se va a copiar en la variable destino es el objeto almacenado por la polimorfismo almacenar objetos de estos tipos en objetos object. Sin embargo, esto no
variable fuente y no la direccin de memoria dinmica a la que apuntaba sta. Por puede hacerse directamente debido a las diferencias semnticas y de almacenamiento
ejemplo, dado el siguiente tipo (ntese que las estructuras se definen igual que las clases que existen entre clases y estructuras: un object siempre ha de almacenar una referencia
pero usando la palabra reservada struct en vez de class): a un objeto en memoria dinmica y una estructura no tiene porqu estarlo. Por ello ha de
realizrsele antes al objeto de tipo valor una conversin conocida como boxing.
struct Point Recprocamente, al proceso de conversin de un object que contenga un objeto de un
{ tipo valor al tipo valor original se le denomina unboxing.
public int x, y;
El proceso de boxing es muy sencillo. Consiste en envolver el objeto de tipo valor en un
public Point(int x, int y)
{ objeto de un tipo referencia creado especficamente para ello. Por ejemplo, para un
this.x = x; objeto de un tipo valor T, el tipo referencia creado sera de la forma:
this.y = y;
} class T_Box
} {
T value;

Jos Antonio Gonzlez Seco Pgina 157 Jos Antonio Gonzlez Seco Pgina 158
El lenguaje de programacin C# Tema 13: Estructuras El lenguaje de programacin C# Tema 13: Estructuras

T_Box(T t) Sin embargo, si Punto se hubiese definido como una clase entonces s que se mostrara
{ por pantalla un 100 ya que entonces no se hara boxing en la asignacin de p a o sino
value = t;
}
que se aplicara el mecanismo de polimorfismo normal, que consiste en tratar p a travs
} de o como si fuese de tipo object pero sin realizarse ninguna conversin.

En realidad todo esto ocurre de forma transparente al programador, el cual simplemente El problema del boxing y el unboxing es que son procesos lentos, ya que implican la
asigna el objeto de tipo valor a un objeto de tipo referencia como si de cualquier creacin y destruccin de objetos envoltorio. Por ello puede interesar evitarlos en
asignacin polimrfica se tratase. Por ejemplo: aquellas situaciones donde la velocidad de ejecucin de la aplicacin sea crtica, y para
ello se proponen varias tcnicas:
int p = new Punto(10,10);
object o = p; // boxing. Es equivalente a object o = new Punto_Box(p); Si el problema se debe al paso de estructuras como parmetros de mtodos
genricos que tomen parmetros de tipo object, puede convenir definir
En realidad la clase envoltorio arriba escrita no se crea nunca, pero conceptualmente es sobrecargas de esos mtodos que en lugar de tomar objects tomen objetos de los
como si se crease. Esto se puede comprobar viendo a travs del siguiente cdigo que el tipos estructura que en concreto la aplicacin utiliza
verdadero tipo del objeto o del ejemplo anterior sigue siendo Punto (y no Punto_Box):
A partir de la versin 2.0 de C#, se pueden utilizar las denominadas plantillas o
Console.WriteLine((p is Punto)); genricos, que no son ms que definiciones de tipos de datos en las que no se
indica cul es el tipo exacto de ciertas variables sino que se deja en funcin de
La salida por pantalla de este cdigo es True, lo que confirma que se sigue parmetros a los que puede drseles distintos valores al crear cada objeto de ese
considerando que en realidad p almacena un Punto (recurdese que el operador is slo tipo. As, en vez de crearse objetos con mtodos que tomen parmetros object, se
devuelve true si el objeto que se le pasa como operando izquierdo es del tipo que se le podran ir creando diferentes versiones del tipo segn la estructura con la se vaya
indica como operando derecho) a trabajar. El Tema 21: Novedades de C# 2.0 explica esto ms detalladamente.
El proceso de unboxing es tambin transparente al programador. Por ejemplo, para Muchas veces conviene hacer unboxing para poder acceder a miembros
recuperar como Punto el valor de tipo Punto almacenado en el objeto o anterior se hara: especficos de ciertas estructuras almacenadas en objects, aunque a continuacin
vuelva a necesitarse realmacenar la estructura en un object. Para evitar esto una
p = (Punto) o; // Es equivalente a ((Punto_Box) o).value
posibilidad sera almacenar en el objecto no directamente la estructura sino un
Obviamente durante el unboxing se har una comprobacin de tipo para asegurar que el objeto de una clase envolvente creada a medida por el programador y que
objeto almacenado en o es realmente de tipo Punto. Esta comprobacin es tan estricta incluya los miembros necesarios para hacer las operaciones anteriores. As se
que se ha de cumplir que el tipo especificado sea exactamente el mismo que el tipo evitara tener que hacer unboxing, pues se convertira de object a esa clase, que
original del objeto, no vale que sea un compatible. Por tanto, este cdigo es invlido: no es un tipo valor y por tanto no implica unboxing.

int i = 123; Con la misma idea, otra posibilidad sera que el tipo estructura implementase
object o = i; ciertas interfaces mediante las que se pudiese hacer las operaciones antes
long l = (long) o // Error: o contiene un int, no un long comentadas. Aunque las interfaces no se tratarn hasta el Tema 15: Interfaces,
por ahora basta saber que las interfaces son tambin tipos referencia y por tanto
Sin embargo, lo que si sera vlido es hacer: convertir de object a un tipo interfaz no implica unboxing.
long l = (long) (int) o;

Como se puede apreciar en el constructor del tipo envoltorio creado, durante el boxing
Constructores
el envoltorio que se crea recibe una copia del valor del objeto a convertir, por lo que los
cambios que se le hagan no afectarn al objeto original. Por ello, la salida del siguiente Los constructores de las estructuras se comportan de una forma distinta a los de las
cdigo ser 10: clases. Por un lado, no pueden incluir ningn inicializador base debido a que como no
puede haber herencia el compilador siempre sabe que ha de llamar al constructor sin
Punto p = new Punto(10,10); parmetros de System.ValueType. Por otro, dentro de su cuerpo no se puede acceder a
object o = p; // boxing sus miembros hasta inicializarlos, pues para ahorrar tiempo no se les da ningn valor
p.X = 100; inicial antes de llamar al constructor.
Console.WriteLine( ((Punto) o).X); // unboxing
Sin embargo, la diferencia ms importante entre los constructores de ambos tipos se
encuentra en la implementacin del constructor sin parmetros: como los objetos

Jos Antonio Gonzlez Seco Pgina 159 Jos Antonio Gonzlez Seco Pgina 160
El lenguaje de programacin C# Tema 13: Estructuras El lenguaje de programacin C# Tema 13: Estructuras

estructura no pueden almacenar el valor por defecto null cuando se declaran sin usar Por otro lado, para conseguir que el valor por defecto de todos los objetos estructuras
constructor ya que ese valor indica referencia a posicin de memoria dinmica sea el mismo, se prohbe darles una valor inicial a sus campos en el momento de
indeterminada y los objetos estructura no almacenan referencias, toda estructura declararlos, pues si no el constructor por defecto habra de tenerlos en cuenta y su
siempre tiene definido un constructor sin parmetros que lo que hace es darle en esos ejecucin sera ms ineficiente. Por esta razn, los constructores definidos por el
casos un valor por defecto a los objetos declarados. Ese valor consiste en poner a cero programador para una estructura han de inicializar todos sus miembros no estticos en
toda la memoria ocupada por el objeto, lo que tiene el efecto de dar como valor a cada tanto que antes de llamarlos no se les da ningn valor inicial.
campo el cero de su tipo12. Por ejemplo, el siguiente cdigo imprime un 0 en pantalla:
Ntese que debido a la existencia de un constructor por defecto cuya implementacin
Punto p = new Punto(); escapa de manos del programador, el cdigo de los mtodos de una estructura puede
Console.WriteLine(p.X); tener que considerar la posibilidad de que se acceda a ellos con los valores resultantes
de una inicializacin con ese constructor. Por ejemplo, dado:
Y el siguiente tambin:
struct A
using System; {
public readonly string S;
struct Punto
{ public A(string s)
public int X,Y; {
} if (s==null)
throw (new ArgumentNullException());
class EjemploConstructorDefecto this.S = s;
{ }
Punto p; }

public static void Main()


{
Nada asegura que en este cdigo los objetos de clase A siempre se inicialicen con un
Console.WriteLine(p.X); valor distinto de null en su campo S, pues aunque el constructor definido para A
} comprueba que eso no ocurra lanzando una excepcin en caso de que se le pase una
} cadena que valga null, si el programador usa el constructor por defecto crear un objeto
en el que S valga null. Adems, ni siquiera es vlido especificar un valor inicial a S en
Sin embargo, el hecho de que este constructor por defecto se aplique no implica que se su definicin, ya que para inicializar rpidamente las estructuras sus campos no
pueda acceder a las variables locales sin antes inicializarlas con otro valor. Por ejemplo, estticos no pueden tener valores iniciales.
el siguiente fragmento de cdigo de un mtodo sera incorrecto:
Punto p;
Console.WriteLine(p.X); // X no inicializada

Sin embrago, como a las estructuras declaradas sin constructor no se les da el valor por
defecto null, s que sera vlido:

Punto p;
p.X = 2;
Console.WriteLine(p.X);

Para asegurar un valor por defecto comn a todos los objetos estructura, se prohibe a los
programadores darles su propia definicin del constructor sin parmetros. Mientras que
en las clases es opcional implementarlo y si no se hace el compilador introduce uno por
defecto, en las estructuras no es vlido hacerlo. Adems, an en el caso de que se
definan otros constructores, el constructor sin parmetros seguir siendo introducido
automticamente por el compilador a diferencia de cmo ocurra con las clases donde
en ese caso el compilador no lo introduca.

12
O sea, cero para los campos de tipos numricos, \u0000 para los de tipo char, false para los de tipo bool
y null para los de tipos referencia.

Jos Antonio Gonzlez Seco Pgina 161 Jos Antonio Gonzlez Seco Pgina 162
El lenguaje de programacin C# Tema 14: Enumeraciones El lenguaje de programacin C# Tema 14: Enumeraciones

detecte la incoherencia, un error en compilacin o una excepcin en ejecucin. Sin


TEMA 14: Enumeraciones embargo, si se hubiesen usado nmeros mgicos del mismo tipo en vez de
enumeraciones no se habra detectado nada, pues en ambos casos para el compilador y
el CLR seran simples nmeros sin ningn significado especial asociado.
Concepto de enumeracin
Definicin de enumeraciones
Una enumeracin o tipo enumerado es un tipo especial de estructura en la que los
literales de los valores que pueden tomar sus objetos se indican explcitamente al
definirla. Por ejemplo, una enumeracin de nombre Tamao cuyos objetos pudiesen Ya hemos visto un ejemplo de cmo definir una enumeracin. Sin embargo, la sintaxis
tomar los valores literales Pequeo, Mediano o Grande se definira as: completa que se puede usar para definirlas es:

enum <nombreEnumeracin> : <tipoBase>


enum Tamao
{
{
<literales>
Pequeo,
}
Mediano,
Grande
} En realidad una enumeracin es un tipo especial de estructura (luego System.ValueType
ser tipo padre de ella) que slo puede tener como miembros campos pblicos
Para entender bien la principal utilidad de las enumeraciones vamos a ver antes un constantes y estticos. Esos campos se indican en <literales>, y como sus modificadores
problema muy tpico en programacin: si queremos definir un mtodo que pueda son siempre los mismos no hay que especificarlos (de hecho, es errneo hacerlo)
imprimir por pantalla un cierto texto con diferentes tamaos, una primera posibilidad
sera dotarlo de un parmetro de algn tipo entero que indique el tamao con el que se El tipo por defecto de las constantes que forman una enumeracin es int, aunque puede
desea mostrar el texto. A estos nmeros que los mtodos interpretan con significados drseles cualquier otro tipo bsico entero (byte, sbyte, short, ushort, uint, int, long o
especficos se les suele denominar nmeros mgicos, y su utilizacin tiene los ulong) indicndolo en <tipoBase>. Cuando se haga esto hay que tener muy presente que
inconvenientes de que dificulta la legibilidad del cdigo (hay que recordar qu significa el compilador de C# slo admite que se indiquen as los alias de estos tipos bsicos,
para el mtodo cada valor del nmero) y su escritura (hay que recordar qu nmero ha pero no sus nombres reales (System.Byte, System.SByte, etc.)
pasrsele al mtodo para que funcione de una cierta forma)
Si no se especifica valor inicial para cada constante, el compilador les dar por defecto
Una alternativa mejor para el mtodo anterior consiste en definirlo de modo que tome valores que empiecen desde 0 y se incrementen en una unidad para cada constante
un parmetro de tipo Tamao para que as el programador usuario no tenga que recordar segn su orden de aparicin en la definicin de la enumeracin. As, el ejemplo del
la correspondencia entre tamaos y nmeros. Vase as como la llamada (2) del ejemplo principio del tema es equivalente a:
que sigue es mucho ms legible que la (1):
enum Tamao:int
obj.MuestraTexto(2); // (1) {
obj.MuestraTexto(Tamao.Mediano); // (2) Pequeo = 0,
Mediano = 1,
Grande = 2
Adems, estos literales no slo facilitan la escritura y lectura del cdigo sino que }
tambin pueden ser usados por herramientas de documentacin, depuradores u otras
aplicaciones para sustituir nmeros mgicos y mostrar textos muchos ms legibles. Es posible alterar los valores iniciales de cada constante indicndolos explcitamente
como en el cdigo recin mostrado. Otra posibilidad es alterar el valor base a partir del
Por otro lado, usar enumeraciones tambin facilita el mantenimiento del cdigo. Por cual se va calculando el valor de las siguientes constantes como en este otro ejemplo:
ejemplo, si el mtodo (1) anterior se hubiese definido de forma que 1 significase tamao
pequeo, 2 mediano y 3 grande, cuando se quisiese incluir un nuevo tamao intermedio enum Tamao
entre pequeo y mediano habra que darle un valor superior a 3 o inferior a 1 ya que los {
dems estaran cogidos, lo que rompera el orden de menor a mayor entre nmeros y Pequeo,
Mediano = 5,
tamaos asociados. Sin embargo, usando una enumeracin no importara mantener el
Grande
orden relativo y bastara aadirle un nuevo literal. }

Otra ventaja de usar enumeraciones frente a nmeros mgicos es que stas participan en En este ltimo ejemplo el valor asociado a Pequeo ser 0, el asociado a Mediano ser 5,
el mecanismo de comprobacin de tipos de C# y el CLR. As, si un mtodo espera un y el asociado a Grande ser 6 ya que como no se le indica explcitamente ningn otro se
objeto Tamao y se le pasa uno de otro tipo enumerado se producir, segn cuando se considera que este valor es el de la constante anterior ms 1.

Jos Antonio Gonzlez Seco Pgina 163 Jos Antonio Gonzlez Seco Pgina 164
El lenguaje de programacin C# Tema 14: Enumeraciones El lenguaje de programacin C# Tema 14: Enumeraciones

en realidad los literales de una enumeracin son constantes publicas y estticas, pues es
Obviamente, el nombre que se de a cada constante ha de ser diferente al de las dems de la sintaxis que se usa para acceder a ese tipo de miembros. El nico sitio donde no es
su misma enumeracin y el valor que se de a cada una ha de estar incluido en el rango necesario preceder el nombre del literal de <nombreEnumeracin>. es en la propia
de valores admitidos por su tipo base. Sin embargo, nada obliga a que el valor que se de definicin de la enumeracin, como tambin ocurre con cualquier constante esttica.
a cada constante tenga que ser diferente al de las dems, y de hecho puede especificarse
el valor de una constante en funcin del valor de otra como muestra este ejemplo: En realidad los literales de una enumeracin son constantes de tipos enteros y las
variables de tipo enumerado son variables del tipo entero base de la enumeracin. Por
enum Tamao eso es posible almacenar valores de enumeraciones en variables de tipos enteros y
{ valores de tipos enteros en variables de enumeraciones. Por ejemplo:
Pequeo,
Mediano = Pequeo,
Grande = Pequeo + Mediano int i = Tamao.Pequeo; // Ahora i vale 0
} Tamao t = (Tamao) 0; //Ahora t vale Tamao.Pequeo (=0)
t = (Tamao) 100; // Ahora t vale 100, que no se corresponde con ningn literal

En realidad, lo nico que importa es que el valor que se d a cada literal, si es que se le
Como se ve en el ltimo ejemplo, tambin es posible darle a una enumeracin valores
da alguno explcitamente, sea una expresin constante cuyo resultado se encuentre en el
enteros que no se correspondan con ninguno de sus literales.
rango admitido por el tipo base de la enumeracin y no provoque definiciones
circulares. Por ejemplo, la siguiente definicin de enumeracin es incorrecta ya que en
Dado que los valores de una enumeracin son enteros, es posible aplicarles muchas de
ella los literales Pequeo y Mediano se han definido circularmente:
las operaciones que se pueden aplicar a los mismos: ==, !=, <, >, <=, >=, +, -, ^, &, |, ~,
++, -- y sizeof. Sin embargo, hay que concretar que los operadores binarios + y no
enum TamaoMal
{ pueden aplicarse entre dos operandos de enumeraciones, sino que al menos uno de ellos
Pequeo = Mediano, ha de ser un tipo entero; y que |, & y ^ slo pueden aplicarse entre enumeraciones.
Mediano = Pequeo,
Grande
} La clase System.Enum
Ntese que tambin la siguiente definicin de enumeracin tambin sera incorrecta ya
Todos los tipos enumerados derivan de System.Enum, que deriva de System.ValueType
que en ella el valor de B depende del de A implcitamente (sera el de A ms 1):
y sta a su vez deriva de la clase primigenia System.Object. Aparte de los mtodos
enum EnumMal
heredados de estas clases padres ya estudiados, toda enumeracin tambin dispone de
{ otros mtodos heredados de System.Enum, los principales de los cuales son:
A = B,
B static Type getUnderlyingType(Type enum): Devuelve un objeto System.Type
} con informacin sobre el tipo base de la enumeracin representada por el objeto
13
System.Type que se le pasa como parmetro .

Uso de enumeraciones string ToString(string formato): Cuando a un objeto de un tipo enumerado se le


aplica el mtodo ToString() heredado de object, lo que se muestra es una cadena
Las variables de tipos enumerados se definen como cualquier otra variable (sintaxis con el nombre del literal almacenado en ese objeto. Por ejemplo (ntese que
<nombreTipo> <nombreVariable>) Por ejemplo: WriteLine() llama automticamente al ToString() de sus argumentos no string):

Tamao t; Tamao t = Color.Pequeo;


Console.WriteLine(t); // Muestra por pantalla la cadena "Pequeo"
El valor por defecto para un objeto de una enumeracin es 0, que puede o no
corresponderse con alguno de los literales definidos para sta. As, si la t del ejemplo Como tambin puede resultar interasante obtener el valor numrico del literal, se
fuese un campo su valor sera Tamao.Pequeo. Tambin puede drsele otro valor al ha sobrecargado System.Enum el mtodo anterior para que tome como
definirla, como muestra el siguiente ejemplo donde se le da el valor Tamao.Grande: parmetro una cadena que indica cmo se desea mostrar el literal almacenado en
el objeto. Si esta cadena es nula, vaca o vale "G" muestra el literal como si del
Tamao t = Tamao.Grande; // Ahora t vale Tamao.Grande

Ntese que a la hora de hacer referencia a los literales de una enumeracin se usa la
13
sintaxis <nombreEnumeracin>.<nombreLiteral>, como es lgico si tenemos en cuenta que Recurdese que para obtener el System.Type de un tipo de dato basta usar el operador typeof pasndole
como parmetros el nombre del tipo cuyo System.Type se desea obtener. Por ejemplo, typeof(int)

Jos Antonio Gonzlez Seco Pgina 165 Jos Antonio Gonzlez Seco Pgina 166
El lenguaje de programacin C# Tema 14: Enumeraciones El lenguaje de programacin C# Tema 14: Enumeraciones

mtodo ToString() estndar se tratase, pero si vale "D" o "X" lo que muestra es su Console.WriteLine(tabla[1]); // Muestra 1, pues Mediano = 1
valor numrico (en decimal si vale "D" y en hexadecimal si vale "X") Por ejemplo: Console.WriteLine(tabla[2]); // Muestra 1, pues Grande = Pequeo+Mediano

Console.WriteLine(t.ToString("X")); // Muestra 0 static string GetName(Type enum, object valor): Devuelve una cadena con el
Console.WriteLine(t.ToString("G")); // Muestra Pequeo nombre del literal de la enumeracin representada por enum que tenga el valor
especificado en valor. Por ejemplo, este cdigo muestra Pequeo por pantalla:
En realidad, los valores de formato son insensibles a la capitalizacin y da igual si
en vez de "G" se usa "g" o si en vez de "X" se usa "x". Console.WriteLine(Enum.GetName(typeof(Tamao), 0)); //Imprime Pequeo

static string Format(Type enum, object valorLiteral, string formato): Funciona de Si la enumeracin no contiene ningn literal con ese valor devuelve null, y si
forma parecida a la sobrecarga de ToString() recien vista, slo que ahora no es tuviese varios con ese mismo valor devolvera slo el nombre del ltimo. Si se
necesario disponer de ningn objeto del tipo enumerado cuya representacin de quiere obtener el de todos es mejor usar GetNames(), que se usa como GetName()
literal se desea obtener sino que basta indicar el objeto Type que lo representa y pero devuelve un string[] con los nombres de todos los literales que tengan el
el nmero del literal a obtener. Por ejemplo: valor indicado ordenados segn su orden de definicin en la enumeracin.

Console.Write(Enum.Format(typeof(Tamao), 0, "G"); // Muestra Pequeo static bool isDefined (Type enum, object valor): Devuelve un booleano que indica
si algn literal de la enumeracin indicada tiene el valor indicado.
Si el valorLiteral indicado no estuviese asociado a ningn literal del tipo
enumerador representado por enum, se devolvera una cadena con dicho nmero.
Por el contrario, si hubiesen varios literales en la enumeracin con el mismo Enumeraciones de flags
valor numrico asociado, lo que se devolvera sera el nombre del declarado en
ltimo lugar al definir la enumeracin.
Muchas veces interesa dar como valores de los literales de una enumeracin nicamente
valores que sean potencias de dos, pues ello permite que mediante operaciones de bits &
static object Parse(Type enum, string nombre, bool mayusculas?): Crea un
y | se puede tratar los objetos del tipo enumerado como si almacenasen simultneamente
objeto de un tipo enumerado cuyo valor es el correspondiente al literal de varios literales de su tipo. A este tipo de enumeraciones las llamaremos enumeraciones
nombre asociado nombre. Si la enumeracin no tuviese ningn literal con ese de flags, y un ejemplo de ellas es el siguiente:
nombre se lanzara una ArgumentException, y para determinar cmo se ha de
buscar el nombre entre los literales de la enumeracin se utiliza el tercer enum ModificadorArchivo
parmetro (es opcional y por defecto vale false) que indica si se ha de ignorar la {
capitalizacin al buscarlo. Un ejemplo del uso de este mtodo es: Lectura = 1,
Escritura = 2,
Oculto = 4,
Tamao t = (Tamao) Enum.Parse(typeof(Tamao), "Pequeo");
Sistema = 8
Console.WriteLine(t) // Muestra Pequeo
}

Aparte de crear objetos a partir del nombre del literal que almacenarn, Parse() Si queremos crear un objeto de este tipo que represente los modificadores de un archivo
tambin permite crearlos a partir del valor numrico del mismo. Por ejemplo: de lectura-escritura podramos hacer:
Tamao t = (Tamao) Enum.Parse(typeof(Tamao), "0"); ModificadorArchivo obj = ModificadorArchivo.Lectura | ModificadorArchivo.Escritura
Console.WriteLine(t) // Muestra Pequeo
El valor del tipo base de la enumeracin que se habr almacenado en obj es 3, que es el
En este caso, si el valor indicado no se correspondiese con el de ninguno de los resultado de hacer la operacin OR entre los bits de los valores de los literales Lectura y
literales de la enumeracin no saltara ninguna excepcin, pero el objeto creado Escritura. Al ser los literales de ModificadorArchivo potencias de dos slo tendrn un nico
no almacenara ningn literal vlido. Por ejemplo: bit a 1 y dicho bit ser diferente en cada uno de ellos, por lo que la nica forma de
generar un 3 (ltimos dos bits a 1) combinando literales de ModificadorArchivo es
Tamao t = (Tamao) Enum.Parse(typeof(Tamao), "255");
combinando los literales Lectura (ltimo bit a 1) y Escritura (penltimo bit a 1) Por tanto,
Console.WriteLine(t) // Muestra 255
el valor de obj identificar unvocamente la combinacin de dichos literales.
static object[] GetValues(Type enum): Devuelve una tabla con los valores de
todos los literales de la enumeracin representada por el objeto System.Type que Debido a esta combinabilidad no se debe determinar el valor literal de los objetos
se le pasa como parmetro. Por ejemplo: ModificadorArchivo tal y como si slo pudiesen almacenar un nico literal, pues su valor
numrico no tendra porqu corresponderse con el de ningn literal de la enumeracin
object[] tabla = Enum.GetValues(typeof(Tamao)); Por ejemplo:
Console.WriteLine(tabla[0]); // Muestra 0, pues Pequeo = 0

Jos Antonio Gonzlez Seco Pgina 167 Jos Antonio Gonzlez Seco Pgina 168
El lenguaje de programacin C# Tema 14: Enumeraciones El lenguaje de programacin C# Tema 14: Enumeraciones

bool permisoLectura = (obj == ModificadorArchivo.Lectura); // Almacena false Oculto = 4,


Sistema = 8
Aunque los permisos representados por obj incluan permiso de lectura, se devuelve }
false porque el valor numrico de obj es 3 y el del ModificadorArchivo.Lectura es 1. Si lo
Entonces la siguiente llamada producir como salida Lectura, Escritura:
que queremos es comprobar si obj contiene permiso de lectura, entonces habr que usar
el operador de bits & para aislarlo del resto de literales combinados que contiene: Console.Write(obj); // Muestra Lectura, Escritura

bool permisoLectura = (ModificadorArchivo.Lectura == Esto se debe a que en ausencia del modificador "F", Format() mira dentro de los
(obj & ModificadorArchivo.Lectura)); // Almacena true metadatos del tipo enumerado al que pertenece el valor numrico a mostrar si ste
dispone del atributo Flags. Si es as funciona como si se le hubiese pasado "F".
O, lo que es lo mismo:
bool permisoLectura = ( (obj & ModificadorArchivo.Lectura) != 0); // Almacena true Tambin cabe destacar que, para crear objetos de enumeraciones cuyo valor sea una
combinacin de valores de literales de su tipo enumerado, el mtodo mtodo Parse() de
Asimismo, si directamente se intenta mostrar por pantalla el valor de un objeto de una Enum permite que la cadena que se le especifica como segundo parmetro cuente con
enumeracin que almacene un valor que sea combinacin de literales, no se obtendr el mltiples literales separados por comas. Por ejemplo, un objeto ModificadorArchivo que
resultado esperado (nombre del literal correspondiente a su valor) Por ejemplo, dado: represente modificadores de lectura y ocultacin puede crearse con:

Console.Write(obj); // Muestra 3 ModificadorArchivo obj =


(ModificadorArchivo) Enum.Parse(typeof(ModificadorArchivo),"Lectura,Oculto"));
Se mostrar un 3 por pantalla ya que en realidad ningn literal de ModificadorArchivo
tiene asociado dicho valor. Como lo natural sera que se desease obtener un mensaje de Hay que sealar que esta capacidad de crear objetos de enumeraciones cuyo valor
la forma Lectura, Escritura, los mtodos ToString() y Format() de las enumeraciones almacenado sea una combinacin de los literales definidos en dicha enumeracin es
ya vistos admiten un cuarto valor "F" para su parmetro formato (su nombre viene de totalmente independiente de si al definirla se utiliz el atributo Flags o no.
flags) con el que se consigue lo anterior. Por tanto:
Console.Write(obj.ToString("F")); // Muestra Lectura, Escritura

Esto se debe a que cuando Format() detecta este indicador (ToString() tambin, pues para
generar la cadena llama internamente a Format()) y el literal almacenado en el objeto no
se corresponde con ninguno de los de su tipo enumerado, entonces lo que hace es mirar
uno por uno los bits a uno del valor numrico asociado de dicho literal y aadirle a la
cadena a devolver el nombre de cada literal de la enumeracin cuyo valor asociado slo
tenga ese bit a uno, usndo como separador entre nombres un carcter de coma.

Ntese que nada obliga a que los literales del tipo enumerado tengan porqu haberse
definido como potencias de dos, aunque es lo ms conveniente para que "F" sea til,
pues si la enumeracin tuviese algn literal con el valor del objeto de tipo enumerado no
se realizara el proceso anterior y se devolvera slo el nombre de ese literal.

Por otro lado, si alguno de los bits a 1 del valor numrico del objeto no tuviese el
correspondiente literal con slo ese bit a 1 en la enumeracin no se realizara tampoco el
proceso anterior y se devolvera una cadena con dicho valor numrico.

Una posibilidad ms cmoda para obtener el mismo efecto que con "F" es marcar la
definicin de la enumeracin con el atributo Flags, con lo que ni siquiera sera necesario
indicar formato al llamar a ToString() O sea, si se define ModificadorArchivo as:
[Flags]
enum ModificadorArchivo
{
Lectura = 1,
Escritura = 2,

Jos Antonio Gonzlez Seco Pgina 169 Jos Antonio Gonzlez Seco Pgina 170
El lenguaje de programacin C# Tema 15: Interfaces El lenguaje de programacin C# Tema 15: Interfaces

TEMA 15: Interfaces <modificadores> interface <nombre>:<interfacesBase>


{
<miembros>
}
Concepto de interfaz
Los <modificadores> admitidos por las interfaces son los mismos que los de las clases Es
Una interfaz es la definicin de un conjunto de mtodos para los que no se da decir, public, internal, private, protected, protected internal o new (e igualmente, los
implementacin, sino que se les define de manera similar a como se definen los cuatro ltimos slo son aplicables a interfaces definidas dentro de otros tipos)
mtodos abstractos. Es ms, una interfaz puede verse como una forma especial de
El <nombre> de una interfaz puede ser cualquier identificador vlido, aunque por
definir clases abstractas que tan slo contengan miembros abstractos.
convenio se suele usar I como primer carcter del mismo (IComparable, IA, etc)
Como las clases abstractas, las interfaces son tipos referencia, no puede crearse objetos
Los <miembros> de las interfaces pueden ser definiciones de mtodos, propiedades,
de ellas sino slo de tipos que deriven de ellas, y participan del polimorfismo. Sin
indizadores o eventos, pero no campos, operadores, constructores o destructores. La
embargo, tambin tienen numerosas diferencias con stas:
sintaxis que se sigue para definir cada tipo de miembro es la misma que para definirlos
como abstractos en una clase pero sin incluir abstract por suponerse implcitamente:
Es posible definir tipos que deriven de ms de una interfaz. Esto se debe a
que los problemas que se podran presentar al crear tipos que hereden de varios
Mtodos: <tipoRetorno> <nombreMtodo>(<parmetros>);
padres se deben a la difcil resolucin de los conflictos derivados de la herencia
de varias implementaciones diferentes de un mismo mtodo. Sin embargo, como
Propiedades: <tipo> <nombrePropiedad> {set; get;}
con las interfaces esto nunca podr ocurrir en tanto que no incluyen cdigo, se
permite la herencia mltiple de las mismas.
Los bloques get y set pueden intercambiarse y puede no incluirse uno de ellos
(propiedad de slo lectura o de slo escritura segn el caso), pero no los dos.
Las estructuras no pueden heredar de clases pero s de interfaces, y las interfaces
no pueden derivar de clases, pero s de otras interfaces.
Indizadores: <tipo> this[<ndices>] {set; get;}
Todo tipo que derive de una interfaz ha de dar una implementacin de todos los Al igual que las propiedades, los bloques set y get pueden intercambiarse y
miembros que hereda de esta, y no como ocurre con las clases abstractas donde obviarse uno de ellos al definirlos.
es posible no darla si se define como abstracta tambin la clase hija. De esta
manera queda definido un contrato en la clase que la hereda que va a permitir
Eventos: event <delegado> <nombreEvento>;
poder usarla con seguridad en situaciones polimrficas: toda clase que herede
una interfaz implementar todos los mtodos de la misma. Por esta razn se
Ntese que a diferencia de las propiedades e indizadores, no es necesario indicar
suele denominar implementar una interfaz al hecho de heredar de ella.
nada sobre sus bloques add y remove. Esto se debe a que siempre se han de
implementar ambos, aunque si se usa la sintaxis bsica el compilador les da una
Ntese que debido a esto, no suele convenir ampliar interfaces ya definidas e
implementacin por defecto automticamente.
implementadas, puesto que cualquier aadido invalidar sus implementaciones
hasta que se defina en las mismas un implementacin para dicho aadido. Sin
Cualquier definicin de un miembro de una interfaz puede incluir el modificador new
embargo, si se hereda de una clase abstracta este problema no se tendr siempre
para indicar que pretende ocultar otra heredada de alguna interfaz padre. Sin embargo,
que el miembro aadido a la clase abstracta no sea abstracto.
el resto de modificadores no son vlidos ya que implcitamente siempre se considera
que son public y abstract. Adems, una interfaz tampoco puede incluir miembros de
Las interfaces slo pueden tener como miembros mtodos normales, eventos, tipo, por lo que es incorrecto incluir el modificador static al definir sus miembros.
propiedades e indizadores; pero no pueden incluir definiciones de campos,
operadores, constructores, destructores o miembros estticos. Adems, todos los Cada interfaz puede heredar de varias interfaces, que se indicaran en <interfacesBase>
miembros de las interfaces son implcitamente pblicos y no se les puede dar separadas por comas. Esta lista slo puede incluir interfaces, pero no clases o
ningn modificador de acceso (ni siquiera public, pues se supone) estructuras; y a continuacin se muestra un ejemplo de cmo definir una interfaz IC que
hereda de otras dos interfaces IA y IB:
Definicin de interfaces public delegate void D (int x);

La sintaxis general que se sigue a la hora de definir una interfaz es: interface IA
{

Jos Antonio Gonzlez Seco Pgina 171 Jos Antonio Gonzlez Seco Pgina 172
El lenguaje de programacin C# Tema 15: Interfaces El lenguaje de programacin C# Tema 15: Interfaces

int PropiedadA{get;} set {Console.WriteLine(Asignado{0} a PropiedadA, value);}


void Comn(int x); }
}
void IA.Comn(int x)
interface IB {
{ Console.WriteLine(Ejecutado Comn() de IA);
int this [int ndice] {get; set;} }
void Comn(int x);
} public int this[int ndice]
{
interface IC: IA, IB get { return 1;}
{ set { Console.WriteLine(Asignado {0} a indizador, value); }
event D EventoC; }
}
void IB.Comn(int x)
Ntese que aunque las interfaces padres de IC contienen un mtodo comn no hay {
problema alguno a la hora de definirlas. En el siguiente epgrafe veremos cmo se Console.WriteLine(Ejecutado Comn() de IB);
}
resuelven las ambigedades que por esto pudiesen darse al implementar IC.
public event D EventoC;
}
Implementacin de interfaces
Como se ve, para implementar la interfaz IC ha sido necesario implementar todos sus
Para definir una clase o estructura que implemente una o ms interfaces basta incluir los miembros, incluso los heredados de IA y IB, de la siguiente manera:
nombres de las mismas como si de una clase base se tratase -separndolas con comas si
son varias o si la clase definida hereda de otra clase- y asegurar que la clase cuente con Al EventoC se le ha dado la implementacin por defecto, aunque si se quisiese se
definiciones para todos los miembros de las interfaces de las que hereda -lo que se podra haber dado una implementacin especfica a sus bloques add y remove.
puede conseguir definindolos en ella o heredndolos de su clase padre.
Al mtodo Comn() se le ha dado una implementacin para cada versin
Las definiciones que se den de miembros de interfaces han de ser siempre pblicas y no heredada de una de las clases padre de IC, usndose para ello la sintaxis de
pueden incluir override, pues como sus miembros son implcitamente abstract se implementacin explcita antes comentada. Ntese que no se ha incluido el
sobreentiende. Sin embargo, s pueden drsele los modificadores como virtual abstract modificador public en la implementacin de estos miembros.
y usar override en redefiniciones que se les den en clases hijas de la clase que
implemente la interfaz. A la PropiedadA se le ha dado una implementacin con un bloque set que no
apareca en la definicin de PropiedadA en la interfaz IA. Esto es vlido hacerlo
Cuando una clase deriva de ms de una interfaz que incluye un mismo miembro, la siempre y cuando la propiedad no se haya implementado explcitamente, y lo
implementacin que se le d servir para todas las interfaces que cuenten con ese mismo ocurre con los indizadores y en los casos en que en vez de set sea get el
miembro. Sin embargo, tambin es posible dar una implementacin diferente para cada bloque extra implementado.
una usando una implementacin explcita, lo que consiste en implementar el miembro
sin el modificador public y anteponiendo a su nombre el nombre de la interfaz a la que Otra utilidad de las implementaciones explcitas es que son la nica manera de
pertenece seguido de un punto (carcter .) conseguir poder dar implementacin a mtodos ocultados en las definiciones de
interfaces. Por ejemplo, si tenemos:
Cuando un miembro se implementa explcitamente, no se le pueden dar modificadores
como en las implementaciones implcitas, ni siquiera virtual o abstract. Una forma de interface IPadre
{
simular los modificadores que se necesiten consiste en darles un cuerpo que lo que haga int P{get;}
sea llamar a otra funcin que s cuente con esos modificadores. }
interface IHija:Padre
El siguiente ejemplo muestra cmo definir una clase CL que implemente la interfaz IC: {
new int P();
}
class CL:IC
{
public int PropiedadA La nica forma de poder definir una clase donde se d una implementacin tanto para el
{ mtodo P() como para la propiedad P, es usando implementacin explcita as:
get {return 5;}
class C: IHija

Jos Antonio Gonzlez Seco Pgina 173 Jos Antonio Gonzlez Seco Pgina 174
El lenguaje de programacin C# Tema 15: Interfaces El lenguaje de programacin C# Tema 15: Interfaces

{
void IPadre.P {} public static void Main()
public int P() {} {
} IA obj = new C1();
IA obj2 = new C2();
O as:
obj.F();
class C: IHija obj2.F();
{ }
public void P () {} }
int IHija.P() {}
} Reimplementar un miembro de una interfaz de esta manera es parecido a redefinir los
miembros reimplementados, slo que ahora la redefinicin sera solamente accesible a
O as: travs de variables del tipo de la interfaz. As, la salida del ejemplo anterior sera:
class C: IHija El F() de C1
{ El F() de C2
void IPadre.P() {}
int IHija.P() {}
} Hay que tener en cuenta que de esta manera slo pueden hacerse reimplementaciones de
miembros si la clase donde se reimplementa hereda directamente de la interfaz
Pero como no se puede implementar es sin ninguna implementacin explcita, pues se implementada explcitamente o de alguna interfaz derivada de sta. As, en el ejemplo
producira un error al tener ambos miembros las misma signatura. Es decir, la siguiente anterior sera incorrecto haber hecho:
definicin no es correcta:
class C2:C1 //La lista de herencias e interfaces implementadas por C2 slo incluye a C1
class C: IHija {
{ void IA.f(); // ERROR: Aunque C1 herede de IA, IA no se incluye directamente
public int P() {} // ERROR: Ambos miembros tienen la misma signatura // en la lista de interfaces implementadas por C2
public void P() {} }
}
Es importante sealar que el nombre de interfaz especificado en una implementacin
Es posible reimplementar en una clase hija las definiciones que su clase padre diese para explcita ha de ser exactamente el nombre de la interfaz donde se defini el miembro
los mtodos que hered de una interfaz. Para hacer eso basta hacer que la clase hija implementado, no el de alguna subclase de la misma. Por ejemplo:
tambin herede de esa interfaz y dar en ella las definiciones explcitas de miembros de
la interfaz que se estimen convenientes, considerndose que las implementaciones para interface I1
los dems sern las heredadas de su clase padre. Por ejemplo: {
void F()
using System; }

interface IA interface I2:I1


{ {}
void F();
} class C1:I2
{
class C1: IA public void I2.F(); //ERROR: habra que usar I1.F()
{ }
public void F()
{ En el ejemplo anterior, la lnea comentada contiene un error debido a que F() se defini
Console.WriteLine("El F() de C1"); dentro de la interfaz I1, y aunque tambin pertenezca a I2 porque sta lo hereda de I1, a
} la hora de implementarlo explcitamente hay que prefijar su nombre de I1, no de I2.
}

class C2: C1, IA


{ Acceso a miembros de una interfaz
void IA.F() // Sin implementacin explcita no redefinira, sino ocultara
{
Console.WriteLine("El F() de C2");
}

Jos Antonio Gonzlez Seco Pgina 175 Jos Antonio Gonzlez Seco Pgina 176
El lenguaje de programacin C# Tema 15: Interfaces El lenguaje de programacin C# Tema 15: Interfaces

Se puede acceder a los miembros de una interfaz implementados en una clase de manera public object Clone()
no explcita a travs de variables de esa clase como si de miembros normales de la {
Console.WriteLine(Implementacin implcita);
misma se tratase. Por ejemplo, este cdigo mostrara un cinco por pantalla: }
public object IClonable.Clone()
CL c = new CL(); {
Console.WriteLine(c.PropiedadA); Console.WriteLine(Implementacin explcita);
}
Sin embargo, tambin es posible definir variables cuyo tipo sea una interfaz. Aunque no
existen constructores de interfaces, estas variables pueden inicializarse gracias al public static void Main()
polimorfismo asignndoles objetos de clases que implementen esa interfaz. As, el {
Clase obj = new Clase();
siguiente cdigo tambin mostrara un cinco por pantalla: ((I) obj).Clone();
obj.Clone();
IA a = new CL(); }
Console.WriteLine(a.PropiedadA); }

Ntese que a travs de una variable de un tipo interfaz slo se puede acceder a El resultado que por pantalla se mostrar tras ejecutarlo es:
miembros del objeto almacenado en ella que estn definidos en esa interfaz. Es decir,
los nicos miembros vlidos para el objeto a anterior seran PropiedadA y Comn() Implementacin explcita
Implementacin implcita
En caso de que el miembro al que se pretenda acceder haya sido implementado
explcitamente, slo puede accederse a l a travs de variables del tipo interfaz al que Acceso a miembros de interfaces y boxing
pertenece y no a travs de variables de tipos que hereden de ella, ya que la definicin de
estos miembros es privada al no llevar modificador de acceso. Por ejemplo:
Es importante sealar que aunque las estructuras puedan implementar interfaces tal y
CL cl = new CL(); como lo hacen las clases, el llamarlas a travs de referencias a la interfaz supone una
IA a = cl; gran prdida de rendimiento, ya que como las interfaces son tipos referencia ello
IB b = cl; implicara la realizacin del ya visto proceso de boxing. Por ejemplo, en el cdigo:
// Console.WriteLine(cl.Comn()); // Error: Comn() fue implementado explcitamente
Console.WriteLine(a.Comn()); using System;
Console.WriteLine(b.Comn());
Console.WriteLine(((IA) cl).Comn()); interface IIncrementable
Console.WriteLine(((IB) cl).Comn()); { void Incrementar();}

Cada vez que se llame a un mtodo implementado explcitamente se llamar a la versin struct Estructura:IIncrementable
del mismo definida para la interfaz a travs de la que se accede. Por ello, la salida del {
public int Valor;
cdigo anterior ser: public void Incrementar()
{
Ejecutado Comn() de IA
Valor++;
Ejecutado Comn() de IB
}
Ejecutado Comn() de IA
Ejecutado Comn() de IB
static void Main()
{
Se puede dar tanto una implementacin implcita como una explcita de cada miembro Estructura o = new Estructura();
de una interfaz. La explcita se usar cuando se acceda a un objeto que implemente esa Console.WriteLine(o.Valor);
interfaz a travs de una referencia a la interfaz, mientras que la implcita se usar ((IIncrementable) o).Incrementar();
cuando el acceso se haga a travs de una referencia del tipo que implementa la interfaz. Console.WriteLine(o.Valor);
o.Incrementar();
Por ejemplo, dado el siguiente cdigo: Console.WriteLine(o.Valor);
}
interface I }
{
object Clone();
} La salida obtenida ser:

class Clase:I 0
0
{

Jos Antonio Gonzlez Seco Pgina 177 Jos Antonio Gonzlez Seco Pgina 178
El lenguaje de programacin C# Tema 15: Interfaces El lenguaje de programacin C# Tema 16: Instrucciones

1
TEMA 16: Instrucciones
Donde ntese que el resultado tras la primera llamada a Incrementar() sigue siendo el
mismo ya que como se le ha hecho a travs de un referencia a la interfaz, habr sido
aplicado sobre la copia de la estructura resultante del boxing y no sobre la estructura Concepto de instruccin
original. Sin embargo, la segunda llamada s que se aplica a la estructura ya que se
realiza directamente sobre el objeto original, y por tanto incrementa su campo Valor. Toda accin que se pueda realizar en el cuerpo de un mtodo, como definir variables
locales, llamar a mtodos, asignaciones y muchas cosas ms que veremos a lo largo de
este tema, son instrucciones.

Las instrucciones se agrupan formando bloques de instrucciones, que son listas de


instrucciones encerradas entre llaves que se ejecutan una tras otra. Es decir, la sintaxis
que se sigue para definir un bloque de instrucciones es:
{
<listaInstrucciones>
}

Toda variable que se defina dentro de un bloque de instrucciones slo existir dentro de
dicho bloque. Tras l ser inaccesible y podr ser destruida por el recolector de basura.
Por ejemplo, este cdigo no es vlido:
public void f();
{
{ int b; }
b = 1; // ERROR: b no existe fuera del bloque donde se declar
}

Los bloques de instrucciones pueden anidarse, aunque si dentro de un bloque interno


definimos una variable con el mismo nombre que otra definida en un bloque externo se
considerar que se ha producido un error, ya que no se podr determinar a cul de las
dos se estar haciendo referencia cada vez que se utilice su nombre en el bloque interno.

Instrucciones bsicas

Definiciones de variables locales

En el Tema 7: Variables y tipos de datos se vio que las variables locales son variables
que se definen en el cuerpo de los mtodos y slo son accesibles desde dichos cuerpos.
Recurdese que la sintaxis explicada para definirlas era la siguiente:

<modificadores> <tipoVariable> <nombreVariable> = <valor>;

Tambin ya entonces se vio que podan definirse varias variables en una misma
instruccin separando sus pares nombre-valor mediante comas, como en por ejemplo:
int a=5, b, c=-1;

Asignaciones

Jos Antonio Gonzlez Seco Pgina 179 Jos Antonio Gonzlez Seco Pgina 180
El lenguaje de programacin C# Tema 16: Instrucciones El lenguaje de programacin C# Tema 16: Instrucciones

Una asignacin es simplemente una instruccin mediante la que se indica un valor a <instruccionesIf>
almacenar en un dato. La sintaxis usada para ello es: else
<instruccionesElse>
<destino> = <origen>;
El significado de esta instruccin es el siguiente: se evala la expresin <condicin>, que
En temas previos ya se han dado numerosos ejemplos de cmo hacer esto, por lo que no ha de devolver un valor lgico. Si es cierta (devuelve true) se ejecutan las
es necesario hacer ahora mayor hincapi en ello. <instruccionesIf>, y si es falsa (false) se ejecutan las <instruccionesElse> La rama else es
opcional, y si se omite y la condicin es falsa se seguira ejecutando a partir de la
instruccin siguiente al if. En realidad, tanto <instruccionesIf> como <instruccionesElse>
Llamadas a mtodos pueden ser una nica instruccin o un bloque de instrucciones.

En el Tema 8: Mtodos ya se explic que una llamada a un mtodo consiste en Un ejemplo de aplicacin de esta instruccin es esta variante del HolaMundo:
solicitar la ejecucin de sus instrucciones asociadas dando a sus parmetros ciertos using System;
valores. Si el mtodo a llamar es un mtodo de objeto, la sintaxis usada para ello es:
class HolaMundoIf
<objeto>.<nombreMtodo>(<valoresParmetros>); {
public static void Main(String[] args)
Y si el mtodo a llamar es un mtodo de tipo, entonces la llamada se realiza con: {
if (args.Length > 0)
Console.WriteLine(Hola {0}!, args[0]);
<nombreTipo>.<nombreMtodo>(<valoresParmetros>); else
Console.WriteLine(Hola mundo!);
Recurdese que si la llamada al mtodo de tipo se hace dentro de la misma definicin de }
tipo donde el mtodo fue definido, la seccin <nombreTipo>. de la sintaxis es opcional. }

Si ejecutamos este programa sin ningn argumento veremos que el mensaje que se
Instruccin nula muestra es Hola Mundo!, mientras que si lo ejecutamos con algn argumento se
mostrar un mensaje de bienvenida personalizado con el primer argumento indicado.
La instruccin nula es una instruccin que no realiza nada en absoluto. Su sintaxis
consiste en escribir un simple punto y coma para representarla. O sea, es:
Instruccin switch
;
La instruccin switch permite ejecutar unos u otros bloques de instrucciones segn el
Suele usarse cuando se desea indicar explcitamente que no se desea ejecutar nada, lo valor de una cierta expresin. Su estructura es:
que es til para facilitar la legibilidad del cdigo o, como veremos ms adelante en el
tema, porque otras instrucciones la necesitan para indicar cundo en algunos de sus switch (<expresin>)
bloques de instrucciones componentes no se ha de realizar ninguna accin. {
case <valor1>: <bloque1>
<siguienteAccin>
case <valor2>: <bloque2>
Instrucciones condicionales <siguienteAccin>
...
default: <bloqueDefault>
Las instrucciones condicionales son instrucciones que permiten ejecutar bloques de <siguienteAccin>
instrucciones slo si se da una determinada condicin. En los siguientes subapartados de }
este epgrafe se describen cules son las instrucciones condicionales disponibles en C#
El significado de esta instruccin es el siguiente: se evala <expresin>. Si su valor es
<valor1> se ejecuta el <bloque1>, si es <valor2> se ejecuta <bloque2>, y as para el resto de
Instruccin if valores especificados. Si no es igual a ninguno de esos valores y se incluye la rama
default, se ejecuta el <bloqueDefault>; pero si no se incluye se pasa directamente a
La instruccin if permite ejecutar ciertas instrucciones slo si de da una determinada ejecutar la instruccin siguiente al switch.
condicin. Su sintaxis de uso es la sintaxis:
if (<condicin>)

Jos Antonio Gonzlez Seco Pgina 181 Jos Antonio Gonzlez Seco Pgina 182
El lenguaje de programacin C# Tema 16: Instrucciones El lenguaje de programacin C# Tema 16: Instrucciones

Los valores indicados en cada rama del switch han de ser expresiones constantes que Instrucciones iterativas
produzcan valores de algn tipo bsico entero, de una enumeracin, de tipo char o de
tipo string. Adems, no puede haber ms de una rama con el mismo valor. Las instrucciones iterativas son instrucciones que permiten ejecutar repetidas veces
una instruccin o un bloque de instrucciones mientras se cumpla una condicin. Es
En realidad, aunque todas las ramas de un switch son opcionales siempre se ha de decir, permiten definir bucles donde ciertas instrucciones se ejecuten varias veces. A
incluir al menos una. Adems, la rama default no tiene porqu aparecer la ltima si se continuacin se describen cules son las instrucciones de este tipo incluidas en C#.
usa, aunque es recomendable que lo haga para facilitar la legibilidad del cdigo.

El elemento marcado como <siguienteAccin> colocado tras cada bloque de instrucciones Instruccin while
indica qu es lo que ha de hacerse tras ejecutar las instrucciones del bloque que lo
preceden. Puede ser uno de estos tres tipos de instrucciones:
La instruccin while permite ejecutar un bloque de instrucciones mientras se de una
goto case <valori>; cierta instruccin. Su sintaxis de uso es:
goto default;
break; while (<condicin>)
<instrucciones>
Si es un goto case indica que se ha de seguir ejecutando el bloque de instrucciones
asociado en el switch a la rama del <valori> indicado, si es un goto default indica que se Su significado es el siguiente: Se evala la <condicin> indicada, que ha de producir un
ha de seguir ejecutando el bloque de instrucciones de la rama default, y si es un break valor lgico. Si es cierta (valor lgico true) se ejecutan las <instrucciones> y se repite el
indica que se ha de seguir ejecutando la instruccin siguiente al switch. proceso de evaluacin de <condicin> y ejecucin de <instrucciones> hasta que deje de
serlo. Cuando sea falsa (false) se pasar a ejecutar la instruccin siguiente al while. En
El siguiente ejemplo muestra cmo se utiliza switch: realidad <instrucciones> puede ser una nica instruccin o un bloque de instrucciones.

using System; Un ejemplo cmo utilizar esta instruccin es el siguiente:


class HolaMundoSwitch using System;
{
public static void Main(String[] args) class HolaMundoWhile
{ {
if (args.Length > 0) public static void Main(String[] args)
switch(args[0]) {
{ int actual = 0;
case Jos: Console.WriteLine(Hola Jos. Buenos das);
break; if (args.Length > 0)
case Paco: Console.WriteLine(Hola Paco. Me alegro de verte); while (actual < args.Length)
break; {
default: Console.WriteLine(Hola {0}, args[0]); Console.WriteLine(Hola {0}!, args[actual]);
break; actual = actual + 1;
} }
else else
Console.WriteLine(Hola Mundo); Console.WriteLine(Hola mundo!);
} }
} }

Este programa reconoce ciertos nombres de personas que se le pueden pasar como En este caso, si se indica ms de un argumento al llamar al programa se mostrar por
argumentos al lanzarlo y les saluda de forma especial. La rama default se incluye para pantalla un mensaje de saludo para cada uno de ellos. Para ello se usa una variable
dar un saludo por defecto a las personas no reconocidas. actual que almacena cul es el nmero de argumento a mostrar en cada ejecucin del
while. Para mantenerla siempre actualizada lo que se hace es aumentar en una unidad su
Para los programadores habituados a lenguajes como C++ es importante resaltarles el valor tras cada ejecucin de las <instrucciones> del bucle.
hecho de que, a diferencia de dichos lenguajes, C# obliga a incluir una sentencia break o
una sentencia goto case al final de cada rama del switch para evitar errores comunes y Por otro lado, dentro de las <instrucciones> de un while pueden utilizarse las siguientes
difciles de detectar causados por olvidar incluir break; al final de alguno de estos dos instrucciones especiales:
bloques y ello provocar que tras ejecutarse ese bloque se ejecute tambin el siguiente.
break;: Indica que se ha de abortar la ejecucin del bucle y continuarse
ejecutando por la instruccin siguiente al while.

Jos Antonio Gonzlez Seco Pgina 183 Jos Antonio Gonzlez Seco Pgina 184
El lenguaje de programacin C# Tema 16: Instrucciones El lenguaje de programacin C# Tema 16: Instrucciones

continue;: Indica que se ha de abortar la ejecucin de las <instrucciones> y El significado de esta instruccin es el siguiente: se ejecutan las instrucciones de
reevaluarse la <condicin> del bucle, volvindose a ejecutar las <instrucciones> si <inicializacin>, que suelen usarse para definir e inicializar variables que luego se usarn
es cierta o pasndose a ejecutar la instruccin siguiente al while si es falsa. en <instrucciones>. Luego se evala <condicin>, y si es falsa se contina ejecutando por
la instruccin siguiente al for; mientras que si es cierta se ejecutan las <instrucciones>
indicadas, luego se ejecutan las instrucciones de <modificacin> -que como su nombre
Instruccin do...while indica suelen usarse para modificar los valores de variables que se usen en
<instrucciones>- y luego se reevala <condicin> repitindose el proceso hasta que sta
La instruccin do...while es una variante del while que se usa as: ltima deje de ser cierta.

do En <inicializacin> puede en realidad incluirse cualquier nmero de instrucciones que no


<instrucciones> tienen porqu ser relativas a inicializar variables o modificarlas, aunque lo anterior sea
while(<condicin>); su uso ms habitual. En caso de ser varias se han de separar mediante comas (,), ya que
el carcter de punto y coma (;) habitualmente usado para estos menesteres se usa en el
La nica diferencia del significado de do...while respecto al de while es que en vez de for para separar los bloques de <inicializacin>, <condicin> y <modificacin> Adems, la
evaluar primero la condicin y ejecutar <instrucciones> slo si es cierta, do...while instruccin nula no se puede usar en este caso y tampoco pueden combinarse
primero ejecuta las <instrucciones> y luego mira la <condicin> para ver si se ha de repetir definiciones de variables con instrucciones de otros tipos.
la ejecucin de las mismas. Por lo dems ambas instrucciones son iguales, e incluso
tambin puede incluirse break; y continue; entre las <instrucciones> del do...while. Con <modificacin> pasa algo similar, ya que puede incluirse cdigo que nada tenga que
ver con modificaciones pero en este caso no se pueden incluir definiciones de variables.
do ... while est especialmente destinado para los casos en los que haya que ejecutar las
<instrucciones> al menos una vez an cuando la condicin sea falsa desde el principio, Como en el resto de instrucciones hasta ahora vistas, en <instrucciones> puede ser tanto
como ocurre en el siguiente ejemplo: una nica instruccin como un bloque de instrucciones. Adems, las variables que se
definan en <inicializacin> sern visibles slo dentro de esas <instrucciones>.
using System;

class HolaMundoDoWhile
La siguiente clase es equivalente a la clase HolaMundoWhile ya vista solo que hace uso
{ del for para compactar ms su cdigo:
public static void Main()
{ using System;
String ledo;
class HolaMundoFor
do {
{ public static void Main(String[] args)
Console.WriteLine(Clave: ); {
ledo = Console.ReadLine(); if (args.Length > 0)
} for (int actual = 0; actual < args.Length; actual++)
while (ledo != Jos); Console.WriteLine(Hola {0}!, args[actual]);
Console.WriteLine(Hola Jos); else
} Console.WriteLine(Hola mundo!);
} }
}
Este programa pregunta al usuario una clave y mientras no introduzca la correcta (Jos)
no continuar ejecutndose. Una vez que introducida correctamente dar un mensaje de Al igual que con while, dentro de las <instrucciones> del for tambin pueden incluirse
bienvenida al usuario. instrucciones continue; y break; que puedan alterar el funcionamiento normal del bucle.

Instruccin for Instruccin foreach

La instruccin for es una variante de while que permite reducir el cdigo necesario para La instruccin foreach es una variante del for pensada especialmente para compactar la
escribir los tipos de bucles ms comnmente usados en programacin. Su sintaxis es: escritura de cdigos donde se realice algn tratamiento a todos los elementos de una
coleccin, que suele un uso muy habitual de for en los lenguajes de programacin que lo
for (<inicializacin>; <condicin>; <modificacin>) incluyen. La sintaxis que se sigue a la hora de escribir esta instruccin foreach es:
<instrucciones>

Jos Antonio Gonzlez Seco Pgina 185 Jos Antonio Gonzlez Seco Pgina 186
El lenguaje de programacin C# Tema 16: Instrucciones El lenguaje de programacin C# Tema 16: Instrucciones

foreach (<tipoElemento> <elemento> in <coleccin>) void Reset();


<instrucciones> }

El significado de esta instruccin es muy sencillo: se ejecutan <instrucciones> para cada El mtodo Reset() ha de implementarse de modo que devuelva el enumerador reiniciado
uno de los elementos de la <coleccin> indicada. <elemento> es una variable de slo a un estado inicial donde an no referencie ni siquiera al primer elemento de la
lectura de tipo <tipoElemento> que almacenar en cada momento el elemento de la coleccin sino que sea necesario llamar a MoveNext() para que lo haga.
coleccin que se est procesando y que podr ser accedida desde <instrucciones>.
El mtodo MoveNext() se ha de implementar de modo que haga que el enumerador pase
Es importante sealar que <coleccin> no puede valer null porque entonces saltara una a apuntar al siguiente elemento de la coleccin y devuelva un booleano que indique si
excepcin de tipo System.NullReferenceException, y que <tipoElemento> ha de ser un tras avanzar se ha alcanzado el final de la coleccin.
tipo cuyos objetos puedan almacenar los valores de los elementos de <coleccin>
La propiedad Current se ha de implementar de modo que devuelva siempre el elemento
En tanto que una tabla se considera que es una coleccin, el siguiente cdigo muestra de la coleccin al que el enumerador est referenciando. Si se intenta leer Current
cmo usar for para compactar an ms el cdigo de la clase HolaMundoFor anterior: habindose ya recorrido toda la coleccin o habindose reiniciado la coleccin y no
habindose colocado en su primer elemento con MoveNext(), se ha de producir una
using System; excepcin de tipo System.Exception.SystemException.InvalidOperationException
class HolaMundoForeach
{ Otra forma de conseguir que foreach considere que un objeto es una coleccin vlida
public static void Main(String[] args) consiste en hacer que dicho objeto siga el patrn de coleccin. Este patrn consiste en
{ definir el tipo del objeto de modo que sus objetos cuenten con un mtodo pblico
if (args.Length > 0) GetEnumerator() que devuelva un objeto no nulo que cuente con una propiedad pblica
foreach(String arg in args)
Console.WriteLine(Hola {0}!, arg);
llamada Current que permita leer el elemento actual y con un mtodo pblico bool
else MoveNext() que permita cambiar el elemento actual por el siguiente y devuelva false
Console.WriteLine(Hola mundo!); slo cuando se haya llegado al final de la coleccin.
}
} El siguiente ejemplo muestra ambos tipos de implementaciones:
Las tablas multidimensionales tambin pueden recorrerse mediante el foreach, el cual using System;
pasar por sus elementos en orden tal y como muestra el siguiente fragmento de cdigo: using System.Collections;

int[,] tabla = { {1,2}, {3,4} }; class Patron


{
foreach (int elemento in tabla) private int actual = -1;
Console.WriteLine(elemento);
public Patron GetEnumerator()
Cuya salida por pantalla es: {
return this;
1 }
2
3 public int Current
4 {
get {return actual;}
En general, se considera que una coleccin es todo aquel objeto que implemente las }
interfaces IEnumerable o IEnumerator del espacio de nombres System.Collections de la
public bool MoveNext()
BCL, que estn definidas como sigue: {
bool resultado = true;
interface IEnumerable
{
actual++;
IEnumerator GetEnumerator();
if (actual==10)
}
resultado = false;
return resultado;
interface IEnumerator
}
{
object Current {get;}
}
bool MoveNext();

Jos Antonio Gonzlez Seco Pgina 187 Jos Antonio Gonzlez Seco Pgina 188
El lenguaje de programacin C# Tema 16: Instrucciones El lenguaje de programacin C# Tema 16: Instrucciones

class Interfaz:IEnumerable,IEnumerator en el penltimo foreach, entonces el cdigo directamente no compilara y se nos


{ informara de un error debido a que los objetos int no son convertibles en objetos Patrn.
private int actual = -1;

public object Current Tambin hay que tener en cuenta que la comprobacin de tipos que se realiza en tiempo
{ de ejecucin si el objeto slo implement la interfaz IEnumerable es muy estricta, en el
get {return actual;} sentido de que si en el ejemplo anterior sustituimos el <tipoElemento> del ltimo foreach
} por byte tambin se lanzar la excepcin al no ser los objetos de tipo int implcitamente
convertibles en bytes sino slo a travs del operador () Sin embargo, cuando se sigue el
public bool MoveNext() patrn de coleccin las comprobaciones de tipo no son tan estrictas y entonces s que
{ sera vlido sustituir int por byte en <tipoElemento>.
bool resultado = true;
El problema de slo implementar el patrn coleccin es que este es una caracterstica
actual++;
if (actual==10)
propia de C# y con las instrucciones foreach (o equivalentes) de lenguajes que no lo
resultado = false; soporten no se podra recorrer colecciones que slo siguiesen este patrn. Una solucin
return resultado; en estos casos puede ser hacer que el tipo del objeto coleccin implemente tanto la
} interfaz IEnumerable como el patrn coleccin. Obviamente esta interfaz debera
implementarse explcitamente para evitarse conflictos derivados de que sus miembros
public IEnumerator GetEnumerator()
{
tengan signaturas coincidentes con las de los miembros propios del patrn coleccin.
return this;
} Si un objeto de un tipo coleccin implementa tanto la interfaz IEnumerable como el
patrn de coleccin, entonces en C# foreach usar el patrn coleccin para recorrerlo.
public void Reset()
{
actual = -1;
} Instrucciones de excepciones
}
Concepto de excepcin.
class Principal
{
public static void Main() Las excepciones son el mecanismo recomendado en la plataforma .NET para propagar
{ los que se produzcan durante la ejecucin de las aplicaciones (divisiones por cero,
Patron obj = new Patron(); lectura de archivos no disponibles, etc.) Bsicamente, son objetos derivados de la clase
Interfaz obj2 = new Interfaz();
System.Exception que se generan cuando en tiempo de ejecucin se produce algn error
foreach (int elem in obj) y que contienen informacin sobre el mismo. Esto es una diferencia respecto a su
Console.WriteLine(elem); implementacin en el C++ tradicional que les proporciona una cierta homogeneidad,
consistencia y sencillez, pues en ste podan ser valores de cualquier tipo.
foreach (int elem in obj2)
Console.WriteLine(elem);
Tradicionalmente, el sistema que en otros lenguajes y plataformas se ha venido usando
}
} para informar estos errores consista simplemente en hacer que los mtodos en cuya
ejecucin pudiesen producirse devolvieran cdigos que informasen sobre si se han
Ntese que en realidad en este ejemplo no hara falta implementar IEnumerable, puesto ejecutado correctamente o, en caso contrario, sobre cul fue el error producido. Sin
que la clase Interfaz ya implementa IEnumerator y ello es suficiente para que pueda ser embargo, las excepciones proporcionan las siguientes ventajas frente a dicho sistema:
recorrida mediante foreach.
Claridad: El uso de cdigos especiales para informar de error suele dificultar la
La utilidad de implementar el patrn coleccin en lugar de la interfaz IEnumerable es legibilidad del fuente en tanto que se mezclan las instrucciones propias de la lgica
que as no es necesario que Current devuelva siempre un object, sino que puede del mismo con las instrucciones propias del tratamiento de los errores que pudiesen
devolver objetos de tipos ms concretos y gracias a ello puede detectarse al compilar si producirse durante su ejecucin. Por ejemplo:
el <tipoElemento> indicado puede o no almacenar los objetos de la coleccin.
int resultado = obj.Mtodo();
Por ejemplo, si en el ejemplo anterior sustituimos en el ltimo foreach el <tipoElemento> if (resultado == 0) // Sin errores al ejecutar obj.Mtodo();
{...}
indicado por Patrn, el cdigo seguir compilando pero al ejecutarlo saltar una else if (resultado == 1) // Tratamiento de error de cdigo 1
excepcin System.InvalidCastException. Sin embargo, si la sustitucin se hubiese hecho {...}

Jos Antonio Gonzlez Seco Pgina 189 Jos Antonio Gonzlez Seco Pgina 190
El lenguaje de programacin C# Tema 16: Instrucciones El lenguaje de programacin C# Tema 16: Instrucciones

else if (resultado == 2) // Tratamiento de error de cdigo 2 representa a la excepcin que la caus. As se pueden formar cadenas de
... excepciones de cualquier longitud. Si se desea obtener la ltima excepcin de la
Como se ver, utilizando excepciones es posible escribir el cdigo como si nunca se cadena es mejor usar el mtodo virtual Exception GetBaseException()
fuesen a producir errores y dejar en una zona aparte todo el cdigo de tratamiento
de errores, lo que contribuye a facilitar la legibilidad de los fuentes. string StackTrace {virtual get;}: Contiene la pila de llamadas a mtodos que se
tena en el momento en que se produjo la excepcin. Esta pila es una cadena con
Ms informacin: A partir del valor de un cdigo de error puede ser difcil deducir informacin sobre cul es el mtodo en que se produjo la excepcin, cul es el
las causas del mismo y conseguirlo muchas veces implica tenerse que consultar la mtodo que llam a este, cul es el que llam a ese otro, etc.
documentacin que proporcionada sobre el mtodo que lo provoc, que puede
incluso que no especifique claramente su causa. string Source {virtual get; virtual set;}: Almacena informacin sobre cul fue la
aplicacin u objeto que caus la excepcin.
Por el contrario, una excepcin es un objeto que cuenta con campos que describen
las causas del error y a cuyo tipo suele drsele un nombre que resuma claramente su
MethodBase TargetSite {virtual get;}: Almacena cul fue el mtodo donde se
causa. Por ejemplo, para informar errores de divisin por cero se suele utilizar una
produjo la excepcin en forma de objeto System.Reflection.MethodBase. Puede
excepcin predefinida de tipo DivideByZeroException en cuyo campo Message se
consultar la documentacin del SDK si desea cmo obtener informacin sobre
detallan las causas del error producido
las caractersticas del mtodo a travs del objeto MethodBase.
Tratamiento asegurado: Cuando se utilizan cdigos de error nada obliga a tratarlos
string HelpLink {virtual get;}: Contiene una cadena con informacin sobre cul es
en cada llamada al mtodo que los pueda producir, e ignorarlos puede provocar ms
la URI donde se puede encontrar informacin sobre la excepcin. El valor de
adelante en el cdigo comportamientos inesperados de causas difciles de descubrir.
esta cadena puede establecerse con virtual Exception SetHelpLink (string URI),
que devuelve la excepcin sobre la que se aplica pero con la URI ya actualizada.
Cuando se usan excepciones siempre se asegura que el programador trate toda
excepcin que pueda producirse o que, si no lo hace, se aborte la ejecucin de la
Para crear objetos de clase System.Exception se puede usar los constructores:
aplicacin mostrndose un mensaje indicando dnde se ha producido el error.
Exception()
Ahora bien, tradicionalmente en lenguajes como C++ el uso de excepciones siempre ha Exception(string msg)
tenido las desventajas respecto al uso de cdigos de error de complicar el compilador y Exception(string msg, Exception causante)
dar lugar a cdigos ms lentos y difciles de optimizar en los que tras cada instruccin
que pudiese producir excepciones el compilador debe introducir las comprobaciones El primer constructor crea una excepcin cuyo valor para Message ser y no causada
necesarias para detectarlas y tratarlas as como para comprobar que los objetos creados por ninguna otra excepcin (InnerException valdr null) El segundo la crea con el valor
sean correctamente destruidos si se producen. indicado para Message, y el ltimo la crea con adems la excepcin causante indicada.

Sin embargo, en la plataforma .NET desaparecen los problemas de complicar el En la prctica, cuando se crean nuevos tipos derivados de System.Exception no se suele
compilador y dificultar las optimizaciones ya que es el CLR quien se encarga de redefinir sus miembros ni aadirles nuevos, sino que slo se hace la derivacin para
detectar y tratar las excepciones y es su recolector de basura quien se encarga asegurar distinguir una excepcin de otra por el nombre del tipo al que pertenecen. Ahora bien,
la correcta destruccin de los objetos. Obviamente el cdigo seguir siendo algo ms es conveniente respetar el convenio de darles un nombre acabado en Exception y
lento, pero es un pequeo sacrificio que merece la pena hacer en tanto que ello asegura redefinir los tres constructores antes comentados.
que nunca se producirn problemas difciles de detectar derivados de errores ignorados.
Excepciones predefinidas comunes
La clase System.Exception
En el espacio de nombres System de la BCL hay predefinidas mltiples excepciones
Como ya se ha dicho, todas las excepciones derivan de un tipo predefinido en la BCL derivadas de System.Exception que se corresponden con los errores ms comunes que
llamado System.Exception. Los principales miembros que heredan de ste son: pueden surgir durante la ejecucin de una aplicacin. En la Tabla 8 se recogen algunas:

string Message {virtual get;}: Contiene un mensaje descriptivo de las causas de la


Tipo de la excepcin Causa de que se produzca la excepcin
ArgumentException Pasado argumento no vlido (base de excepciones de
excepcin. Por defecto este mensaje es una cadena vaca ()
argumentos)
ArgumentNullException Pasado argumento nulo
Exception InnerException {virtual get;}: Si una excepcin fue causada como
ArgumentOutOfRangeException Pasado argumento fuera de rango
consecuencia de otra, esta propiedad contiene el objeto System.Exception que

Jos Antonio Gonzlez Seco Pgina 191 Jos Antonio Gonzlez Seco Pgina 192
El lenguaje de programacin C# Tema 16: Instrucciones El lenguaje de programacin C# Tema 16: Instrucciones

ArrayTypeMistmatchException Asignacin a tabla de elemento que no es de su tipo mostrando un mensaje de error en el que se describe la excepcin producida
COMException Excepcin de objeto COM (informacin de su propiedad Message) y dnde se ha producido (informacin de su
DivideByZeroException Divisin por cero propiedad StackTrace) As, dado el siguiente cdigo fuente de ejemplo:
IndexOutOfRangeException ndice de acceso a elemento de tabla fuera del rango
using System;
vlido (menor que cero o mayor que el tamao de la
tabla) class PruebaExcepciones
InvalidCastException Conversin explcita entre tipos no vlida {
InvalidOperationException Operacin invlida en estado actual del objeto static void Main()
InteropException {
Base de excepciones producidas en comunicacin
A obj1 = new A();
con cdigo inseguro obj1.F();
NullReferenceException Acceso a miembro de objeto que vale null }
OverflowException Desbordamiento dentro de contexto donde se ha de }
comprobar los desbordamientos (expresin constante,
class A
instruccin checked, operacin checked u opcin del {
compilador /checked) public void F()
OutOfMemoryException Falta de memoria para crear un objeto con new {
SEHException Excepcin SHE del API Win32 G();
StackOverflowException }
Desbordamiento de la pila, generalmente debido a un
excesivo nmero de llamadas recurrentes. static public void G()
TypeInizializationException Ha ocurrido alguna excepcin al inicializar los {
campos estticos o el constructor esttico de un tipo. int c = 0;
En InnerException se indica cul es. int d = 2/c;
}
Tabla 8: Excepciones predefinidas de uso frecuente }

Al compilarlo no se detectar ningn error ya que al compilador no le merece la pena


Obviamente, es conveniente que si las aplicaciones que escribamos necesiten lanzar calcular el valor de c en tanto que es una variable, por lo que no detectar que dividir 2/c
excepciones relativas a errores de los tipos especificados en la Tabla 8, lancen no es vlido. Sin embargo, al ejecutarlo se intentar dividir por cero en esa instruccin y
precisamente las excepciones indicadas en esa tabla y no cualquier otra ya sea definida ello provocar que aborte la aplicacin mostrando el siguiente mensaje:
por nosotros mismos o predefinida en la BCL con otro significado.
Unhandled Exception: System.DivideByZeroException: Attempted to divide
by zero.
Lanzamiento de excepciones. Instruccin throw at PruebaExcepciones.Main()

Como se ve, en este mensaje se indica que no se ha tratado una excepcin de divisin
Para informar de un error no basta con crear un objeto del tipo de excepcin apropiado, por cero (tipo DivideByZeroException) dentro del cdigo del mtodo Main() del tipo
sino que tambin hay pasrselo al mecanismo de propagacin de excepciones del CLR. PruebaExcepciones. Si al compilar el fuente hubisemos utilizado la opcin /debug, el
A esto se le llama lanzar la excepcin, y para hacerlo se usa la siguiente instruccin: compilador habra creado un fichero .pdb con informacin extra sobre las instrucciones
throw <objetoExcepcinALanzar>; del ejecutable generado que permitira que al ejecutarlo se mostrase un mensaje mucho
ms detallado con informacin sobre la instruccin exacta que provoc la excepcin, la
Por ejemplo, para lanzar una excepcin de tipo DivideByZeroException se podra hacer: cadena de llamadas a mtodos que llevaron a su ejecucin y el nmero de lnea que cada
una ocupa en el fuente:
throw new DivideByZeroException();
Unhandled Exception: System.DivideByZeroException: Attempted to divide
Si el objeto a lanzar vale null, entonces se producir una NullReferenceException que by zero.
ser lanzada en vez de la excepcin indicada en la instruccin throw. at A.G() in E:\c#\Ej\ej.cs:line 22
at A.F() in E:\c#\Ej\ej.cs:line 16
at PruebaExcepciones.Main() in E:\c#\Ej\ej.cs:line 8

Captura de excepciones. Instruccin try Si se desea tratar la excepcin hay que encerrar la divisin dentro de una instruccin try
con la siguiente sintaxis:
Una vez lanzada una excepcin es posible escribir cdigo que es encargue de tratarla.
Por defecto, si este cdigo no se escribe la excepcin provoca que la aplicacin aborte try

Jos Antonio Gonzlez Seco Pgina 193 Jos Antonio Gonzlez Seco Pgina 194
El lenguaje de programacin C# Tema 16: Instrucciones El lenguaje de programacin C# Tema 16: Instrucciones

<instrucciones>
catch (<excepcin1>) Slo si dentro de un bloque finally se lanzase una excepcin se aborta la ejecucin del
<tratamiento1>
catch (<excepcin2>)
mismo. Dicha excepcin sera propagada al try padre o al mtodo llamante padre del try
<tratamiento2> que contuviese el finally.
...
finally Aunque los bloques catch y finally son opcionales, toda instruccin try ha de incluir al
<instruccionesFinally> menos un bloque catch o un bloque finally.
El significado de try es el siguiente: si durante la ejecucin de las <instrucciones> se
El siguiente ejemplo resume cmo funciona la propagacin de excepciones:
lanza una excepcin de tipo <excepcin1> (o alguna subclase suya) se ejecutan las
instrucciones <tratamiento1>, si fuese de tipo <excepcin2> se ejecutara <tratamiento2>, y using System;
as hasta que se encuentre una clusula catch que pueda tratar la excepcin producida.
Si no se encontrase ninguna y la instruccin try estuviese anidada dentro de otra, se class MiException:Exception {}
mirara en los catch de su try padre y se repetira el proceso. Si al final se recorren todos
los try padres y no se encuentra ningn catch compatible, entonces se buscara en el class Excepciones
{
cdigo desde el que se llam al mtodo que produjo la excepcin. Si as se termina public static void Main()
llegando al mtodo que inici el hilo donde se produjo la excepcin y tampoco all se {
encuentra un tratamiento apropiado se aborta dicho hilo; y si ese hilo es el principal (el try
que contiene el punto de entrada) se aborta el programa y se muestra el mensaje de error {
con informacin sobre la excepcin lanzada ya visto. Console.WriteLine(En el try de Main());
Mtodo();
Console.WriteLine(Al final del try de Main());
As, para tratar la excepcin del ejemplo anterior de modo que una divisin por cero }
provoque que a d se le asigne el valor 0, se podra rescribir G() de esta otra forma: catch (MiException)
{
static public void G() Console.WriteLine(En el catch de Main());
{ }
try finally
{ {
int c = 0; Console.WriteLine(finally de Main());
int d = 2/c; }
} }
catch (DivideByZeroException)
{ d=0; } public static void Mtodo()
} {
try
{
Para simplificar tanto el compilador como el cdigo generado y favorecer la legibilidad Console.WriteLine(En el try de Mtodo());
del fuente, en los catchs se busca siempre orden de aparicin textual, por lo que para Mtodo2();
evitar catchs absurdos no se permite definir catchs que puedan capturar excepciones Console.WriteLine(Al final del try de Mtodo());
capturables por catchs posteriores a ellos en su misma instruccin try. }
catch (OverflowException)
{
Tambin hay que sealar que cuando en <instrucciones> se lance una excepcin que sea Console.WriteLine(En el catch de Mtodo());
tratada por un catch de algn try -ya sea de la que contiene las <instrucciones>, de algn }
try padre suyo o de alguno de los mtodos que provocaron la llamada al que produjo la finally
excepcin- se seguir ejecutando a partir de las instrucciones siguientes a ese try. {
Console.WriteLine(finally de Mtodo());
}
El bloque finally es opcional, y si se incluye ha de hacerlo tras todas los bloques catch. }
Las <instruccionesFinally> de este bloque se ejecutarn tanto si se producen excepciones
en <instrucciones> como si no. En el segundo caso sus instrucciones se ejecutarn tras las public static void Mtodo2()
<instrucciones>, mientras que en el primero lo harn despus de tratar la excepcin pero {
antes de seguirse ejecutando por la instruccin siguiente al try que la trat. Si en un try try
{
no se encuentra un catch compatible, antes de pasar a buscar en su try padre o en su Console.WriteLine(En el try de Mtodo2());
mtodo llamante padre se ejecutarn las <instruccionesFinally>. throw new MiException();

Jos Antonio Gonzlez Seco Pgina 195 Jos Antonio Gonzlez Seco Pgina 196
El lenguaje de programacin C# Tema 16: Instrucciones El lenguaje de programacin C# Tema 16: Instrucciones

Console.WriteLine(Al final del try de Mtodo2()); incluido en C# una variante de catch que s que realmente puede capturar excepciones
} de cualquier tipo, tanto si derivan de System.Exception como si no. Su sintaxis es:
catch (DivideByZeroException)
{ Console.WriteLine(En el catch de Mtodo2()); } catch
finally {
{ Console.WriteLine(finally de Mtodo2()); } <tratamiento>
} }
}
Como puede deducirse de su sintaxis, el problema que presenta esta ltima variante de
Ntese que en este cdigo lo nico que se hace es definir un tipo nuevo de excepcin
catch es que no proporciona informacin sobre cul es la excepcin capturada, por lo
llamado MiException y llamarse en el Main() a un mtodo llamado Mtodo() que llama a
que a veces puede resultar poco til y si slo se desea capturar cualquier excepcin
otro de nombre Mtodo2() que lanza una excepcin de ese tipo. Viendo la salida de este
derivada de System.Exception es mejor usar la sintaxis previamente explicada.
cdigo es fcil ver el recorrido seguido durante la propagacin de la excepcin:

En try de Main() En cualquier caso, ambos tipos de clusulas catch slo pueden ser escritas como la
En try de Mtodo() ltima clusula catch del try, ya que si no las clusulas catch que le siguiesen nunca
En try de Mtodo2() llegaran a ejecutarse debido a que las primeras capturaran antes cualquier excepcin
finally de Mtodo2 derivada de System.Exception.
finally de Mtodo
En catch de Main()
finally de Main() Respecto al uso de throw, hay que sealar que hay una forma extra de usarlo que slo es
vlida dentro de cdigos de tratamiento de excepciones (cdigos <tratamientoi> de las
Como se puede observar, hay muchos WriteLine() que nunca se ejecutan ya que en clusulas catch) Esta forma de uso consiste en seguir simplemente esta sintaxis:
cuanto se lanza una excepcin se sigue ejecutando tras la instruccin siguiente al try que
la trat (aunque ejecutando antes los finally pendientes, como se deduce de la salida del throw;
ejemplo) De hecho, el compilador se dar cuenta que la instruccin siguiente al throw En este caso lo que se hace es relanzar la misma excepcin que se captur en el bloque
nunca se ejecutar e informar de ello con un mensaje de aviso. catch dentro de cuyo de cdigo de tratamiento se usa el throw; Hay que precisar que la
excepcin relanzada es precisamente la capturada, y aunque en el bloque catch se la
La idea tras este mecanismo de excepciones es evitar mezclar cdigo normal con cdigo modifique a travs de la variable que la representa, la versin relanzada ser la versin
de tratamiento de errores. En <instrucciones> se escribira el cdigo como si no pudiesen original de la misma y no la modificada.
producirse errores, en las clusulas catch se trataran los posibles errores, y en el finally
se incluira el cdigo a ejecutar tanto si producen errores como si no (suele usarse para Adems, cuando se relance una excepcin en un try con clusula finally, antes de pasar a
liberar recursos ocupados, como ficheros o conexiones de red abiertas) reprocesar la excepcin en el try padre del que la relanz se ejecutar dicha clusula.
En realidad, tambin es posible escribir cada clusula catch definiendo una variable que
se podr usar dentro del cdigo de tratamiento de la misma para hacer referencia a la
Instrucciones de salto
excepcin capturada. Esto se hace con la sintaxis:
catch (<tipoExcepcin> <nombreVariable>) Las instrucciones de salto permiten variar el orden normal en que se ejecutan las
{ instrucciones de un programa, que consiste en ejecutarlas una tras otra en el mismo
<tratamiento> orden en que se hubiesen escrito en el fuente. En los subapartados de este epgrafe se
}
describirn cules son las instrucciones de salto incluidas en C#:
Ntese que en tanto que todas las excepciones derivan de System.Exception, para
definir una clusula catch que pueda capturar cualquier tipo de excepcin basta usar:
Instruccin break
catch(System.Exception <nombreObjeto>)
{ Ya se ha visto que la instruccin break slo puede incluirse dentro de bloques de
<tratamiento>
} instrucciones asociados a instrucciones iterativas o instrucciones switch e indica que se
desea abortar la ejecucin de las mismas y seguir ejecutando a partir de la instruccin
En realidad la sintaxis anterior slo permite capturar las excepciones propias de la siguiente a ellas. Se usa as:
plataforma .NET, que derivan de System.Exception. Sin embargo, lenguajes como C++ break;
permiten lanzar excepciones no derivadas de dicha clase, y para esos casos se ha

Jos Antonio Gonzlez Seco Pgina 197 Jos Antonio Gonzlez Seco Pgina 198
El lenguaje de programacin C# Tema 16: Instrucciones El lenguaje de programacin C# Tema 16: Instrucciones

Cuando esta sentencia se usa dentro de un try con clusula finally, antes de abortarse la
ejecucin de la instruccin iterativa o del switch que la contiene y seguirse ejecutando
por la instruccin que le siga, se ejecutarn las instrucciones de la clusula finally del Instruccin goto
try. Esto se hace para asegurar que el bloque finally se ejecute an en caso de salto.
La instruccin goto permite pasar a ejecutar el cdigo a partir de una instruccin cuya
Adems, si dentro una clusula finally incluida en de un switch o de una instruccin etiqueta se indica en el goto. La sintaxis de uso de esta instruccin es:
iterativa se usa break, no se permite que como resultado del break se salga del finally.
goto <etiqueta>;

Instruccin continue Como en la mayora de los lenguajes, goto es una instruccin maldita cuyo uso no se
recomienda porque dificulta innecesariamente la legibilidad del cdigo y suele ser fcil
simularla usando instrucciones iterativas y selectivas con las condiciones apropiadas.
Ya se ha visto que la instruccin continue slo puede usarse dentro del bloque de
Sin embargo, en C# se incluye porque puede ser eficiente usarla si se anidan muchas
instrucciones de una instruccin iterativa e indica que se desea pasar a reevaluar
instrucciones y para reducir sus efectos negativos se le han impuesto unas restricciones:
directamente la condicin de la misma sin ejecutar el resto de instrucciones que
contuviese. La evaluacin de la condicin se hara de la forma habitual: si es cierta se
repite el bucle y si es falsa se contina ejecutando por la instruccin que le sigue. Su Slo se pueden etiquetar instrucciones, y no directivas preprocesado, directivas
using o definiciones de miembros, tipos o espacios de nombres.
sintaxis de uso es as de sencilla:
continue; La etiqueta indicada no pueda pertenecer a un bloque de instrucciones anidado
dentro del bloque desde el que se usa el goto ni que etiquete a instrucciones de otro
En cuanto a sus usos dentro de sentencias try, tiene las mismas restricciones que break: mtodo diferente a aqul en el cual se encuentra el goto que la referencia.
antes de salir de un try se ejecutar siempre su bloque finally y no es posible salir de un
finally incluido dentro de una instruccin iterativa como consecuencia de un continue. Para etiquetar una instruccin de modo que pueda ser destino de un salto con goto basta
precederla del nombre con el que se la quiera etiquetar seguido de dos puntos (:) Por
ejemplo, el siguiente cdigo demuestra cmo usar goto y definir una etiqueta:
Instruccin return
using System;
Esta instruccin se usa para indicar cul es el objeto que ha de devolver un mtodo. Su class HolaMundoGoto
sintaxis es la siguiente: {
public static void Main(string[] args)
return <objetoRetorno>; {
for (int i=0; i<args.Length; i++)
La ejecucin de esta instruccin provoca que se aborte la ejecucin del mtodo dentro {
del que aparece y que se devuelva el <objetoRetorno> al mtodo que lo llam. Como es if (args[i] != salir)
lgico, este objeto ha de ser del tipo de retorno del mtodo en que aparece el return o de Console.WriteLine(args[i]);
else
alguno compatible con l, por lo que esta instruccin slo podr incluirse en mtodos goto fin:
cuyo tipo de retorno no sea void, o en los bloques get de las propiedades o indizadores. }
De hecho, es obligatorio que todo mtodo con tipo de retorno termine por un return. fin: ;
}
Los mtodos que devuelvan void pueden tener un return con una sintaxis espacial en la }
que no se indica ningn valor a devolver sino que simplemente se usa return para
Este programa de ejemplo lo que hace es mostrar por pantalla todos los argumentos que
indicar que se desea terminar la ejecucin del mtodo:
se le pasen como parmetros, aunque si alguno de fuese salir entonces se dejara de
return; mostrar argumentos y se aborta la ejecucin de la aplicacin. Vase adems que este
ejemplo pone de manifiesto una de las utilidades de la instruccin nula, ya que si no se
Nuevamente, como con el resto de instrucciones de salto hasta ahora vistas, si se hubiese escrito tras la etiqueta fin el programa no compilara en tanto que toda etiqueta
incluyese un return dentro de un bloque try con clusula finally, antes de devolverse el ha de preceder a alguna instruccin (aunque sea la instruccin nula)
objeto especificado se ejecutaran las instrucciones de la clusula finally. Si hubiesen
varios bloques finally anidados, las instrucciones de cada uno es ejecutaran de manera Ntese que al fin y al cabo los usos de goto dentro de instrucciones switch que se vieron
ordenada (o sea, del ms interno al ms externo) Ahora bien, lo que no es posible es al estudiar dicha instruccin no son ms que variantes del uso general de goto, ya que
incluir un return dentro de una clusula finally. default: no es ms que una etiqueta y case <valor>: puede verse como una etiqueta un

Jos Antonio Gonzlez Seco Pgina 199 Jos Antonio Gonzlez Seco Pgina 200
El lenguaje de programacin C# Tema 16: Instrucciones El lenguaje de programacin C# Tema 16: Instrucciones

tanto especial cuyo nombre es case seguido de espacios en blanco y un valor. En ambos En este caso, todo desbordamiento que se produzca al realizar operaciones aritmticas
casos, la etiqueta indicada ha de pertenecer al mismo switch que el goto usado y no vale con tipos bsicos enteros en <instrucciones> ser ignorado y lo que se har ser tomar el
que ste no la contenga pero la contenga algn switch que contenga al switch del goto. valor resultante de quedarse con los bits menos significativos necesarios.

El uso de goto dentro de sentencias try, tiene las mismas restricciones que break, Por defecto, en ausencia de estas instrucciones las expresiones constantes se evalan
continue y return: antes de salir con un goto de un try se ejecutar siempre su bloque como si se incluyesen dentro de una instruccin checked y las que no constantes como
finally y no es posible forzar a saltar fuera de un finally. si se incluyesen dentro de una instruccin unchecked. Sin embargo, a travs de la
opcin /checked del compilador es posible tanto hacer que por defecto se comprueben
los desbordamientos en todos los casos para as siempre poder detectarlos y tratarlos.
Instruccin throw
Desde Visual Studio.NET, la forma de controlar el tipo de comprobaciones que por
La instruccin throw ya se ha visto que se usa para lanzar excepciones de este modo: defecto se harn es a travs de View Propety Pages Configuration Settings
Build Check for overflow underflow.
throw <objetoExcepcinALanzar>;
El siguiente cdigo muestra un ejemplo de cmo usar ambas instrucciones:
En caso de que no se indique ningn <objetoExcepcinALanzar> se relanzar el que se
estuviese tratando en ese momento, aunque esto slo es posible si el throw se ha escrito using System;
dentro del cdigo de tratamiento asociado a alguna clusula catch.
class Unchecked
{
Como esta instruccin ya ha sido explicada a fondo en este mismo tema, para ms static short x = 32767; // Valor maximo del tipo short
informacin sobre ella puede consultarse el epgrafe Excepciones del mismo.
public static void Main()
{
Otras instrucciones unchecked
{
Console.WriteLine((short) (x+1)); // (1)
Las instrucciones vistas hasta ahora son comunes a muchos lenguajes de programacin. Console.WriteLine((short) 32768); // (2)
Sin embargo, en C# tambin se ha incluido un buen nmero de nuevas instrucciones }
propias de este lenguaje. Estas instrucciones se describen en los siguientes apartados: }
}

En un principio este cdigo compilara, pero los desbordamientos producidos por el


Instrucciones checked y unchecked hecho de que 32768 no es un valor que se pueda representar con un short (16 bits con
signo) provocaran que apareciese por pantalla dicho valor truncado, mostrndose:
Las instrucciones checked y unchecked permiten controlar la forma en que tratarn los
desbordamientos que ocurran durante la realizacin de operaciones aritmticas con tipos -32768
-32678
bsico enteros. Funcionan de forma similar a los operadores checked y unchecked ya
vistos en el Tema 4: Aspectos lxicos, aunque a diferencia de stos son aplicables a Sin embargo, si sustituysemos la instruccin unchecked por checked, el cdigo
bloques enteros de instrucciones y no a una nica expresin. As, la instruccin checked anterior ni siquiera compilara ya que el compilador detectara que se va a producir un
se usa de este modo: desbordamiento en (2) debido a que 32768 es constante y no representable con un short.
checked
<instrucciones> Si eliminamos la instruccin (2) el cdigo compilara ya que (x+1) no es una expresin
constante y por tanto el compilador no podra detectar desbordamiento al compilar. Sin
Todo desbordamiento que se produzca al realizar operaciones aritmticas con enteros en embargo, cuando se ejecutase la aplicacin se lanzara una System.OverflowException.
<instrucciones> provocar que se lance una excepcin System.OverflowException. Por su
parte, la instruccin unchecked se usa as:
Instruccin lock
unchecked
<instrucciones>
La instruccin lock es til en aplicaciones concurrentes donde mltiples hilos pueden
estar accediendo simultneamente a un mismo recurso, ya que lo que hace es garantizar
que un hilo no pueda acceder a un recurso mientras otro tambin lo est haciendo. Su
sintaxis es la siguiente:

Jos Antonio Gonzlez Seco Pgina 201 Jos Antonio Gonzlez Seco Pgina 202
El lenguaje de programacin C# Tema 16: Instrucciones El lenguaje de programacin C# Tema 16: Instrucciones

{
lock (<objeto>) void Dispose()
<instrucciones> }

Su significado es el siguiente: ningn hilo puede ejecutar las <instrucciones> del bloque En la implementacin de Dispose() se escribira el cdigo de limpieza necesario, pues el
indicado si otro las est ejecutando, y si alguno lo intenta se quedar esperando hasta significado de using consiste en que al acabar la ejecucin de <instrucciones>, se llama
que acabe el primero. Esto tambin afecta a bloques de <instrucciones> de cualquier otro automticamente al mtodo Dispose() de los objetos definidos en <declaraciones>.
lock cuyo <objeto> sea el mismo. Este <objeto> ha de ser de algn tipo referencia.
Hay que tener en cuenta que la llamada a Dispose() se hace sea cual sea la razn de que
En realidad, la instruccin anterior es equivalente a hacer: se deje de ejecutar las <instrucciones> Es decir, tanto si se ha producido una excepcin
como si se ha acabado su ejecucin normalmente o con una instruccin de salto,
System.Threading.Monitor.Enter(<objeto>); Dispose() es siempre llamado. En realidad una instruccin using como:
try
<instrucciones> using (R1 r1 = new R1())
finally {
{ r1.F();
System.Threading.Monitor.Exit(<objeto>); }
}
Es tratada por el compilador como:
Sin embargo, usar lock tiene dos ventajas: es ms compacto y eficiente (<objeto> slo se
evala una vez) {
R1 r1 = new R1()
Una buena forma de garantizar la exclusin mutua durante la ejecucin de un mtodo de try
un cierto objeto es usando this como <objeto> En el caso de que se tratase de un mtodo {
r1.F();
de tipo, en tanto que this no tiene sentido dentro de estos mtodos estticos una buena }
alternativa sera usar el objeto System.Type que representase a ese tipo. Por ejemplo: finally
{
class C if (r1!=null)
{ ((IDisposable) r1).Dispose();
public static void F() }
{ }
lock(typeof(C))
{ Si se declarasen varios objetos en <declaraciones>, a Dispose() se le llamara en el orden
// ... Cdigo al que se accede exclusivamente
}
inverso a como fueron declarados. Lo mismo ocurre si se anidasen varias instrucciones
} using: primero se llamara al Dispose() de las variables declaradas en los using internos
} y luego a las de los externos. As, estas dos instrucciones son equivalentes:
using (Recurso obj = new Recurso(), obj2= new Recurso())
Instruccin using {
r1.F();
r2.F();
La instruccin using facilita el trabajo con objetos que tengan que ejecutar alguna tarea }
de limpieza o liberacin de recursos una vez que termine de ser tiles. Aunque para
estos menesteres ya estn los destructores, dado su carcter indeterminista puede que en using (Recurso obj = new Recurso())
{
determinadas ocasiones no sea conveniente confiar en ellos para realizar este tipo de using (Recurso obj2= new Recurso())
tareas. La sintaxis de uso de esta instruccin es la siguiente: {
r1.F();
using (<tipo> <declaraciones>) r2.F();
<instrucciones> }
}
En <declaraciones> se puede indicar uno o varios objetos de tipo <tipo> separados por
comas. Estos objetos sern de slo lectura y slo sern accesibles desde <instrucciones>. El siguiente ejemplo resume cmo funciona la sentencia using:
Adems, han de implementar la interfaz System.IDisposable definida como sigue:
using System;
interface IDisposable
class A:IDisposable

Jos Antonio Gonzlez Seco Pgina 203 Jos Antonio Gonzlez Seco Pgina 204
El lenguaje de programacin C# Tema 16: Instrucciones El lenguaje de programacin C# Tema 17: Atributos

{
public void Dispose()
{
TEMA 17: ATRIBUTOS
Console.WriteLine("Llamado a Dispose() de {0}", Nombre);
}

public A(string nombre)


Concepto de atributo
{
Nombre = nombre; Un atributo es informacin que se puede aadir a los metadatos de un mdulo de
} cdigo. Esta informacin puede ser referente tanto al propio mdulo o el ensamblado al
string Nombre; que pertenezca como a los tipos de datos definidos en l, sus miembros, los parmetros
} de sus mtodos, los bloques set y get de sus propiedades e indizadores o los bloques add
y remove de sus eventos.
class Using
{ En C# se incluyen numerosos modificadores que nos permiten asociar informacin a los
public static void Main()
{
metadatos de un mdulo. Por ejemplo, con los modificadores public, protected, private,
A objk = new A("objk"); internal o protected internal podemos aadir informacin sobre la visibilidad de los
tipos del mdulo y de sus miembros. Pues bien, los atributos pueden verse como un
using (A obj1 = new A("obj1"), obj2 = new A("objy")) mecanismo mediante el cual el programador puede crear sus propios modificadores.
{
Console.WriteLine("Dentro del using");
}
Un ejemplo de atributo podra ser uno llamado Ayuda que pudiese prefijar las
Console.WriteLine("Fuera del using"); definiciones de miembros de tipos e indicase cul es la URL donde se pudiese encontrar
} informacin detallada con ayuda sobre el significado del miembro prefijado.
}

La salida por pantalla resultante de ejecutar este ejemplo ser: Utilizacin de atributos
Dentro del using
Llamando a Dispose() de objy Para colocar un atributo a un elemento basta prefijar la definicin de dicho elemento
Llamando a Dispose() de obj1 con una estructura de esta forma:
Fuera del using
[<nombreAtributo>(<parmetros>)]
Como se deduce de los mensajes de salida obtenidos, justo antes de salirse del using se
llama a los mtodos Dispose() de los objetos declarados en la seccin <declaraciones> de Esta estructura ha de colocarse incluso antes que cualquier modificador que pudiese
dicha instruccin y en el mismo orden en que fueron declarados. acompaar la definicin del elemento a atribuir.

Los parmetros de un atributo pueden ser opcionales, y si se usa sin especificar valores
Instruccin fixed para sus parmetros no hay porqu que usar parntesis vacos como en las llamadas a
mtodos, sino que basta usar el atributo indicando slo la sintaxis [<nombreAtributo>]
La instruccin fixed se utiliza para fijar objetos en memoria de modo que el recolector
de basura no pueda moverlos durante la ejecucin de un cierto bloque de instrucciones. Los parmetros de un atributo pueden ser de dos tipos:

Esta instruccin slo tiene sentido dentro de regiones de cdigo inseguro, concepto que Parmetros sin nombre: Se usan de forma similar a los parmetros de los
se trata en el Tema 18: Cdigo inseguro, por lo que ser all es donde se explique a mtodos, slo que no pueden contar con modificadores ref u out.
fondo cmo utilizarla. Aqu slo diremos que su sintaxis de uso es:
Parmetros con nombre: Son opcionales y pueden colocarse en cualquier
fixed(<tipoPunteros> <declaracionesPunterosAFijar>) posicin en la lista de <parmetros> del atributo. Lo ltimo se debe a que para
<instrucciones> darles valor se usa la sintaxis <nombreParmetro>=<valor>, con lo el compilador
no depender de la posicin en que se les pasen los valores para determinar a
qu parmetro se le est dando cada valor, sino que recibe explcita su nombre.

Jos Antonio Gonzlez Seco Pgina 205 Jos Antonio Gonzlez Seco Pgina 206
El lenguaje de programacin C# Tema 17: Atributos El lenguaje de programacin C# Tema 17: Atributos

Para evitar conflictos entre parmetros con nombre y parmetros sin nombre, los method: Indica que el atributo se aplica al mtodo al que precede. En realidad no
primeros siempre se han de incluir despus de los segundos, no siendo posible es necesario usarlo porque, como se dice en la explicacin de los indicadores
mezclarlos indiscriminadamente. param y return, es lo que se considera por defecto. Sin embrago, y como pasaba
con type, se incluye por consistencia y porque puede ser buena idea incluirlo
Si se desean especificar varios atributos para un mismo elemento se pueden indicar para facilitar la legibilidad del cdigo con su explicitacin.
todos ellos entre unos mismos corchetes separados por comas. Es decir, de la forma:
event: Indica que el atributo se aplica al evento a cuya definicin precede. En
[<atributo1>(<parametros1>), <atributo2>(<parmetros2>), ...]
realidad no es necesario incluirlo porque es lo que se considera por defecto, pero
Aunque tambin sera posible especificarlos por separado. O sea, de esta otra forma: nuevamente se ha incluido por consistencia y para facilitar la lectura del cdigo.

[<atributo1>(<parametros1>)] [<atributo2>(<parmetros2>)] ... property: Indica que el atributo se aplica a la propiedad a cuya definicin
precede. ste tambin es un indicador innecesario e incluido tan slo por
Hay casos en los que por la ubicacin del atributo no se puede determinar de manera consistencia y para facilitar la legibilidad del cdigo.
unvoca a cul elemento se le desea aplicar, ya que podra ser aplicable a varios. En esos
casos, para evitar ambigedades lo que se hace es usar el atributo prefijando su nombre field: Indica que el atributo se aplica al cuya definicin precede. Como otros
de un indicador de tipo de elemento, quedando as la sintaxis a usar: indicadores, slo se incluye por consistencia y para hacer ms legible el cdigo.
[<indicadorElemento>:<nombreAtributo>(<parmetros>)]

Aunque cada implementacin de C# puede incluir sus propios indicadores de tipo de Definicin de nuevos atributos
elemento, todas ellas incluirn al menos los siguientes:
Especificacin del nombre del atributo
assembly: Indica que el atributo se aplica al ensamblado en que se compile el
cdigo fuente que lo contenga. Al definir atributos de ensamblado es obligatorio Se considera que un atributo es toda aquella clase que derive de System.Attribute. Por
incluir este indicador, ya que estos atributos se colocan precediendo cualquier tanto, para definir un nuevo tipo de atributo hay que crear una clase que derive de ella.
definicin de clase o espacio de nombres y si no se incluyesen se confundira Por convenio, a este tipo de clases suele drseles nombres acabados en Attribute, aunque
con atributos de tipo, que se colocan en el mismo sitio. a la hora de usarlas desde C# es posible obviar dicho sufijo. Un ejemplo de cmo definir
un atributo llamado Ayuda es:
module: Indica que el atributo se aplica al mdulo en que se compile el cdigo
fuente que lo contenga. Al igual que el indicador assembly, hay que incluirlo using System;
siempre para definir este tipo de atributos porque si no se confundiran con class AyudaAttribute:Attribute
atributos de tipo, ya que tambin se han de ubicar precediendo las definiciones {}
de clases y espacios de nombres.
Y ejemplos de cmo usarlo prefijando la definicin de clases son:
type: Indica que el atributo se aplica al tipo cuya definicin precede. En realidad
no hace falta utilizarlo, pues es lo que por defecto se considera para todo atributo [Ayuda] class A
{}
que preceda a una definicin de tipo. Sin embargo, se ha incluido por
consistencia con el resto de indicadores de tipo de atributo y porque puede [AyudaAttribute] class B
resultar conveniente incluirlo ya que explicitarlo facilita la lectura del cdigo. {}

return: Indica que el atributo se aplica a un valor de retorno de un mtodo, Puede darse la circunstancia de que se haya definido un atributo con un cierto nombre
operador, bloque get, o definicin de delegado. Si no se incluyese se sin sufijo Attribute y otro que si lo tenga. Como es lgico, en ese caso cuando se use el
considerara que se aplica a la definicin del mtodo, operador, bloque get o atributo sin especificar el sufijo se har referencia a la versin sin sufijo y cuando se use
delegado, ya que estos atributos se colocan antes de la misma al igual que los con sufijo se har referencia a la versin con sufijo.
atributos de valores de retorno.

param: Indica que el atributo se aplica a un parmetro de un mtodo. Si no se Especificacin del uso de un atributo
incluyese al definir bloques set, add o remove se considerara que el atributo se
refiere a los bloques en s y no al parmetro value en ellos implcito. Por defecto cualquier atributo que se defina puede preceder la definicin de cualquier
elemento del lenguaje. Si se desea limitar a qu definiciones puede preceder es

Jos Antonio Gonzlez Seco Pgina 207 Jos Antonio Gonzlez Seco Pgina 208
El lenguaje de programacin C# Tema 17: Atributos El lenguaje de programacin C# Tema 17: Atributos

necesario prefijar la clase que lo define con un atributo especial llamado Especificacin de parmetros vlidos
System.AttributeUsage. Este atributo consta de los siguientes parmetros con nombre:
Se considera que los parmetros sin nombre que puede tomar un atributo son aquellos
AllowMultiple: Por defecto cada atributo slo puede aparecer una vez prefijando que se especifiquen como parmetros en el constructor del tipo que lo define, y que sus
a cada elemento. Dndole el valor true a este parmetro se considerar que parmetros con nombre sern las propiedades y campos pblicos, no estticos y de
puede aparecer mltiples veces. lectura/escritura definidos en dicho tipo.

Inherited: Por defecto los atributos aplicados a una clase no son heredados en sus Un ejemplo de cmo definir el atributo Ayuda anterior de modo que tome un parmetro
clases hijas. Dndole el valor true a este parmetro se consigue que s lo sean. sin nombre con la URL que indique dnde encontrar la ayuda sobre el miembro o clase
al que precede y un parmetro con nombre llamado Autor que indique quin es el autor
Aparte de estos dos parmetros, AttributeUsage tambin puede contar con un parmetro de esa documentacin es:
opcional sin nombre que indique a qu tipos de definiciones puede preceder. Por defecto
se considera que un atributo puede preceder a cualquier elemento, lo que es equivalente [AttributeUsage(AttributeTargets.Class | AttributeTargets.Enum)]
a darle el valor AttributeTargets.All a este parmetro. Sin embrago es posible especificar class Ayuda:Attribute
{
otras posibilidades dndole valores de la enumeracin System.AttributeTargets, que son private string autor;
los que se recogen en la Tabla 9: private string url;

Valor de AttributeTargets Significa que el atributo puede preceder a... public Ayuda(string URL)
All Cualquier definicin { url=URL; }
Assembly Definiciones de espacio de nombres, considerndose public string Autor
que el atributo se refiere al ensamblado en general. {
Module Definiciones de espacio de nombres, considerndose set {autor = value;}
que el atributo se refiere al mdulo en su conjunto. get {return autor;}
Class }
Definiciones de clases }
Delegate Definiciones de delegados
Interface Definiciones de interfaces Ejemplos de usos vlidos de este atributo son:
Struct Definiciones de estructuras
Enum Definiciones de enumeraciones [Ayuda(http://www.josan.com/Clases/A.html)]
Field class A {}
Definiciones de campos
Method Definiciones de mtodos [Ayuda(http://www.josan.com/Clases/B.html, Autor=Jos Antonio Gonzlez Seco)]
Constructor Definiciones de constructores class B {}
Property Definiciones de propiedades o indizadores
Event Definiciones de eventos Los tipos vlidos de parmetros, tanto con nombre como sin l, que puede tomar un
Parameter Definiciones de parmetros de mtodos atributo son: cualquier tipo bsico excepto decimal y los tipos enteros sin signo,
ReturnValue Definiciones de valores de retorno de mtodos cualquier enumeracin pblica, System.Type o tablas unidimensionales de elementos de
cualquiera de los anteriores tipos vlidos.
Tabla 9: Valores de AttributeTargets

Es posible combinar varios de estos valores mediante operaciones lgicas or (carcter


| ) Por ejemplo, si queremos definir el atributo Ayuda anterior de modo que slo pueda
Lectura de atributos en tiempo de ejecucin
ser usado para prefijar definiciones de enumeraciones o de clases se hara:
Para acceder a los metadatos de cualquier ensamblado se utilizan las clases del espacio
[AttributeUsage(AttributeTargets.Class | AttributeTargetes.Enum)] de nombres System.Reflection. Este espacio de nombres es inmenso y explicar cmo
class Ayuda:Attribute utilizarlo queda fuera del alcance de este libro, aunque de todos modos a continuacin
{}
se darn unas ideas bsicas sobre cmo acceder a travs de sus tipos a los atributos
Es importante resaltar que AttributeUsage slo puede incluirse precediendo definiciones incluidos en los ensamblados.
de otros atributos (o sea, de clases derivadas de System.Attribute)
La clave para acceder a los atributos se encuentra en el mtodo esttico de la clase
System.Attribute llamado Attribute[] GetCustomAttributes(<x> objetoReflexivo), donde
<x> es el tipo de System.Reflection que representa a los elementos cuyos atributos se
desea obtener. Los posibles tipos son: Assembly, que representa ensamblados, Module

Jos Antonio Gonzlez Seco Pgina 209 Jos Antonio Gonzlez Seco Pgina 210
El lenguaje de programacin C# Tema 17: Atributos El lenguaje de programacin C# Tema 17: Atributos

que representa mdulos, MemberInfo que representa miembros (incluidos tipos, que al para obtener los objetos reflexivos que representen a los ndices de los indizadores
fin y al cabo son miembros de espacios de nombres), y ParameterInfo que representa tambin se dispone de un mtodo ParamterInfo[] GetIndexParameters()
parmetros. El parmetro tomado por este mtodo ser el objeto que represente al
elemento en concreto cuyos metadatos se quieren obtener. Y en cuanto a los eventos, los objetos EventInfo disponen de mtodos MethodInfo
GetAddMethod() y MethodInfo GetRemoveMethod() mediante los que es posible obtener
Como se ve, GetCustomAttributes() devuelve una tabla con los atributos en forma de los objetos reflexivos que representan a sus bloques add y remove.
objetos Attribute, que es la clase base de todos los atributos, por lo que si a partir de
ellos se desease acceder a caractersticas especficas de cada tipo de atributo habra que A continuacin se muestra un programa de ejemplo que lo que hace es mostrar por
aplicar downcasting como se coment en el Tema 5: Clases (para asegurase de que las pantalla el nombre de todos los atributos que en l se hayan definido:
conversiones se realicen con xito recurdese que se puede usar el operador is para
determinar cul es el verdadero tipo de cada atributo de esta tabla) using System.Reflection;
using System;
Para obtener el objeto Assembly que representa al ensamblado al que pertenezca el [assembly: EjemploEnsamblado]
cdigo que se est ejecutando se usa el mtodo Assembly GetExecutingAssembly() de la [module: EjemploModulo]
clase Assembly, que se usa tal y como se muestra:
[AttributeUsage(AttributeTargets.Method)]
Assembly ensamblado = Assembly.GetExecutingAssembly(); class EjemploMtodo:Attribute
{}
Otra posibilidad sera obtener ese objeto Assembly a partir del nombre del fichero
donde se encuentre almacenado el ensamblado. Para ello se usa el mtodo Assembly [AttributeUsage(AttributeTargets.Assembly)]
class EjemploEnsamblado:Attribute
LoadFrom(string rutaEnsamblado) de la clase Assembly como se muestra:
{}
Assembly ensamblado = Assembly.LoadFrom(josan.dll); [AttributeUsage(AttributeTargets.Module)]
class EjemploModulo:Attribute
Una vez obtenido el objeto que representa a un ensamblado, pueden obtenerse los {}
objetos Module que representan a los mdulos que lo forman a travs de su mtodo
Module[] GetModules(). [AttributeUsage(AttributeTargets.Class)]
class EjemploTipo:Attribute
{}
A partir del objeto Module que representa a un mdulo puede obtenerse los objetos Type
que representan a sus tipos a travs de su mtodo Type[] GetTypes() Otra posibilidad [AttributeUsage(AttributeTargets.Field)]
sera usar el operador typeof ya visto para obtener el Type que representa a un tipo en class EjemploCampo:Attribute
concreto sin necesidad de crear objetos Module o Assembly. {}

[EjemploTipo]
En cualquier caso, una vez obtenido un objeto Type, a travs de sus mtodos FieldInfo[] class A
GetFields(), MethodInfo[] GetMethods(), ConstructorInfo[] GetConstructors(), EventInfo[] {
GetEvents[] y PropertyInfo[] GetProperties() pueden obtenerse los objetos reflexivos que public static void Main()
representan, de manera respectiva, a sus campos, mtodos, constructores, eventos y {
propiedades o indizadores. Tanto todos estos objetos como los objetos Type derivan de Assembly ensamblado = Assembly.GetExecutingAssembly();
MemberInfo, por lo que pueden ser pasados como parmetros de GetCustomAttributes()
foreach (Attribute atributo in Attribute.GetCustomAttributes(ensamblado))
para obtener los atributos de los elementos que representan. Console.WriteLine("ENSAMBLADO: {0}",atributo);

Por otro lado, a travs de los objetos MethodInfo y ConstructorInfo, es posible obtener foreach (Module modulo in ensamblado.GetModules())
los tipos reflexivos que representan a los parmetros de mtodos y constructores {
foreach(Attribute atributo in Attribute.GetCustomAttributes(modulo))
llamando a su mtodo ParameterInfo[] GetParameters() Adems, en el caso de los Console.WriteLine("MODULO: {0}", atributo);
objetos MethodInfo tambin es posible obtener el objeto que representa al tipo de retorno
del mtodo que representan mediante su propiedad Type ReturnType {get;}. foreach (Type tipo in modulo.GetTypes())
{
En lo referente a las propiedades, es posible obtener los objetos MethodInfo que foreach(Attribute atributo in Attribute.GetCustomAttributes(tipo))
Console.WriteLine("TIPO: {0}", atributo);
representan a sus bloques get y set a travs de los mtodos MethodInfo GetSetMethod() y
MethodInfo GetSetMethod() de los objetos PropertyInfo que las representan. Adems, foreach (FieldInfo campo in tipo.GetFields())
muestra("CAMPO", campo);

Jos Antonio Gonzlez Seco Pgina 211 Jos Antonio Gonzlez Seco Pgina 212
El lenguaje de programacin C# Tema 17: Atributos El lenguaje de programacin C# Tema 17: Atributos

Atributos de compilacin
f oreach (MethodInfo metodo in tipo.GetMethods())
muestra("METODO", metodo);
Aunque la mayora de los atributos son interpretados en tiempo de ejecucin por el CLR
foreach (EventInfo evento in tipo.GetEvents()) u otras aplicaciones, hay una serie de atributos que tienen un significado especial en C#
muestra("EVENTO", evento); y condicionan el proceso de compilacin. Estos son los que se explican a continuacin.
f oreach (PropertyInfo propiedad in tipo.GetProperties())
muestra("PROPIEDAD", propiedad);
Atributo System.AttributeUsage
foreach (ConstructorInfo constructor in tipo.GetConstructors())
muestra("CONSTRUCTOR",constructor);
} Ya hemos visto en este mismo tema que se usa para indicar dnde se pueden colocar los
} nuevos atributos que el programador defina, por lo que no se har ms hincapi en l.

}
Atributo System.Obsolete
static private void muestra(string nombre, MemberInfo miembro)
{
foreach (Attribute atributo in Attribute.GetCustomAttributes(miembro)) Puede preceder a cualquier elemento de un fichero de cdigo fuente para indicar que ha
Console.WriteLine("{0}: {1}", nombre, atributo); quedado obsoleto. Admite los siguientes dos parmetros sin nombre:
}

} Un primer parmetro de tipo string que contenga una cadena con un mensaje a
mostrar cuando al compilar se detecte que se ha usado el elemento obsoleto.
Lo nico que hace el Main() de este programa es obtener el Assembly que representa el
ensamblado actual y mostrar todos sus atributos de ensamblado. Luego obtiene todos Un segundo parmetro de tipo bool que indique si se ha de producir un aviso o
los Modules que representa a los mdulos de dicho ensamblado, y muestra todos los un error cuando se detecte el uso del elemento obsoleto. Por defecto se muestra
atributos de mdulo de cada uno. Adems, de cada mdulo se obtienen todos los Types un aviso, pero si se da valor true a este parmetro, el mensaje ser de error.
que representan a los tipos en l definidos y se muestran todos sus atributos; y de cada
tipo se obtienen los objetos reflexivos que representan a sus diferentes tipos de El siguiente ejemplo muestra como utilizar este atributo:
miembros y se muestran los atributos de cada miembro.
using System;
Aparte del mtodo Main() en el ejemplo se han incluido definiciones de numerosos class Obsoleta
atributos de ejemplo aplicables a diferentes tipos de elemento y se han diseminado a lo {
largo del fuente varios usos de estos atributos. Por ello, la salida del programa es: [Obsolete(No usar f(), que est obsoleto., true)]
public static void f()
ENSAMBLADO: EjemploEnsamblado {}
ENSAMBLADO: System.Diagnostics.DebuggableAttribute
MODULO EjemploModulo public static void Main()
TIPO: System.AttributeUsageAttribute {
TIPO: System.AttributeUsageAttribute f();
TIPO: System.AttributeUsageAttribute }
TIPO: System.AttributeUsageAttribute }
TIPO: System.AttributeUsageAttribute
TIPO: EjemploTipo Cuando se compile este programa, el compilador emitir el siguiente mensaje de error:
METODO: EjemploMtodo
obsolete.cs(11,17): error CS0619: Obsoleta.f() is obsolete: No usar f(), que est obsoleto.
Ntese que aparte de los atributos utilizados en el cdigo fuente, la salida del programa
muestra que el compilador ha asociado a nivel de ensamblado un atributo extra llamado
Si no se hubiese especificado a Obsolete su segundo parmetro, entonces el mensaje
Debuggable. Este atributo incluye informacin sobre si pueden aplicarse optimizaciones
sera de aviso en vez de error:
al compilar JIT el ensamblado o si se ha de realizar una traza de su ejecucin. Sin
embargo, no conviene fiarse de su implementacin ya que no est documentado por obsolete.cs(11,17): warning CS0618: Obsoleta.f() is obsolete: No usar f(), que est obsoleto.
Microsoft y puede cambiar en futuras versiones de la plataforma .NET.

Jos Antonio Gonzlez Seco Pgina 213 Jos Antonio Gonzlez Seco Pgina 214
El lenguaje de programacin C# Tema 17: Atributos El lenguaje de programacin C# Tema 17: Atributos

Atributo System.Diagnostics.Conditional Atributo System.ClsCompliant

Este atributo slo puede prefijar definiciones de mtodos, y permite definir si las Permite especificar que el compilador ha de asegurarse de que el ensamblado, tipo de
llamadas al mtodo prefijado se han de compilar o no. Puede usarse mltiples veces dato o miembro al que se aplica es compatible con el CLS. Ello se le indica (ya sea por
prefijando a un mismo mtodo y toma un parmetro sin nombre de tipo string. Slo se nombre o posicionalmente) a travs de su nico parmetro bool IsCompliant, tal y como
compilarn aquellas llamadas al mtodo tales que en el momento de hacerlas est se muestra en el siguiente cdigo ejemplo:
definida alguna directiva de preprocesado con el mismo nombre que el parmetro de
alguno de los atributos Conditional que prefijen la definicin de ese mtodo. using System;

[assembly:CLSCompliant(true)]
Como se ve, este atributo es una buena forma de simplificar la escritura de cdigo que
se deba compilar condicionalmente, ya que evita tener varias directivas #if que encierren public class A
cada llamada al mtodo cuya ejecucin se desea controlar. Sin embargo, Conditional no {
controla la compilacin de ese mtodo, sino slo las llamadas al mismo. public void F(uint x)
{}
public static void Main()
El siguiente ejemplo muestra cmo usar Conditional: {}
}
using System;
using System.Diagnostics;
Si intenta compilarlo tal cual, obtendr el siguiente mensaje de error:
class Condicional
{ error CS3001: El tipo de argumento 'uint' no es compatible con CLS
[Conditional(DEBUG)]
public static void F() Esto se debe a que el tipo uint no forma parte del CLS, y en el cdigo se est utilizando
{ Console.WriteLine(F()); }
como parte de un mtodo pblico an cuando mediante el atributo CLSCompliant se est
public static void Main() indicando que se desea que el cdigo sea compatible con el CLS. Si se le quitase este
{ atributo, o se diese el valor false a su parmetro, o se definiesen la clase A o el mtodo
F(); F() como privados, o se cambiase el tipo del parmetro x a un tipo perteneciente al CLS
} (pe, int), entonces s que compilara el cdigo.
}

Slo si compilamos el este cdigo definiendo la constante de preprocesado DEBUG se Ntese que cuando el atributo CLSCompliant se aplica a nivel de todo un ensamblado, se
mostrar por pantalla el mensaje F() En caso contrario, nunca se har la llamada a F() comprueba la adecuacin al CLS de todos sus tipos de datos, mientras que si solamente
se aplica a un tipo de dato, slo se comprobar la adecuacin al CLS del mismo; y si tan
Hay que precisar que en realidad Conditional no puede preceder a cualquier definicin slo se aplica a un miembro, nicamente se comprobar la adecuacin al CLS de ste.
de mtodo, sino que en su colocacin hay impuestas ciertas restricciones especiales:

El mtodo ha de tener un tipo de retorno void. Esto se debe a que si tuviese otro Pseudoatributos
se podra usar su valor de retorno como operando en expresiones, y cuando no
fuesen compiladas sus llamadas esas expresiones podran no tener sentido y La BCL proporciona algunos atributos que, aunque se usan de la misma manera que el
producir errores de compilacin. resto, se almacenan de manera especial en los metadatos, como lo haran modificadores
como virtual o private. Por ello se les denomina pseudoatributos, y no se pueden
Si se aplica a un mtodo virtual todas sus redefiniciones lo heredan, siendo recuperar mediante el ya visto mtodo GetCustomAttributes(), aunque para algunos de
errneo aplicrselo explcitamente a una de ellas. Esto debe a que en tiempo de ellos se proporcionan otro mecanismos de recuperacin especficos. Un ejemplo es el
compilacin puede no saberse cul es el verdadero tipo de un objeto, y si unas atributo DllImport que ya se ha visto que se usa para definicin de mtodos externos.
redefiniciones pudiesen ser condicionales y otras no, no podra determinarse al As, dado el siguiente cdigo:
compilar si es condicional la versin del mtodo a la que en cada caso se llame.
using System.Reflection;
using System.Runtime.InteropServices;
No puede atribuirse a mtodos definidos en interfaces ni a implementaciones de using System;
mtodos de interfaces, pues son tambin virtuales y podran reimplementarse. using System.Diagnostics;

class A
{

Jos Antonio Gonzlez Seco Pgina 215 Jos Antonio Gonzlez Seco Pgina 216
El lenguaje de programacin C# Tema 18: Cdigo inseguro El lenguaje de programacin C# Tema 18: Cdigo inseguro

[DllImport("kernel32")][Conditional("DEBUG")]
public static extern void CopyFile(string fuente, string destino);
TEMA 18: Cdigo inseguro
public static void Main()
{ Concepto de cdigo inseguro
MethodInfo mtodo = typeof(A).GetMethod("CopyFile");
foreach (Attribute atributo in mtodo.GetCustomAttributes(false))
Console.WriteLine(atributo); Cdigo inseguro es todo aqul fragmento de cdigo en C# dentro del cual es posible
} hacer uso de punteros.
}
Un puntero en C# es una variable que es capaz de almacenar direcciones de memoria.
La salida que se obtendra al ejecutarlo es la siguiente: Generalmente suele usarse para almacenar direcciones que almacenen objetos, por lo
que en esos casos su significado es similar al de variables normales de tipos referencia.
System.Diagnostics.ConditionalAttribute
Sin embargo, los punteros no cuentan con muchas de las restricciones de stas a la hora
Donde como se puede ver, no se ha recuperado el pseudoatributo DllImport mientras que de acceder al objeto. Por ejemplo, al accederse a los elementos de una tabla mediante un
el otro (Conditional), que es un atributo normal, s que lo ha hecho. puntero no se pierde tiempo en comprobar que el ndice especificado se encuentre
dentro de los lmites de la tabla, lo que permite que el acceso se haga ms rpidamente.

Aparte de su mayor eficiencia, tambin hay ciertos casos en que es necesario disponer
del cdigo inseguro, como cuando se desea hacer llamadas a funciones escritas en
lenguajes no gestionados cuyos parmetros tengan que ser punteros.

Es importante sealar que los punteros son una excepcin en el sistema de tipos de
.NET, ya que no derivan de la clase primigenia System.Object, por lo que no dispondrn
de los mtodos comunes a todos los objetos y una variable object no podr almacenarlos
(tampoco existen procesos similares al boxing y unboxing que permitan simularlo)

Compilacin de cdigos inseguros

El uso de punteros hace el cdigo ms proclive a fallos en tanto que se salta muchas de
las medidas incluidas en el acceso normal a objetos, por lo que es necesario incluir
ciertas medidas de seguridad que eviten la introduccin accidental de esta inseguridad

La primera medida tomada consiste en que explcitamente hay que indicar al


compilador que deseamos compilar cdigo inseguro. Para ello, al compilador de lnea
de comandos hemos de pasarle la opcin /unsafe, como se muestra el ejemplo:
csc cdigoInseguro.cs /unsafe

Si no se indica la opcin unsafe, cuando el compilador detecte algn fuente con cdigo
inseguro producir un mensaje de error como el siguiente:

cdigoInseguro(5,23): error CS0277: unsafe code may only appear if compiling with /unsafe

En caso de que la compilacin se vaya a realizar a travs de Visual Studio.NET, la


forma de indicar que se desea compilar cdigo inseguro es activando la casilla View
Property Pages Configuration Properties Build Allow unsafe code blocks

Jos Antonio Gonzlez Seco Pgina 217 Jos Antonio Gonzlez Seco Pgina 218
El lenguaje de programacin C# Tema 18: Cdigo inseguro El lenguaje de programacin C# Tema 18: Cdigo inseguro

Marcado de cdigos inseguros Definicin de punteros

Aparte de forzarse a indicar explcitamente que se desea compilar cdigo inseguro, C# Para definir una variable puntero de un determinado tipo se sigue una sintaxis parecida a
tambin obliga a que todo uso de cdigo inseguro que se haga en un fichero fuente la usada para definir variables normales slo que al nombre del tipo se le postpone un
tenga que ser explcitamente indicado como tal. A las zonas de cdigo donde se usa smbolo de asterisco (*) O sea, un puntero se define as:
cdigo inseguro se les denomina contextos inseguros, y C# ofrece varios mecanismos
para marcar este tipo de contextos. <tipo> * <nombrePuntero>;

Por ejemplo, una variable puntero llamada a que pueda almacenar referencias a
Una primera posibilidad consiste en preceder un bloque de instrucciones de la palabra
posiciones de memoria donde se almacenen objetos de tipo int se declara as:
reservada unsafe siguiendo la siguiente sintaxis:
int * a;
unsafe <instrucciones>
En caso de quererse declarar una tabla de punteros, entonces el asterisco hay que
En el cdigo incluido en <instrucciones> podrn definirse variables de tipos puntero y
incluirlo tras el nombre del tipo pero antes de los corchetes. Por ejemplo, una tabla de
podr hacerse uso de las mismas. Por ejemplo:
nombre t que pueda almacenar punteros a objetos de tipo int se declara as:
public void f()
{ int*[] t;
unsafe
{ Hay un tipo especial de puntero que es capaz de almacenar referencias a objetos de
int *x; cualquier tipo. stos punteros se declaran indicando void como <tipo>. Por ejemplo:
}
} void * punteroACualquierCosa;

Otra forma de definir contextos inseguros consiste en aadir el modificador unsafe a la Hay que tener en cuenta que en realidad lo que indica el tipo que se d a un puntero es
definicin de un miembro, caso en que dentro de su definicin se podr hacer uso de cul es el tipo de objetos que se ha de considerar que se almacenan en la direccin de
punteros. As es posible definir campos de tipo puntero, mtodos con parmetros de memoria almacenada por el puntero. Si se le da el valor void lo que se est diciendo es
tipos puntero, etc. El siguiente ejemplo muestra cmo definir dos campos de tipo que no se desea que se considere que el puntero apunta a ningn tipo especfico de
puntero. Ntese sin embargo que no es posible definir los dos en una misma lnea: objeto. Es decir, no se est dando informacin sobre el tipo apuntado.
struct PuntoInseguro Se pueden declarar mltiples variables locales de tipo puntero en una misma lnea. En
{
public unsafe int *X; // No es vlido hacer public unsafe int *X, Y;
ese caso el asterisco slo hay que incluirlo antes del nombre de la primera. Por ejemplo:
public unsafe int *Y; // Tampoco lo es hacer public unsafe int *X, *Y;
} int * a, b; // a y b son de tipo int * No sera vlido haberlas definido como int *a, *b;

Obviamente, en un mtodo que incluya el modificador unsafe no es necesario preceder Hay que tener en cuenta que esta sintaxis especial para definir en una misma definicin
con dicha palabra sus bloques de instrucciones inseguros. varios punteros de un mismo tipo slo es vlida en definiciones de variables locales. Al
definir campos no sirve y hay que dar para cada campo una definicin independiente.
Hay que tener en cuenta que el aadido de modificadores unsafe es completamente
inocuo. Es decir, no influye para nada en cmo se haya de redefinir y si un mtodo El recolector de basura no tiene en cuenta los datos a los que se referencie con punteros,
Main() lo tiene sigue siendo un punto de entrada vlido. pues ha de conocer cul es el objeto al referenciado por cada variable y un puntero en
realidad no tiene porqu almacenar referencias a objetos de ningn tipo en concreto. Por
Una tercera forma consiste en aadir el modificador unsafe en la definicin de un tipo, ejemplo, pueden tenerse punteros int * que en realidad apunten a objeto char, o punteros
caso en que todas las definiciones de miembros del mismo podrn incluir cdigo void * que no almacenen informacin sobre el tipo de objeto al que debera considerarse
inseguro sin necesidad de aadir a cada una el modificador unsafe o preceder sus que apuntan, o punteros que apunte a direcciones donde no hayan objetos, etc.
bloques de instrucciones inseguras de la palabra reservada unsafe. Por ejemplo:
Como el recolector de basura no trabaja con punteros, no es posible definir punteros de
unsafe struct PuntoInseguro tipos que se almacenen en memoria dinmica o contengan miembros que se almacenen
{ en memoria dinmica, ya que entonces podra ocurrir que un objeto slo referenciado a
public int * X, *Y;
}
travs de punteros sea destruido por considerar el recolector que nadie le referenciaba.
Por ello, slo es vlido definir punteros de tipos cuyos objetos se puedan almacenar

Jos Antonio Gonzlez Seco Pgina 219 Jos Antonio Gonzlez Seco Pgina 220
El lenguaje de programacin C# Tema 18: Cdigo inseguro El lenguaje de programacin C# Tema 18: Cdigo inseguro

completamente en pila, pues la vida de estos objetos no est controlada por el recolector {
de basura sino que se destruyen cuando se abandona el mbito donde fueron definidos. int x;
unsafe
{ int px = &x;}
En concreto, los nicos punteros vlidos son aquellos que apunten a tipos valor bsicos, }
enumeraciones o estructuras que no contengan campos de tipos referencias. Tambin
pueden definirse punteros a tipos puntero, como se muestra en el siguiente ejemplo de Esto se debe a que uno de los principales usos de los punteros en C# es poderlos pasar
declaracin de un puntero a punteros de tipo int llamando punteroApunteros: como parmetros de funciones no gestionadas que esperen recibir punteros. Como
muchas de esas funciones han sido programadas para inicializar los contenidos de los
int ** punteroApunteros;
punteros que se les pasan, pasarles punteros inicializados implicara perder tiempo
Obviamente la anidacin puede hacerse a cualquier nivel de profundidad, pudindose innecesariamente en inicializarlos.
definir punteros a punteros a punteros, o punteros a punteros a punteros a punteros, etc.
Acceso a contenido de puntero. Operador *
Manipulacin de punteros
Un puntero no almacena directamente un objeto sino que suele almacenar la direccin
Obtencin de direccin de memoria. Operador & de memoria de un objeto (o sea, apunta a un objeto) Para obtener a partir de un puntero
el objeto al que apunta hay que aplicarle al mismo el operador prefijo *, que devuelve el
objeto apuntado. Por ejemplo, el siguiente cdigo imprime en pantalla un 10:
Para almacenar una referencia a un objeto en un puntero se puede aplicar al objeto el
operador prefijo &, que lo que hace es devolver la direccin que en memoria ocupa el int x = 10;
objeto sobre el que se aplica. Un ejemplo de su uso para inicializar un puntero es: int * px= &x;
Console.WriteLine(*px);
int x =10;
int * px = &x; Es posible en un puntero almacenar null para indicar que no apunta a ninguna direccin
vlida. Sin embargo, si luego se intenta acceder al contenido del mismo a travs del
Este operador no es aplicable a expresiones constantes, pues stas no se almacenan en operador * se producir generalmente una excepcin de tipo NullReferenceException
ninguna direccin de memoria especfica sino que se incrustan en las instrucciones. Por (aunque realmente esto depende de la implementacin del lenguaje) Por ejemplo:
ello, no es vlido hacer directamente:
int * px = null;
int px = &10; // Error 10 no es una variable con direccin propia Console.WriteLine(*px); // Produce una NullReferenceException

Tampoco es vlido aplicar & a campos readonly, pues si estos pudiesen ser apuntados No tiene sentido aplicar * a un puntero de tipo void * ya que estos punteros no
por punteros se correra el riesgo de poderlos modificar ya que a travs de un puntero se almacenan informacin sobre el tipo de objetos a los que apuntan y por tanto no es
accede a memoria directamente, sin tenerse en cuenta si en la posicin accedida hay posible recuperarlos a travs de los mismos ya que no se sabe cuanto espacio en
algn objeto, por lo que mucho menos se considerar si ste es de slo lectura. memoria a partir de la direccin almacenada en el puntero ocupa el objeto apuntado y,
por tanto, no se sabe cuanta memoria hay que leer para obtenerlo.
Lo que es s vlido es almacenar en un puntero es la direccin de memoria apuntada por
otro puntero. En ese caso ambos punteros apuntaran al mismo objeto y las
modificaciones a ste realizadas a travs de un puntero tambin afectaran al objeto Acceso a miembro de contenido de puntero. Operador ->
visto por el otro, de forma similar a como ocurre con las variables normales de tipos
referencia. Es ms, los operadores relacionales tpicos (==, !=, <, >, <= y >=) se han Si un puntero apunta a un objeto estructura que tiene un mtodo F() sera posible
redefinido para que cuando se apliquen entre dos punteros de cualesquiera dos tipos lo llamarlo a travs del puntero con:
que se compare sean las direcciones de memoria que estos almacenan. Por ejemplo:
(*objeto).F();
int x = 10;
int px = &x; Sin embargo, como llamar a objetos apuntados por punteros es algo bastante habitual,
int px2 = px; // px y px2 apuntan al objeto almacenado en x
para facilitar la sintaxis con la que hacer esto se ha incluido en C# el operador ->, con el
Console.WriteLine( px == px2); // Imprime por pantalla True
que la instruccin anterior se escribira as:
En realidad las variables sobre las que se aplique & no tienen porqu estar inicializadas.
objeto->f();
Por ejemplo, es vlido hacer:

private void f()

Jos Antonio Gonzlez Seco Pgina 221 Jos Antonio Gonzlez Seco Pgina 222
El lenguaje de programacin C# Tema 18: Cdigo inseguro El lenguaje de programacin C# Tema 18: Cdigo inseguro

Es decir, del mismo modo que el operador . permite acceder a los miembros de un
objeto referenciado por una variable normal, -> permite acceder a los miembros de un Ntese que aunque en un principio es posible hacer que un puntero almacene cualquier
objeto referenciado por un puntero. En general, un acceso de la forma O -> M es direccin de memoria, si dicha direccin no pertenece al mismo proceso que el cdigo
equivalente a hacer (*O).M. Por tanto, al igual que es incorrecto aplicar * sobre punteros en que se use el puntero se producir un error al leer el contenido de dicha direccin. El
de tipo void *, tambin lo es aplicar -> tipo de error ha producir no se indica en principio en la especificacin del lenguaje, pero
la implementacin de Microsoft lanza una referencia NullReferenceException. Por
ejemplo, el siguiente cdigo produce una excepcin de dicho tipo al ejecurtase:
Conversiones de punteros
using System;

De todo lo visto hasta ahora parece que no tiene mucho sentido el uso de punteros de class AccesoInvlido
tipo void * Pues bien, una utilidad de este tipo de punteros es que pueden usarse como {
almacn de punteros de cualquier otro tipo que luego podrn ser recuperados a su tipo public unsafe static void Main()
original usando el operador de conversin explcita. Es decir, igual que los objetos de {
int * px = (int *) 100;
tipo object pueden almacenar implcitamente objetos de cualquier tipo, los punteros void Console.Write(*px); // Se lanza NullReferenceException
* pueden almacenar punteros de cualquier tipo y son tiles para la escritura de mtodos }
que puedan aceptar parmetros de cualquier tipo de puntero. }

A diferencia de lo que ocurre entre variables normales, las conversiones entre punteros
siempre se permiten, al realizarlas nunca se comprueba si son vlidas. Por ejemplo: Aritmtica de punteros

char c = 'A'; Los punteros se suelen usar para recorrer tablas de elementos sin necesidad de tener que
char* pc = &c; comprobarse que el ndice al que se accede en cada momento se encuentra dentro de los
void* pv = pc;
int* pi = (int*)pv;
lmites de la tabla. Por ello, los operadores aritmticos definidos para los punteros estn
int i = *pi; // Almacena en 16 bits del char de pi + otros 16 indeterminados orientados a facilitar este tipo de recorridos.
Console.WriteLine(i);
*pi = 123456; // Machaca los 32 bits apuntados por pi Hay que tener en cuenta que todos los operadores aritmticos aplicables a punteros
dependen del tamao del tipo de dato apuntado, por lo que no son aplicables a punteros
En este cdigo pi es un puntero a un objeto de tipo int (32 bits), pero en realidad el void * ya que estos no almacenan informacin sobre dicho tipo. Esos operadores son:
objeto al que apunta es de tipo char (16 bits), que es ms pequeo. El valor que se
almacene en i es en principio indefinido, pues depende de lo que hubiese en los 16 bits ++ y --: El operador ++ no suma uno a la direccin almacenada en un puntero,
extras resultantes de tratar pv como puntero a int cuando en realidad apuntaba a un char. sino que le suma el tamao del tipo de dato al que apunta. As, si el puntero
apuntaba a un elemento de una tabla pasar a apuntar al siguiente (los elementos
Del mismo modo, conversiones entre punteros pueden terminar produciendo que un de las tablas se almacenan en memoria consecutivamente) Del mismo modo, --
puntero apunte a un objeto de mayor tamao que los objetos del tipo del puntero. En resta a la direccin almacenada en el puntero el tamao de su tipo de dato. Por
estos casos, el puntero apuntara a los bits menos significativos del objeto apuntado. ejemplo, una tabla de 100 elementos a cuyo primer elemento inicialmente
apuntase pt podra recorrerse as:
Tambin es posible realizar conversiones entre punteros y tipos bsicos enteros. La
conversin de un puntero en un tipo entero devuelve la direccin de memoria apuntada for (int i=0; i<100; i++)
por el mismo. Por ejemplo, el siguiente cdigo muestra por pantalla la direccin de Console.WriteLine(Elemento{0}={1}, i, (*p)++);
memoria apuntada por px:
El problema que puede plantear en ciertos casos el uso de ++ y -- es que hacen
int x = 10; que al final del recorrido el puntero deje de apuntar al primer elemento de la
int *px = &x; tabla. Ello podra solucionarse almacenando su direccin en otro puntero antes
Console.WriteLine((int) px); de iniciar el recorrido y restaurndola a partir de l tras finalizarlo.
Por su parte, convertir cualquier valor entero en un puntero tiene el efecto de devolver
+ y -: Permiten solucionar el problema de ++ y -- antes comentado de una forma
un puntero que apunte a la direccin de memoria indicada por ese nmero. Por
ms cmoda basada en sumar o restar un cierto entero a los punteros. + devuelve
ejemplo, el siguiente cdigo hace que px apunte a la direccin 1029 y luego imprime
la direccin resultante de sumar a la direccin almacenada en el puntero sobre el
por pantalla la direccin de memoria apuntada por px (que ser 1029):
que se aplica el tamao del tipo de dicho puntero tantas veces como indique el
int *px = (int *) 10; entero sumado. - tiene el mismo significado pero r estando dicha cantidad en vez
Console.WriteLine((int) px); de sumarla. Por ejemplo, usando + el bucle anterior podra rescribirse as:

Jos Antonio Gonzlez Seco Pgina 223 Jos Antonio Gonzlez Seco Pgina 224
El lenguaje de programacin C# Tema 18: Cdigo inseguro El lenguaje de programacin C# Tema 18: Cdigo inseguro

Para el resto de tipos a los que se les puede aplicar, sizeof no tiene porqu devolver un
for (int i=0; i<100; i++) resultado constante sino que los compiladores pueden alinear en memoria las estructuras
Console.WriteLine(Elemento{0}={1}, i, *(p+i));
incluyendo bits de relleno cuyo nmero y valores sean en principio indeterminado. Sin
El operador - tambin puede aplicarse entre dos punteros de un mismo tipo, caso embargo, el valor devuelto por sizeof siempre devolver el tamao en memoria exacto
en que devuelve un long que indica cuntos elementos del tipo del puntero del tipo de dato sobre el que se aplique, incluyendo bits de relleno si los tuviese.
pueden almacenarse entre las direcciones de los punteros indicados.
Ntese que es fcil implementar los operadores de aritmtica de punteros usando sizeof.
[]: Dado que es frecuente usar + para acceder a elementos de tablas, tambin se
Para ello, ++ se definira como aadir a la direccin almacenada en el puntero el
ha redefinido el operador [] para que cuando se aplique a una tabla haga lo resultado de aplicar sizeof a su tipo de dato, y -- consistira en restarle dicho valor. Por
mismo y devuelva el objeto contenido en la direccin resultante. O sea *(p+i) es su parte, el operador + usado de la forma P + N (P es un puntero de tipo T y N un entero)
equivalente a p[i], con lo que el cdigo anterior equivale a: lo que devuelve es el resultado de aadir al puntero sizeof(T)*N, y P N devuelve el
resultado de restarle sizeof(T)*N. Por ltimo, si se usa - para restar dos punteros P1 y P2
for (int i=0; i<100; i++) de tipo T, ello es equivalente a calcular (((long)P1) - ((long)P2)))/sizeof(T)
Console.WriteLine(Elemento{0}={1}, i, p[i]);

No hay que confundir el acceso a los elementos de una tabla aplicando [] sobre Operador stackalloc. Creacin de tablas en pila.
una variable de tipo tabla normal con el acceso a travs de un puntero que apunte
a su primer elemento. En el segundo caso no se comprueba si el ndice indicado
Cuando se trabaja con punteros puede resultar interesante reservar una zona de memoria
se encuentra dentro del rango de la tabla, con lo que el acceso es ms rpido pero
en la pila donde posteriormente se puedan ir almacenando objetos. Precisamente para
tambin ms proclive a errores difciles de detectar.
eso est el operador stackalloc, que se usa siguindose la siguiente sintaxis:
Finalmente, respecto a la aritmtica de punteros, hay que tener en cuenta que por stackalloc <tipo>[<nmero>]
eficiencia, en las operaciones con punteros nunca se comprueba si se producen
desbordamientos, y en caso de producirse se truncan los resultados sin avisarse de ello stackalloc reserva en pila el espacio necesario para almacenar contiguamente el nmero
mediante excepciones. Por eso hay que tener especial cuidado al operar con punteros no de objetos de tipo <tipo> indicado en <nmero> (reserva sizeof(<tipo>)*<nmero> bytes) y
sea que un desbordamiento no detectado cause errores de causas difciles de encontrar. devuelve un puntero a la direccin de inicio de ese espacio. Si no quedase memoria libre
suficiente para reservarlo se producira una excepcin System.StackOverflowException.

Operadores relacionados con cdigo inseguro stackalloc slo puede usarse para inicializar punteros declarados como variables locales
y slo en el momento de su declaracin.. Por ejemplo, un puntero pt que apuntase al
Operador sizeof. Obtencin de tamao de tipo principio de una regin con capacidad para 100 objetos de tipo int se declarara con:
int * pt = stackalloc int[100];
El operador unario y prefijo sizeof devuelve un objeto int con el tamao en bytes del
tipo de dato sobre el que se aplica. Slo puede aplicarse en contextos inseguros y slo a Sin embargo, no sera vlido hacer:
tipos de datos para los que sea posible definir punteros, siendo su sintaxis de uso:
int * pt;
sizeof(<tipo>) pt = stackalloc int[100]; // ERROR: Slo puede usarse stackalloc en declaraciones

Cuando se aplica a tipos de datos bsicos su resultado es siempre constante. Por ello, el Aunque pueda parecer que stackalloc se usa como sustituto de new para crear tablas en
compilador optimiza dichos usos de sizeof sustituyndolos internamente por su valor pila en lugar de en memoria dinmica, no hay que confundirse: stackalloc slo reserva
(inlining) y considerando que el uso del operador es una expresin constante. Estas un espacio contiguo en pila para objetos de un cierto tipo, pero ello no significa que se
constantes correspondientes a los tipos bsicos son las indicadas en la Tabla 10: cree una tabla en pila. Las tablas son objetos que heredan de System.Array y cuentan
con los miembros heredados de esta clase y de object, pero regiones de memoria en pila
Tipos Resultado reservadas por stackalloc no. Por ejemplo, el siguiente cdigo es invlido.
sbyte, byte, bool 1
short, ushort, char 2 int[] tabla;
int, uint, float 4 int * pt = stackalloc int[100];
long, ulong, double 8 tabla = *pt; // ERROR: El contenido de pt es un int, no una tabla (int[])
Console.WriteLine(pt->Length); // ERROR: pt no apunta a una tabla
Tabla 10: Resultados de sizeof para tipos bsicos
Sin embargo, gracias a que como ya se ha comentado en este tema el operador [] est
redefinido para trabajar con punteros, podemos usarlo para acceder a los diferentes

Jos Antonio Gonzlez Seco Pgina 225 Jos Antonio Gonzlez Seco Pgina 226
El lenguaje de programacin C# Tema 18: Cdigo inseguro El lenguaje de programacin C# Tema 18: Cdigo inseguro

objetos almacenados en las regiones reservadas con stackalloc como si fuesen tablas. Los punteros declarados en <declaraciones> slo existirn dentro de <instrucciones>, y al
Por ejemplo, este cdigo guarda en pila los 100 primeros enteros y luego los imprime: salir de dicho bloque se destruirn. Adems, si se les indica como valor inicial una tabla
o cadena que valga null saltar una NullReferenceException. Tambin hay que sealar
class Stackalloc que aunque slo pueden declarase punteros de un mismo tipo en cada fixed, se puede
{ simular fcilmente la declaracin de punteros de distintos tipos anidando varios fixed.
public unsafe static void Main()
{
int * pt = stackalloc int[100]; Por otro lado, los punteros declarados en <declaraciones> son de slo lectura, ya que si
for (int i=0; i<100; i++) no podra cambirseles su valor por el de una direccin de memoria no fijada y conducir
pt[i] = i; ello a errores difciles de detectar.
for(int i=0; i<100; i++)
System.Console.WriteLine(pt[i]);
}
Un uso frecuente de fixed consiste en apuntar a objetos de tipos para los que se puedan
} declarar punteros pero que estn almacenados en tablas, ya que ello no se puede hacer
directamente debido a que las tablas se almacenan en memoria dinmica. Por ejemplo,
Ntese que, a diferencia de lo que ocurrira si pt fuese una tabla, en los accesos con pt[i] copiar usando punteros una tabla de 100 elementos de tipo int en otra se hara as:
no se comprueba que i no supere el nmero de objetos para los que se ha reservado
memoria. Como contrapartida, se tiene el inconveniente de que al no ser pt una tabla no class CopiaInsegura
{
cuenta con los mtodos tpicos de stas y no puede usarse foreach para recorrerla. public unsafe static void Main()
{
Otra ventaja de la simulacin de tablas con stackalloc es que se reserva la memoria int[] tOrigen = new int[100];
mucho ms rpido que el tiempo que se tardara en crear una tabla. Esto se debe a que int[] tDestino = new int[100];
reservar la memoria necesaria en pila es tan sencillo como incrementar el puntero de
fixed (int * pOrigen=tOrigen, pDestino=tDestino)
pila en la cantidad correspondiente al tamao a reservar, y no hay que perder tiempo en {
solicitar memoria dinmica. Adems, stackalloc no pierde tiempo en inicializar con for (int i=0; i<100; i++)
algn valor el contenido de la memoria, por lo que la tabla se crea antes pero a costa pOrigen[i] = pDestino[i];
de que luego sea ms inseguro usarla ya que hay que tener cuidado con no leer trozos de }
ella antes de asignarles valores vlidos. }
}

Como puede deducirse del ejemplo, cuando se inicializa un puntero con una tabla, la
Fijacin de variables apuntadas direccin almacenada en el puntero en la zona <declaraciones> del fixed es la del primer
elemento de la tabla (tambin podra haberse hecho pOrigen = &tOrigen[0]), y luego es
Aunque un puntero slo puede apuntar a datos de tipos que puedan almacenarse posible usar la aritmtica de punteros para acceder al resto de elementos a partir de la
completamente en pila (o sea, que no sean ni objetos de tipos referencia ni estructuras direccin del primero ya que stos se almacenan consecutivamente.
con miembros de tipos referencia), nada garantiza que los objetos apuntados en cada
momento estn almacenados en pila. Por ejemplo, las variables estticas de tipo int o los Al igual que tablas, tambin puede usarse fixed para recorrer cadenas. En este caso lo
elementos de una tabla de tipo int se almacenan en memoria dinmica an cuando son que hay que hacer es inicializar un puntero de tipo char * con la direccin del primer
objetos a los que se les puede apuntar con punteros. carcter de la cadena a la que se desee que apunte tal y como muestra este ejemplo en el
que se cambia el contenido de una cadena Hola por XXXX:
Si un puntero almacena la direccin de un objeto almacenado en memoria dinmica y el
recolector de basura cambia al objeto de posicin tras una compactacin de memoria class CadenaInsegura
resultante de una recoleccin, el valor almacenado en el puntero dejar de ser vlido. {
Para evitar que esto ocurra se puede usar la instruccin fixed, cuya sintaxis de uso es: public unsafe static void Main()
{
fixed(<tipo> <declaraciones>) string s=Hola;
<instrucciones>
Console.WriteLine(Cadena inicial: {0}, s);
fixed (char * ps=s)
El significado de esta instruccin es el siguiente: se asegura que durante la ejecucin del
{
bloque de <instrucciones> indicado el recolector de basura nunca cambie la direccin de for (int i=0;i<s.Length;i++)
ninguno de los objetos apuntados por los punteros de tipo <tipo> declarados. Estas ps[i] = A;
<declaraciones> siempre han de incluir una especificacin de valor inicial para cada }
puntero declarado, y si se declaran varios se han de separar con comas. Console.WriteLine(Cadena final: {0}, s);
}

Jos Antonio Gonzlez Seco Pgina 227 Jos Antonio Gonzlez Seco Pgina 228
El lenguaje de programacin C# Tema 18: Cdigo inseguro El lenguaje de programacin C# Tema 19: Documentacin XML

}
Tema 19: Documentacin XML
La salida por pantalla de este ltimo programa es:
Hola Concepto y utilidad de la documentacin XML
AAAA
La documentacin de los tipos de datos creados siempre ha sido una de las tareas ms
La ventaja de modificar la cadena mediante punteros es que sin ellos no sera posible
pesadas y aburridas a las que un programador se ha tenido que enfrentar durante un
hacerlo ya que el indizador definido para los objetos string es de slo lectura.
proyecto, lo que ha hecho que muchas veces se escriba de manera descuidada y poco
concisa o que incluso que no se escriba en absoluto. Sin embargo, escribirla es una tarea
Cuando se modifiquen cadenas mediante punteros hay que tener en cuenta que, aunque
muy importante sobre todo en un enfoque de programacin orientada a componentes en
para facilitar la comunicacin con cdigo no gestionado escrito en C o C++ las cadenas
tanto que los componentes desarrollados muchas veces van a reutilizados por otros. E
en C# tambin acaban en el carcter \0, no se recomienda confiar en ello al recorrerlas
incluso para el propio creador del componente puede resultar de inestimable ayuda si en
con punteros porque \0 tambin puede usarse como carcter de la cadena. Por ello, es
el futuro tiene que modificarlo o usarlo y no recuerda exactamente cmo lo implement.
mejor hacer como en el ejemplo y detectar su final a travs de su propiedad Length.
Para facilitar la pesada tarea de escribir la documentacin, el compilador de C# es capaz
Hay que sealar que como fixed provoca que no pueda cambiarse de direccin a ciertos
de generarla automticamente a partir de los comentarios que el progamador escriba en
objetos almacenados en memoria dinmica, ello puede producir la generacin de huecos
los ficheros de cdigo fuente. As se evita trabajar con dos tipos de documentos por
en memoria dinmica, lo que tiene dos efectos muy negativos:
separado (fuentes y documentacin) que deban actualizarse simultneamente para evitar
incosistencias derivadas de que por error evolucionen por separado.
El recolector de basura est optimizado para trabajar con memoria compactada,
pues si todos los objetos se almacenan consecutivamente en memoria dinmica
El compilador genera la documentacin en formato XML con la idea de que sea
crear uno nuevo es tan sencillo como aadirlo tras el ltimo. Sin embargo, fixed
fcilmente legible para cualquier aplicacin. Por ejemplo, el sistema Intellisense de
rompe esta consecutividad y la creacin de objetos en memoria dinmica dentro
VS.NET la aprovecha para proporcionar las descripciones emergentes de los tipos de
de este tipo de instrucciones es ms lenta porque hay que buscar huecos libres.
datos y miembros que se vayan utilizando en el cdigo. Para ello, espera encontrar esta
documentacin en el mismo directorio que los ensamblados a los que se referencie, con
Por defecto, al eliminarse objetos de memoria durante una recoleccin de basura el mismo nombre que estos y extensin .xml. El siguiente ejemplo muestra a VS.NET
se compacta la memoria que queda ocupada para que todos los objetos se aprovechando esta documentacin para describir un parmetro b de un mtodo F():
almacenen en memoria dinmica. Hacer esto dentro de sentencias fixed es ms
lento porque hay que tener en cuenta si cada objeto se puede o no mover.

Por estas razones es conveniente que el contenido del bloque de instrucciones de una
sentencia fixed sea el mnimo posible, para que as el fixed se ejecute lo antes posible.

Jos Antonio Gonzlez Seco Pgina 229 Jos Antonio Gonzlez Seco Pgina 230
El lenguaje de programacin C# Tema 19: Documentacin XML El lenguaje de programacin C# Tema 19: Documentacin XML

Por su parte, para facilitar la legibilidad de esta documentacin a humanos bastara Por ejemplo:
aadir al XML generado una hoja de estilo XSL o usar utilizar aplicacin especfica
encargada de convertirla a un formato fcilmente legible por humanos, como HTML, el <EtiquetaSinContenidoDeEjemplo/>
PDF de Acrobat Reader, el .doc de Microsoft Word o el .chm de la ayuda de Windows.
4. En realidad en la <etiqueta> inicial no tiene porqu indicarse slo un identificador
que sirva de nombre para la etiqueta usada, sino que tambin pueden indicarse
Aunque explicar XML y XSL queda fuera del alcance del libro, a continuacin
atributos que permitan configurar su significado. Estos atributos se escriben de la
resumirn brevemente tanto XML como la forma de aplicar hojas XSL a ficheros XML.
forma <nombreAtributo>=<valor> y separados mediante espacios. Por ejemplo:
<EtiquetaConAtributo AtributoEjemplo=valor1 >
Introduccin a XML Etiqueta de ejemplo que incluye un atributo
</EtiquetaConAtributo>
Antes de continuar es necesario hacer una pequea introduccin a XML ya que es el <EtiquetaSinContenidoYConAtributo AtributoEjemplo=valor2 />
lenguaje en que se han de escribir los comentarios especiales de documentacin. Si ya
conoce este lenguaje puede saltarse este epgrafe. 5. Slo pueden utilizarse caracteres ASCII, y los no ASCII (acentos, ees, ...) o que
tengan algn significado especial en XML, han de sustituirse por secuencias de
XML (Extensible Markup Language) es un metalenguaje de etiquetas, lo que significa escape de la forma &#<cdigoUnicode>; Para los caracteres ms habituales tambin
que es un lenguaje que se utiliza para definir lenguajes de etiquetas. A cada lenguaje se han definido las siguientes secuencias de escape especiales:
creado con XML se le denomina vocabulario XML, y la documentacin generada por
el compilador de C# est escrita en un vocabulario de este tipo. Carcter Secuencia de escape Unicode Secuencia de escape especial
< &#60; &lt;
Los comentarios a partir de los que el compilador generar la documentacin han de > &#62; &gt;
escribirse en XML, por lo que han de respetar las siguientes reglas comunes a todo & &#38; &amp;
&#39; &apos;
documento XML bien formado:
&#34; &quot;

1. La informacin ha de incluirse dentro de etiquetas, que son estructuras de la forma: Tabla 11: Secuencias de espace XML de uso frecuente

<<etiqueta>> <contenido> </<etiqueta>>


Comentarios de documentacin XML
En <etiqueta> se indica cul es el nombre de la etiqueta a usar. Por ejemplo:
Sintaxis general
<EtiquetaEjemplo> Esto es una etiqueta de ejemplo </EtiquetaEjemplo>

Como <contenido> de una etiqueta puede incluirse tanto texto plano (es el caso del Los comentarios de documentacin XML se escriben como comentarios normales de
ejemplo) como otras etiquetas. Lo que es importante es que toda etiqueta cuyo uso una lnea pero con las peculiaridades de que su primer carcter ha de ser siempre / y de
comience dentro de otra tambin ha de terminar dentro de ella. O sea, no es vlido: que su contenido ha de estar escrito en XML ya que ser insertado por el compilador en
el fichero XML de documentacin que genera. Por tanto, son comentarios de la forma:
<Etiqueta1> <Etiqueta2> </Etiqueta1></Etiqueta2>
/// <textoXML>
Pero lo que s sera vlido es:
Estos comentarios han preceder las definiciones de los elementos a documentar. Estos
<Etiqueta1> <Etiqueta2> </Etiqueta2></Etiqueta1> elementos slo pueden ser definiciones de miembros, ya sean tipos de datos (que son
miembros de espacios de nombres) o miembros de tipos datos, y han de colocarse
Tambin es posible mezclar texto y otras etiquetas en el <contenido>. Por ejemplo: incluso antes que sus atributos.
<Etiqueta1> Hola <Etiqueta2> a </Etiqueta2> todos </Etiqueta1>
En <textoXML> el programador puede incluir cualesquiera etiquetas con el significado,
2. XML es un lenguaje sensible a maysculas, por lo que si una etiqueta se abre con contenido y atributos que considere oportunos, ya que en principio el compilador no las
una cierta capitalizacin, a la hora de cerrarla habr que usar exactamente la misma. procesa sino que las incluye tal cual en la documentacin que genera dejando en manos
de las herramientas encargadas de procesar dicha documentacin la determinacin de si
3. Es posible usar la siguiente sintaxis abreviada para escribir etiquetas sin <contenido>: se han usado correctamente.

<<etiqueta>/> Sin embargo, el compilador comprueba que los comentarios de documentacin se


coloquen donde deberan y que contengan XML bien formado. Si no fuese as generara

Jos Antonio Gonzlez Seco Pgina 231 Jos Antonio Gonzlez Seco Pgina 232
El lenguaje de programacin C# Tema 19: Documentacin XML El lenguaje de programacin C# Tema 19: Documentacin XML

un mensaje de aviso y en la documentacin generada los sustituira por un comentario < op_LessThan >> op_RightShift
XML que explicase el tipo de error cometido. > op_GreaterThan true op_True
>= op_GreaterThanOrEqual false op_False
<= op_LowerThanOrEqual ++ op_Increment
== op_Equality -- op_Decrement
El atributo cref != op_Inequality Conversin explcita Op_Explict
! op_LogicalNot Conversin implcita Op_Implicit
Aunque en principio los atributos de las etiquetas no tienen ningn significado Tabla 12: Nombres dados a operadores en documentacin XML
predeterminado para el compilador, hay una excepcin: el atributo cref siempre va a
tener un significado concreto consistente en forzarlo a comprobar cuando vaya a generar En el caso de los operadores de conversin, tras la lista de parmetros se incluye
la documentacin si existe el elemento cuyo nombre indique y, si no es as, hacerle adicionalmente un carcter ~ seguido del tipo de retorno del operador.
producir un mensaje de aviso (su nombre viene de check reference)
Para que se entienda mejor la forma en que se han de dar valores a cref, a continuacin
Los elementos especificados en cref suelen indicarse mediante calificacin completa, y se muestra un fragmento de cdigo de ejemplo en el que junto a cada definicin se ha
pueden ser tanto nombres de miembros como de espacios de nombres. En el Tema 6: escrito un comentario con el valor que habra que darle a cref para referenciarla:
Espacios de Nombres ya se explic como indicar as nombres de tipos y de espacios de
nombres, mientras que para indicar el de miembros de tipos basta escribir el nombre // cref=Espacio
completo del tipo donde estn definidos seguido de un punto tras el cual, dependiendo namespace Espacio
{
del tipo de miembro del que se trate, se escribira : // cref=Espacio.Clase
class Clase
Si es un campo, propiedad, evento o tipo interno, su nombre. {
// cref=Espacio.Clase.Campo
int Campo;
Si es un mtodo, su nombre seguido de los nombres completos de los tipos de sus
parmetros separados mediante comas y entre parntesis. Estos nombres de tipos de // cref=Espacio.Clase.Propiedad
parmetros llevan un carcter @ concatenado al final en los parmetros ref u out, un int Propiedad
carcter * al final en los que sean de tipos punteros, un smbolo [] por cada nivel de { set {} }
anidacin al final de los que sean tablas unidimensionales, y una estructura de la
// cref=Espacio.Clase.EstructuraInterna
forma [0:,0:] al final de los que sean tablas bidimensionales (para tablas de ms struct EstructuraInterna {}
dimensiones simplemente se iran aadiendo los bloques ,0: apropiados)14
// cref=Espacio.Clase.DelegadoInterno
Si es un indizador, el identificador Item seguido de la lista de tipos de sus ndices public delegate int DelegadoInterno(string s, float f);
como si de los parmetros de un mtodo se tratase
// cref =Espacio.Clase.Evento
public event DelegadoInterno Evento;
Si es un constructor de objeto, el identificador #ctor seguido de la lista de tipos de
sus parmetros como si de un mtodo normal se tratase. Si el constructor fuese de // cref=Espacio.Clase.Metodo(System.Int32, System.Int32@,
tipos entonces el identificador usado sera #cctor // System.Int32*, System.Int32@,
// System.Int32[][], System.Int32[0:, 0:, 0:])
int Metodo(int a, out int b, int * c, ref d, int[][] e, int[,,] f)
Si es un destructor, el identificador Finalize. {return 1;}

Si es un operador, el identificador que represente a ese operador seguido de la lista // cref=Espacio.Clase.Item(System.String)


int this[string s]
de los tipos de sus operandos como si fuesen los parmetros de un mtodo normal.
{ set {} }
En la Tabla 12 se resumen los identificadores que se dan a cada operador:
// cref=Espacio.Clase.#ctor
Operador Identificador Operador Identficador Clase(int a)
+ op_Addition & op_BitwiseAnd {}
- op_Substraction | op_BitwiseOr
* op_Multiply ^ op_ExclusiveOr // cref=Espacio.Clase.#cctor
/ op_Division ~ op_OnesComplement static Clase(int a)
% op_Modulus << op_LeftShift {}

// cref=Espacio.Clase.Finalize
14
En general la sintaxis que se sigue es <ndiceInferior>:<ndiceSuperior>, pero en C# se genera ~X()
siempre 0: porque las tablas slo pueden indizarse desde 0 y su lmite superior es variable,

Jos Antonio Gonzlez Seco Pgina 233 Jos Antonio Gonzlez Seco Pgina 234
El lenguaje de programacin C# Tema 19: Documentacin XML El lenguaje de programacin C# Tema 19: Documentacin XML

{} <seealso>: Se usa para indicar un elemento cuya documentacin guarda alguna


relacin con la del elemento al que precede. No tiene contenido y el nombre del
// cref=Espacio.Clase.op_Addition(Espacio.Clase, Espacio.Clase)
public static int operator +(Clase operando1, Clase operando2) elemento al que se remite se indica en su atributo cref, por lo que el compilador
{ return 1; } comprobar si existe. Para indicar mltiples documentaciones relativas a un cierto
elemento basta usar una etiqueta <seealso> por cada una.
// cref=Espacio.Clase.op_Explicit (Espacio.Clase)~System.Int32
public static explicit operator int(Clase fuente) <permission>: Se utiliza para indicar qu permiso necesita un elemento para poder
{ return 1; }
funcionar. En su contenido se indica una descripcin del mismo, y su atributo cref
} suele usarse para indicar el tipo que representa a ese permiso Por ejemplo:
}
/// <permission cref=System.Security.Permissions.FileIOPermission>
En realidad no es siempre necesario usar calificacin completa en el valor de cref. Si se /// Necesita permiso de lectura/escritura en el directorio C:\Datos
/// </permission>
referencia a un tipo desde la misma definicin de espacio de nombres desde donde se le
defini o que importa su espacio de nombres, no es necesario incluir dicho espacio en la Como con <seealso>, si un miembro ha de disponer varios tipos de permisos puede
referencia; y si se referencia a un miembro desde el mismo tipo donde se defini, no es documentarse su definicin con tantas etiquetas <permission> como sea necesario.
necesario incluir ni el nombre del tipo ni el de su espacio de nombres.

Etiquetas relativas a mtodos


Etiquetas recomendadas para documentacin XML
Adems de las etiquetas uso general ya vistas, en las definiciones de mtodos se pueden
Aunque el programador puede utilizar las etiquetas estime oportunas en sus comentarios usar las siguientes etiquetas recomendadas adicionales para describir sus parmetros y
de documentacin y darles el significado que quiera, Microsoft recomienda usar un valor de retorno:
juego de etiquetas concreto con significados concretos para escribir ciertos tipos de
informacin comn. Con ello se obtendra un conjunto bsico de etiquetas que cualquier <param>: Permite documentar el significado de un parmetro de un mtodo. En su
herramienta que trabaje con documentacin XML pueda estar preparada para procesar propiedad name se indica el nombre del parmetro a documentar y en su contenido
(como veremos ms adelante, el propio Visual Studio.NET da ciertos usos especficos a se describe su utilidad. Por ejemplo:
la informacin as documentada)
/// <summary> Mtodo que muestra un texto por pantalla </summary>
En los siguientes epgrafes se explican estas etiquetas recomendadas agrupndolas /// <param name=texto> Texto a mostrar </param>
segn su utilidad. Todas son opcionales, y no incluirlas slo tiene el efecto de que no en
bool MuestraTexto(string texto)
la documentacin resultante no se generaran las secciones correspondientes a ellas. {...}

Al generarse la documentacin se comprueba si el mtodo documentado dispone de


Etiquetas de uso genrico algn parmetro con el nombre indicado en name y, como ocurre con cref, si no
fuese as se generara un mensaje de aviso informando de ello.
Hay una serie de etiquetas predefinidas que pueden colocarse, en cualquier orden,
precediendo las definiciones de miembros en los ficheros fuente. Estas etiquetas, junto <paramref>: Se usa para referenciar a parmetros de mtodos. No tiene contenido y
al significado recomendado para su contenido, son las explicadas a continuacin: el nombre del parmetro referenciado se indica en su atributo name. Por ejemplo:

<summary>: Su contenido se utiliza para indicar un resumen sobre el significado del /// <summary>
elemento al que precede. Cada vez que en VS.NET se use el operador . para acceder /// Mtodo que muestra por pantalla un texto con un determinado color
/// </summary>
a algn miembro de un objeto o tipo se usar esta informacin para mostrar sobre la /// <param name=texto> Texto a mostrar </param>
pantalla del editor de texto un resumen acerca de su utilidad. /// <param name=color>
/// Color con el que mostrar el <paramref name=texto/> indicado
<remarks>: Su contenido indica una explicacin detallada sobre el elemento al que /// </param>
precede. Se recomienda usar <remarks> para dar una explicacin detallada de los
bool MuestraTexto(string texto, Color color)
tipos de datos y <summary> para dar una resumida de cada uno de sus miembros. {...}

<example>: Su contenido es un ejemplo sobre cmo usar el elemento al que precede.

Jos Antonio Gonzlez Seco Pgina 235 Jos Antonio Gonzlez Seco Pgina 236
El lenguaje de programacin C# Tema 19: Documentacin XML El lenguaje de programacin C# Tema 19: Documentacin XML

Nuevamente, al generarse la documentacin se comprobar si realmente el Etiquetas relativas a formato


parmetro referenciado existe en la definicin del mtodo documentado y si no es
as se genera un mensaje de aviso informando de ello. Para mejorarla forma de expresar el contenido de las etiquetas de documentacin que se
utilicen es posible incluir en ellas las siguientes etiquetas de formato:
<returns>: Permite documentar el significado del valor de retorno de un mtodo,
indicando como contenido suyo una descripcin sobre el mismo. Por ejemplo: <see>: Se utiliza para indicar hipervnculos a otros elementos de la documentacin
/// <summary>
generada. Es una etiqueta sin contenido en la que el destino del enlace es la
/// Mtodo que muestra por pantalla un texto con un determinado color documentacin del miembro cuyo nombre completo se indica en su atributo cref.
/// </summary> Ese nombre es tambin el texto que las hojas de estilo suelen mostrar para
/// <param name=texto> Texto a mostrar </param> representar por pantalla el enlace, por lo que los usos de esta etiqueta suelen ser de
/// <param name=color> la forma:
/// Color con el que mostrar el <paramref name=texto/> indicado
/// </param> /// <summary>
/// <returns> Indica si el mtodo se ha ejecutado con xito o no </returns> /// Muestra por la salida estndar el mensaje Hola!
/// Si no sabe como se escribe en pantalla puede consultar la documentacin del
bool MuestraTexto(string texto, Color color) ///mtodo <see cref=System.Console.WriteLine/>
{...} /// </summary>
public static void Saluda()
{
Etiquetas relativas a propiedades Console.WriteLine(Hola!);
}

El uso ms habitual de una propiedad consiste en controlar la forma en que se accede a


Ntese que la diferencia de <see> y <seealso> es que la primera se usa para indicar
un campo privado, por lo que esta se comporta como si almacenase un valor. Mediante
enlaces en medio de textos mientras que la otra se usa para indicar enlaces que se
el contenido de la etiqueta <value> es posible describir el significado de ese valor:
deseen incluir en una seccin aparte tipo Vase tambin.
private int edad;
<code> y <c>: Ambas etiquetas se usan para delimitar textos han de ser considerarse
/// <summary> fragmentos de cdigo fuente. La diferencia entre ellas es que <code> se recomienda
/// Almacena la edad de una persona. Si se le asigna una edad menor que 0 la usar para fragmentos multilnea y <c> para los de una nica lnea; y que las hojas de
/// sustituye por 0. estilo mostrarn el contenido de las etiquetas <code> respetando su espaciado y el
/// </summary>
/// <value> Edad de la persona representada </value> de las etiquetas <c> sin respetarlo y tratando cualquier aparicin consecutiva de
public int Edad varios caracteres de espaciado como si fuesen un nico espacio en blanco.
{
set { edad = (value<0)? 0:value } En general, <code> suele usarse dentro de etiquetas <example> para mostrar
get { return edad; } fragmentos de cdigos de ejemplo, mientras que <c> suele usarse para hacer
} referencia a elementos puntales de los cdigos fuente. Por ejemplo:

/// <example>
Etiquetas relativas a excepciones /// Este ejemplo muestra cmo llamar al mtodo <c>Cumple()</c> de esta clase:
/// <code>
Para documentar el significado de un tipo definido como excepcin puede incluirse un /// Persona p = new Persona(...);
/// p.Cumple();
resumen sobre el mismo como contenido de una etiqueta de documentacin <exception> /// </code>
que preceda a su definicin. El atributo cref de sta suele usarse para indicar la clase de /// </example>
la que deriva la excepcin definida. Por ejemplo:
<para>: Se usa para delimitar prrafos dentro del texto contenido en otras etiquetas,
/// <exception cref=System.Exception> considerndose que el contenido de cada etiqueta <para> forma parte de un prrafo
/// Excepcin de ejemplo creada por Josan
/// </exception> distinto. Generalmente se usa dentro de etiquetas <remarks>, ya que son las que
suelen necesitar prrafos al tener un contenido ms largo. Por ejemplo:
class JosanExcepcin: Exception
{} /// <remarks>
/// <para>
/// Primer prrafo de la descripcin del miembro...
/// </para>

Jos Antonio Gonzlez Seco Pgina 237 Jos Antonio Gonzlez Seco Pgina 238
El lenguaje de programacin C# Tema 19: Documentacin XML El lenguaje de programacin C# Tema 19: Documentacin XML

/// <para> /// <term>


/// Segundo prrafo de la descripcin del miembro... /// Trmino 2
/// </para> /// </term>
/// </remarks> /// <description>
/// Descripcin de trmino 2
<list>: Se utiliza para incluir listas y tablas como contenido de otras etiquetas. Todo /// </description>
uso de esta etiqueta debera incluir un atributo type que indique el tipo de estructura /// </item>
/// </list>
se desea definir segn tome uno de los siguientes valores:

bullet: Indica que se trata de una lista no numerada Generacin de documentacin XML
number: Indica que se trata de una lista numerada
table: Indica que se trata de una tabla
Generacin a travs del compilador en lnea de comandos
El contenido de <list> depender del tipo de estructura representado en cada caso:
Usando el compilador en lnea de comandos puede generarse documentacin sobre los
Si se trata de una lista normal ya sea numerada o no numerada- su contenido tipos definidos en los fuentes a compilar usando la opcin de compilacin
ser una etiqueta <item> por cada elemento de la lista, y cada etiqueta de este /doc:<fichero>. Por ejemplo, para compilar un fichero de cdigo fuente Persona.cs y
tipo contendr una etiqueta <description> con el texto correspondiente a ese generar su documentacin en Persona.xml, habra que llamar al compilador con:
elemento. Por ejemplo: csc persona.cs /doc:persona.xml
/// <list type=bullet>
/// <item>
Si se abre con Internet Explorer el fichero XML as generado se ver un conjunto de
/// <description> etiquetas que recogen toda la informacin ubicada en los comentarios de documentacin
/// Elemento 1 de los fuentes compilados. Aunque para una persona pueda resultar difcil leer esta
/// </description> informacin, para una aplicacin hacerlo es muy sencillo a travs de un analizador
/// </item> XML. Si se desea que tambin sea legible para humanos basta abrirlo con cualquier
/// <item>
/// <description>
editor de textos y aadirle una primera lnea de la forma:
/// Elemento 2
/// </description> <?xml:stylesheet href=<ficheroXSL>" type="text/xsl"?>
/// </item>
/// </list> Con esta lnea se indica que se desea utilizar el fichero indicado en <ficheroXSL> como
hoja de estilo XSL con la que convertir la documentacin XML a algn lenguaje ms
Si se tratase de una tabla, su contenido sera similar al de las listas normales slo fcilmente legible por humanos (generalmente, HTML). Por ejemplo, si doc.xsl es el
que por cada fila se incluira una etiqueta <item> y dentro de sta se incluira una nombre de dicho fichero XSL, bastara escribir:
etiqueta <description> por cada columna de esa fila. Opcionalmente se podra
incluir tambin una etiqueta ~<listheader> antes de las ~<item> donde se <?xml:stylesheet href="doc.xsl" type="text/xsl"?>
indicara el texto de la cabecera de la tabla. Esta etiqueta se usa igual que las
Para hacerse una idea de las diferencias existentes entre abrir con Internet Explorer un
etiquetas ~<item>: incluir una etiqueta ~<description> por cada columna.
fichero de documentacin sin hoja XSL asociada y abrir ese mismo fichero pero
Por ltimo, si fuese una lista de definiciones cada <item> contendra una primera asocindole una hoja XSL, puede observar en la Ilustracin 6 y la Ilustracin 7:
etiqueta <term> con el nombre del elemento a definir y otra segunda etiqueta
<description> con su definicin. Opcionalmente tambin podra incluirse una
etiqueta <listheader> con la cabecera de la lista. Por ejemplo:

/// <list type=bullet>


/// <item>
/// <term>
/// Trmino 1
/// </term>
/// <description>
/// Descripcin de trmino 1
/// </description>
/// </item>
/// <item>

Jos Antonio Gonzlez Seco Pgina 239 Jos Antonio Gonzlez Seco Pgina 240
El lenguaje de programacin C# Tema 19: Documentacin XML El lenguaje de programacin C# Tema 19: Documentacin XML

Si prefiere usar Visual Studio.NET, entonces para la generacin de la documentacin


basta sealar el proyecto a documentar en el Solution Explorer y escribir el nombre
del fichero XML a generar en el cuadro de texto View Property Pages
Configuration Properties Build XML Documentation File

Cuando se compile el proyecto, la documentacin XML sobre el mismo se guardar en


el fichero indicado en el cuadro de texto anterior. Este fichero se almacenar dentro de
la subcarpeta Bin del directorio del proyecto, y si se desea poder visualizarla desde el
Solution Explorer hay que activar en ste el botn Show All Files.

En principio, para conseguir visualizar esta documentacin en un formato ms legible


para humanos podra asocirsele una hoja XSL como se explic para el caso del
compilador en lnea de comandos. Sin embargo, Visual Studio.NET proporciona una
forma ms sencilla de hacerlo a travs de la herramienta ubicada en Tools Build
Comments Web Pages Esta utilidad a partir de la informacin incluida en las etiquetas
recomendadas de los comentarios del fuente genera pginas HTML que muestran la
documentacin del proyecto de una forma vistosa e intuitiva (ver Ilustracin 8)

Ilustracin 6: Documentacin XML sin hoja de estilo

Ilustracin 8: Documentacin HTML generada por Visual Studio.NET

Ilustracin 7: Documentacin XML con hoja de estilo XSL


Estructura de la documentacin XML
No se preocupe si no sabe escribir hojas de estilo, pues como se explica en el siguiente
epgrafe, Visual Studio.NET incluye una herramienta que puede generar directamente la Ahora que ya sabemos cmo escribir comentarios de documentacin y generar a partir
documentacin en un HTML fcilmente legible para humanos. de ellos un fichero XML con la documentacin de los tipos de datos de un fichero, slo
queda estudiar cul es concretamente la estructura de dicho fichero generado ya que
entenderla es fundamental para la escritura de aplicaciones encargadas de procesarlo.
Generacin a travs de Visual Studio.NET
En principio, si compilamos como mdulo un fuente sin comentarios de documentacin
pero solicitando la generacin de documentacin, se obtendr el siguiente fichero XML:

Jos Antonio Gonzlez Seco Pgina 241 Jos Antonio Gonzlez Seco Pgina 242
El lenguaje de programacin C# Tema 19: Documentacin XML El lenguaje de programacin C# Tema 19: Documentacin XML

<?xml version="1.0"?> <member name="T:A">


<doc> <summary>
<members> Clase de ejemplo de cmo escribir documentacin XML
</members> </summary>
</doc> </member>
<member name="M:A.Main">
Como se ve, la primera lnea del fichero es la cabecera tpica de todo fichero XML en la <summary>
que se indica cul es la versin del lenguaje que utiliza. Tras ella se coloca una etiqueta Mtodo principal de ejemplo perteneciente a clase <see cref="T:A"/>
</summary>
<doc> que contendr toda la documentacin generada, y los comentarios de
<remarks>
documentacin de los miembros del fuente compilado se iran incluyendo dentro de la No hace nada
etiqueta <members> que contiene (en este caso dicha etiqueta est vaca ya que el fuente </remarks>
compilado careca de comentarios de documentacin) </member>
</members>
</doc>
Si hubisemos compilado el fuente como librera o como ejecutable se habra generado
un ensamblado, y a la estructura anterior se le aadira una etiqueta adicional dentro de Como puede verse, dentro de la etiqueta <members> no se sigue ninguna estructura
<doc> con informacin sobre el mismo, quedando: jerrquica a la hora de describir los elementos del fuente, sino que todos se describen al
<?xml version="1.0"?>
mismo nivel y de la misma forma: se incluye una etiqueta <member> por cada miembro
<doc> documentado en cuyo atributo name se indica su nombre y en cuyo contenido se inserta
<assembly> el texto de sus comentarios de documentacin.
<name>Persona</name>
</assembly> Ntese que a cada elemento se le da en el atributo name de su etiqueta <member>
<members>
</members>
correspondiente un identificador que lo distingue unvocamente del resto de miembros
</doc> documentados y que sigue la siguiente sintaxis:

Como se ve, dentro de la etiqueta <assembly> contenida en <doc> se indican las <indicadorElemento>:<nombreCompletamenteCalificado>
caractersticas del ensamblado generado. En concreto, su nombre se indica en la etiqueta
El <indicadorElemento> es simplemente un carcter que indica qu tipo de elemento se
<name> que contiene (se supone que el ensamblado se compil con el nombre Persona)
documenta dentro de la etiqueta <member>. Puede tomar estos valores:
Si ahora le aadimos comentarios de documentacin veremos que el contenido de estos
Indicador de tipo de elemento Tipo de elemento indicado
se inserta dentro de la etiqueta <members>, en una etiqueta <member> especfica para T Tipo de dato
cada miembro con comentarios de documentacin. Por ejemplo, dado el fuente:
F Campo
/// <summary> P Propiedad o indizador
/// Clase de ejemplo de cmo escribir documentacin XML M Mtodo (incluidos operadores y contructores)
/// </summary> E Evento
class A
{ Tabla 13: Indicadores de tipos de elementos en documentaciones XML
/// <summary>
/// Mtodo principal de ejemplo perteneciente a clase <see cref="A"/>
/// </summary> Como se ve en el ejemplo, en la documentacin generada se usa tambin la sintaxis de
/// <remarks> los valores del atributo name de las etiquetas <member> para representar las referencias
/// No hace nada mediante atributos cref. Adems, cuando dicha sintaxis se usa para expresar valores de
/// </remarks>
cref pueden usarse dos tipos de indicadores ms:
static void Main()
{}
} Indicador de tipo de elemento Tipo de elemento indicado
N Espacio de nombres
La documentacin XML que generara compilarlo con la opcin /doc es: ! Ninguno. Se genera cuando el miembro indicado
en cref no existe.
<?xml version="1.0"?>
<doc> Tabla 14: Indicadores de tipos de elementos para atributos cref
<assembly>
<name>A</name>
</assembly> La idea que hay detrs de usar la sintaxis vista para representar elementos del fuente es
<members> proporcionar un mecanismo sencillo mediante el que las herramientas encargadas de

Jos Antonio Gonzlez Seco Pgina 243 Jos Antonio Gonzlez Seco Pgina 244
El lenguaje de programacin C# Tema 19: Documentacin XML El lenguaje de programacin C# Tema 19: Documentacin XML

procesar las documentaciones XML puedan determinar cules son los miembros /// </example>
documentados o referenciados y acceder, con ayuda de los tipos de System.Reflection, a class A
{}
sus metadatos asociados.

Separacin entre documentacin XML y cdigo fuente

A veces puede que interesar incrustar toda la documentacin en el mismo fichero que el
cdigo fuente, por ejemplo si se desea reutilizarla en mltiples fuentes o si es muy
voluminosa e incluirla en el fuente dificultara su legibilidad. Para estos casos se da la
posibilidad de dejar la documentacin en un fichero XML aparte y referenciarla en el
cdigo fuente a travs de la etiqueta de documentacin <include>, que su usa as:
<include file=<nombreFichero> path=<rutaDocumentacin>/>

Cuando el compilador encuentre esta etiqueta al generar la documentacin lo que har


ser tratarla como si fuese la etiqueta del fichero <nombreFichero> indicada por la
expresin XPath15 <rutaDocumentacin> Por ejemplo, si se tiene el cdigo:
/// <include file=otro.xml path=Miembros/Miembro[@nombre=A]/*/>
class A
{}

En este uso de <include> se est indicando que se ha de insertar todo el contenido de la


etiqueta <Miembro> contenida en <Miembros> cuyo atributo nombre valga A. Luego, si el
contenido del fichero otro.xml es de la forma:
<Miembros>
...
<Miembro name=A>
<remarks>
Ejemplo de inclusin de documentacin XML externa
</remarks>
<example>
Para crear un objeto de esta clase usar:
<code>
A obj = new A();
</code>
</example>
</Miembro>
...
</Miembros>

Entonces, el compilador generar documentacin como si el fuente contuviese:

/// <remarks>
/// Ejemplo de inclusin de documentacin XML externa
/// </remarks>
/// <example>
/// Para crear un objeto de esta clase usar:
/// <code>
/// A obj = new A();
/// </code>

15
XPath es un lenguaje que se utiliza para especificar rutas en ficheros XML que permitan seleccionar
ciertas etiquetas de los mismos. Si no lo conoce puede encontrar su especificacin en [XPath]

Jos Antonio Gonzlez Seco Pgina 245 Jos Antonio Gonzlez Seco Pgina 246
El lenguaje de programacin C# Tema 20: El compilador de C# de Microsoft El lenguaje de programacin C# Tema 20: El compilador de C# de Microsoft

<indicadorOpcin><opcin>
TEMA 20: El compilador de C# de Microsoft
El <indicadorOpcin> puede ser el carcter / o el carcter -, aunque en adelante slo
Introduccin haremos uso de /. Respecto a <opcin>, pueden indicarse dos tipos de opciones:

A lo largo de los temas anteriores se han explicando muchos aspectos sobre cmo usar Flags: Son opciones cuya aparicin o ausencia tienen un determinado significado
el compilador de C# de Microsoft incluido en el .NET Framework SDK. Sin embargo, para el compilador. Se indican de esta manera:
una vez descrito el lenguaje por completo es el momento adecuado para explicar
<nombreFlag><activado?>
pormenorizadamente cmo utilizarlo y qu opciones de compilacin admite, pues
muchas de ellas se basan en conceptos relacionados con caractersticas del lenguaje. <activado> es opcional e indica si se desea activar el significado del flag. Puede
ser el carcter + para indicar que s o el carcter para indicar que no, aunque en
Por otro lado, las diferentes explicaciones dadas sobre l se han ido desperdigando a lo
realidad darle el valor + es innecesario porque es lo que se toma por defecto.
largo de muchos de los temas previos, por lo que es tambin conviene agruparlas todas
Tambin hay algunos flags que no admiten ninguno de los dos caracteres, pues se
en un mismo sitio de modo que sea ms fcil localizarlas.
considera que siempre que aparezcan en la llamada al compilador es porque se desea
activar su significado y si no apareciesen se considerara que se desea desactivarlo.
Aunque en un principio lo que se va es a explicar cmo usar el compilador en lnea de
comandos, dado que Visual Studio.NET tambin hace uso interno de l para compilar, A continuacin se muestran algunos ejemplos de uso de un flag llamado /optimize.
al final del tema se incluir un epgrafe dedicado a explicar cmo controlar desde dicha No se preocupe por saber ahora para que sirve, sino simplemente fjese en cmo se
herramienta visual las opciones que se utilizarn al llamarlo. usa y note que los dos primeros ejemplos son equivalentes:
csc /optimize Fuente.cs
Sintaxis general de uso del compilador csc /optimize+ Fuente.cs
csc /optimize- Fuente.cs

El nombre del ejecutable del compilador de C# incluido en el .NET Framework SDK es Opciones con valores: A diferencia de los flags, son opciones cuya aparicin no es
csc.exe y podr encontrarlo en la carpeta Microsoft.NET\Framework\v2.0.40607 vlida por s misma sino que siempre que se usen han de incluir la especificacin de
incluida dentro del directorio de instalacin de su versin de Windows16. uno o varios valores. La forma en que se especifican es:

Como ya se vi en el Tema 2 durante la primera toma de contacto con C#, el SDK <nombreFlag>:<valores>
automticamente aade al path esta ruta para poder referenciarlo sin problemas, aunque
si estamos usando VS.NET habr que aadrselo a mano, ejecutando vsvars32.bat o Los <valores> indicados pueden ser cualesquiera, aunque si se desea especificar
abriendo la consola de comandos desde Smbolo del sistema de Visual Studio.NET. varios hay que separarlos entre s con caracteres de coma (,) punto y coma (;)

La forma ms bsica de ejecutar al compilador consiste en pasarle como argumentos los Como es lgico, en principio los <valores> indicados no pueden incluir caracteres
nombres de los fuentes a compilar, caso en que intentara generar en el directorio desde de espacio ya que stos se interpretaran como separadores de argumentos en la
el que se le llame un ejecutable a partir de ellos con el mismo nombre que el primero de llamada a csc. Sin embargo, lo que s se permite es incluirlos si previamente se les
los fuentes indicados y extensin .exe Por ejemplo, ante una llamada como: encierra entre comillas dobles ()

csc FuenteA.cs FuenteB.cs FuenteC.cs Obviamente, como las comillas dobles tambin tiene un significado especial en los
argumentos de csc tampoco ser posible incluirlas directamente como carcter en
El compilador intentar generar un fuente FuenteA.exe en el directorio desde el que se <valores>. En este caso, para solventar esto lo que se hace es interpretarlas como
lo llam cuyo cdigo sea el resultante de compilar FuenteA.cs, FuenteB.cs y FuenteC.cs caracteres normales si van precedidas de \ y con su significado especial si no.
Obviamente, para que ello sea posible el compilador habr de disponer de permiso de
escritura y espacio suficiente en dicho directorio y adems alguno de los fuentes De nuevo, esto lleva al problema de que el significado de \ si precede a tambin
indicados tendr que disponer de un punto de entrada vlido. puede ser especial, y para solucionarlo lo ahora que se hace es incluirlo duplicado
(\\) si aparece precediendo a un pero no se desea que tome su significado especial.
Este comportamiento por defecto puede variarse especificando en la llamada a csc
opciones de compilacin adicionales que sigan la sintaxis: Ejemplos equivalentes de cmo compilar dando valores a una opcin /r son:
16
El nombre de la carpeta v2.0.40607 segn la versin del SDK que utilice. En concreto, el valor que csc /r:Lib.dll /r:Lib2.dll Fuente.cs
aqu se indica corresponde a la de la Beta 1 del .NET Framework SDK 2.0. csc /r:Lib1.dll,Lib2.dll Fuente.cs

Jos Antonio Gonzlez Seco Pgina 247 Jos Antonio Gonzlez Seco Pgina 248
El lenguaje de programacin C# Tema 20: El compilador de C# de Microsoft El lenguaje de programacin C# Tema 20: El compilador de C# de Microsoft

csc /r:Lib1.dll;Lib3.dll Fuente.cs


Tanto las libreras como los ejecutables son simples colecciones de tipos de datos
Aunque en los ejemplos mostrados siempre se han incluido las opciones antes que los compilados. La nica diferencia entre ellos es que los segundos disponen de un
nombres de los fuentes a compilar, en realidad ello no tiene porqu ser as y se pueden mtodo especial (Main()) que sirve de punto de entrada a partir del que puede
mezclar libremente y en cualquier orden opciones y nombres de fuentes a compilar ejecutarse cdigo usando los mecanismos ofrecidos por el sistema operativo
(salvo excepciones que en su momento se explicarn) (escribiendo su nombre en la lnea de comandos, seleccionndolo grficamente, etc.)

La diferencia de un mdulo con los anteriores tipos de ficheros es que ste no forma
Opciones de compilacin parte de ningn ensamblado mientras que los primeros s. El CLR no puede trabajar
con mdulos porque estos carecen de manifiesto, pero crearlos permite disponer de
Una vez explicado cmo utilizar el compilador en lneas generales es el momento cdigo compilado que pueda aadirse a ensamblados que se generen posteriormente
propicio para pasar a explicar cules son en concreto las opciones que admite. Esto se y que podrn acceder a sus miembros internal.
har desglosndolas en diferentes categoras segn su utilidad.
/main: Si al compilar un ejecutable hubiese ms de un punto de entrada vlido entre
Antes de empezar es preciso comentar que la mayora de estas opciones disponen de dos los tipos definidos en los fuentes a compilar se ha de indicar como valor de esta
nombres diferentes: un nombre largo que permite deducir con facilidad su utilidad y un opcin cual es el nombre del tipo que incluye la definicin del Main() a utilizar, pues
nombre corto menos claro pero que permite especificarlas ms abreviadamente. Cuando si no el compilador no sabra con cal de todas quedarse.
se haga referencia por primera vez a cada opcin se utilizar su nombre largo y entre
parntesis se indicar su nombre corto justo a continuacin. El resto de referencias a Como es lgico, lo que nunca puede hacerse es definir ms de un punto de entrada
cada opcin se harn usando indistintamente uno u otro de sus nombres. en un mismo tipo de dato, pues entonces ni siquiera a travs de la opcin /main
podra resolverse la ambigedad.
Opciones bsicas /out (/o): Por defecto el resultado de la compilacin de un ejecutable es un fichero
.exe con el nombre del fuente compilado que contenga el punto de entrada, y el de
En este epgrafe se explicarn todas aquellas opciones que suelen usarse con mayor la compilacin de un mdulo o librera es un fichero con el nombre del primero de
frecuencia a la hora de compilar aplicaciones. Como la mayora ya se explicaron en los fuentes a compilar indicados y extensin dependiente del tipo de fichero
detalle en el Tema 2: Introduccin a C#, dichas opciones aqu simplemente se resumen: generado (.netmodule para mdulos y .dll para libreras) Si se desea darle otro
nombre basta indicarlo como valor de esta opcin.
/recurse: Si en vez de indicar el nombre de cada fichero a compilar como se ha
dicho se indica como valor de esta opcin se consigue que si el compilador no lo El valor que se le d ha de incluir la extensin del fichero a generar, lo que permite
encuentra en la ruta indicada lo busque en los subdirectorios de la misma. compilar ficheros con extensiones diferentes a las de su tipo. Por ejemplo, para crear
un mdulo A.exe a partir de un fuente A.cs puede hacerse:
Por ejemplo, la siguiente llamada indica que se desea compilar el fichero fuente.cs
csc /out:A.exe /t:module A.cs
ubicado dentro del directorio c:\Mis Documentos o algn subdirectorio suyo:
csc /recurse:Mis Documentos\fuente.cs Obviamente, aunque tenga extensin .exe el fichero generado ser un mdulo y no
un ejecutable, por lo que si se intenta ejecutarlo se producir un error informando de
/target (/t): Por defecto al compilar se genera un ejecutable cuya ejecucin que no es un ejecutable vlido. Como puede deducirse, cambiar la extensin de los
provoca la apertura de una ventana de consola si al lanzarlo no hubiese ninguna ficheros generados no suele ser til y slo podra venir bien para dificultar aposta la
abierta. Esto puede cambiarse dando uno de los valores indicados en la Tabla 15 a comprensin del funcionamiento de una aplicacin o para identificar ensamblados
esta opcin: con algn significado o contenido especial.

Valor Tipo de fichero a generar /reference (/r): Por defecto slo se buscan definiciones de tipos de datos
exe ninguno Ejecutable con ventana de consola (valor por defecto) externas a los fuentes a compilar en la librera mscorlib.dll que forma parte de la
winexe Ejecutable sin ventana de consola. til para escribir BCL. Si alguno de los fuentes a compilar hace uso de tipos pblicos definidos en
aplicaciones de ventanas o sin interfaz otros ensamblados hay que indicar como valores de /r cules son esos ensamblados
library Librera para que tambin se busque en ellos.
module Mdulo de cdigo no perteneciente a ningn ensamblado
En mscorlib.dll se encuentran los tipos de uso ms frecuentes incluidos en la
Tabla 15: Valores admitidos por la opcin /t de csc BCL. En el poco frecuente caso de que haya definido su propia versin de ellos y no

Jos Antonio Gonzlez Seco Pgina 249 Jos Antonio Gonzlez Seco Pgina 250
El lenguaje de programacin C# Tema 20: El compilador de C# de Microsoft El lenguaje de programacin C# Tema 20: El compilador de C# de Microsoft

desee que se use la de la BCL, puede pasar al compilador el flag /nostdlib para
indicarle que no desea que busque implcitamente en mscorlib.dll. Sin embargo, al hacer as compilaciones mltiples hay que tener en cuenta que slo es
vlido solicitar que el primer grupo de ficheros indicado se compile como ensamblado.
Puede que termine descubriendo que en realidad tampoco hace falta referenciar a la Por tanto, sera incorrecto hacer:
mayora de las restantes libreras que forman la BCL. Pues bien, esto no se debe a
que tambin las referencie implcitamente el compilador, sino a que se incluyen en csc /t:module /out:ModB.netmodule B.cs /t:library /out:LibA.dll A.cs
un fichero de respuesta (ms adelante se explica lo que son este tipo de ficheros)
usado por defecto por el compilador. Si no desea que utilice este fichero puede Esta llamada es incorrecta porque indica que se desea que el segundo grupo de ficheros
pasarle el flag /noconfig. d lugar a un ensamblado y ello slo puede hacerse con el primero.

Por otro lado, tambin hay que tener en cuenta que no es vlido que un mismo tipo de
Cuando se den valores a /r hay que tener en cuenta que por defecto el compilador
dato se defina en varios de los grupos de ficheros indicados. Por ejemplo, si se quisiese
interpretar cada ruta as indicada de manera relativa respecto al directorio desde el
compilar A.cs como ejecutable y como mdulo podra pensarse en hacer:
que se le llame. Si no lo encuentra all lo har relativamente respecto al directorio
donde est instalado el CLR, que en los sistemas Windows es el subdirectorio csc A.cs /t:library A.cs
Microsoft.NET\Framework\v<versinClr> del directorio de instalacin de
Windows. Y si tampoco lo encuentra all la interpretar respecto a los directorios Sin embargo, esta llamada no es vlida porque los dos grupos de ficheros indicados
indicados por la variable de entorno LIB de su sistema operativo. contienen el mismo fichero y por tanto definiciones comunes de tipos de datos. La nica
solucin posible sera hacer dos llamadas por separado al compilador como:
Esta poltica de bsqueda puede modificarse incluyendo opciones /lib al llamar al
compilador cuyos valores le indiquen en qu directorios ha de buscar antes de pasar csc A.cs
csc /t:libary A.cs
a buscar en los indicados por la variable de entorno LIB.

/addmodule: Funciona de forma parecida a /r pero se utiliza cuando lo que usan los Manipulacin de recursos
fuentes son tipos definidos externamente en mdulos en vez de en ensamblados.
Incluso a la hora de buscar mdulos se sigue la misma poltica que al buscar
Los ficheros de recursos son archivos que no contienen cdigo sino slo datos tales
ensamblados y se admite el uso de /lib para modificarla. como cadenas de textos, imgenes, vdeos o sonidos. Su utilidad es facilitar el desacople
Se incluyen opciones /r y /addmodule separadas porque aadir un mdulo a una entre las aplicaciones y los datos concretos que usen, de modo que sea fcil reutilizarlos
en mltiples aplicaciones, modificarlos sin tener que recompilar los fuentes y desarrollar
compilacin implica decir que se desea que los tipos que incluye formen parte del
diferentes versiones de cada aplicacin en las que slo varen dichos datos.
ensamblado a generar, por lo que los fuentes a compilar podrn acceder a sus
miembros internal. Sin embargo, cuando se referencia a otros ensamblados con /r
Estos ficheros son especialmente tiles al hora de internacionalizar aplicaciones, pues si
esto no ocurre y los fuentes compilados no podrn acceder a sus miembros internal.
se dejan todos los datos que se utilicen en ficheros de recursos independientes del
cdigo, a la hora de crear nuevas versiones en otros idiomas slo ser necesario cambiar
Es importante sealar que el CLR espera que todos los mdulos que se aadan a un
los ficheros de recursos y habr que tocar para nada el cdigo.
ensamblado se distribuyan dentro del mismo directorio que la librera o ejecutable
correspondiente al mismo. Si no se hiciese as no los podra localizar y en tiempo de
El objetivo de este tema no es explicar cmo crear y acceder a ficheros de recursos, sino
ejecucin se producira una System.TypeLoadException si se intentase acceder a los
explicar el significado de las opciones de compilacin relacionadas con ellos. Si desea
tipos definidos en ellos.
aprender ms sobre recursos puede comenzar buscando en el apartado Visual
Studio.NET .NET Framework .NET Framework Tutorials Resources
Aunque en principio se ha dicho que no importa cmo se intercalen opciones y
and Localization Using the .NET Framework SDK de la ayuda del SDK.
nombres de fuentes entre los argumentos pasados a csc, hay una excepcin que consiste
en que /out y /r siempre han de indicarse antes de algn fuente. Esto permite que en Lo que s es importante es sealar que aunque en la plataforma .NET pueden crearse
una misma llamada al compilador sea posible solicitar la generacin de un ensamblado ficheros de recursos tanto en formato .txt como .resx, el compilador de C# slo los
y mltiples mdulos de cdigo, pues se considera que cada aparicin de las opciones
admite si estn compilados en formato .resources. Para ello, en el SDK se incluye una
anteriores hace referencia slo a los fuentes que le siguen. Por ejemplo, dada:
utilidad llamad resgen.exe que permite compilar en dicho formato ficheros de recursos
csc /t:library /out:LibA.dll A.cs /t:module /out:ModB.netmodule B.cs escritos en cualquiera de los formatos anteriores con slo pasrselos como argumentos.
Por ejemplo, si se le llama as:
Esta llamada provocar la compilacin de A.cs como librera de nombre LibA.dll y la
resgen misrecursos.resx
de B.cs como mdulo llamado ModB.netmodule.

Jos Antonio Gonzlez Seco Pgina 251 Jos Antonio Gonzlez Seco Pgina 252
El lenguaje de programacin C# Tema 20: El compilador de C# de Microsoft El lenguaje de programacin C# Tema 20: El compilador de C# de Microsoft

Suponiendo que el contenido de misrecursos.resx sea el de un fichero .resx vlido, ej.cs(7,3): error CS0117: 'A' does not contain a definition for 'K'
tras esta llamada se habr generado en el directorio desde el que se le llam un fichero
misrecursos.resources con el contenido de misrecursos.resx. Ntese que del fichero slo se da su nombre y ello podra no identificarlo unvocamente
si se compilaron a la vez varios con el mismo nombre pero pertenecientes a directorios
Para aadir este fichero al ensamblado resultante de una compilacin se puede utilizar la diferentes. Para solucionar esto puede usarse la opcin /fullpaths, con lo que de los
opcin /linkresource (/linkres) As por ejemplo, para crear un ensamblado mensajes de error incluiran siempre la ruta completa de los ficheros defectuosos. Por
fuente1.dll formado por el cdigo resultante de compilar fuente1.cs y los recursos ejemplo, si el fichero del ejemplo anterior se encontraba en C:\Ejemplo, al compilarlo
de misrecursos.resources podra compilarse con: con esta opcin se mostrara el mensaje de error as:

csc /t:library fuente1.cs /linkres:misrescursos.resources C:\Ejemplo\ej.cs(7,3): error CS0117: 'A' does not contain a definition
for 'K'
De este modo el fichero de recursos formar parte del ensamblado generado pero
permanecer en un fichero separado de fuente1.dll. Si se desease incrustarlo en l Hay veces que el compilador detecta que se han escrito en el fuente ciertas secciones de
habra que haber compilado con la opcin /resource (/res) en vez de /linkres tal y tal manera que sin ser errneas son cuanto menos sospechosas (ya sea por ser absurdas,
como se muestra a continuacin: por prestarse a confusin, etc), y en esos casos lo que hace es emitir mensajes de aviso.
Por ejemplo, si en la definicin del tipo A del fuente prueba.cs se hubiese incluido:
csc /t:library fuente1.cs /res:misrescursos.resources
static void Main(int x)
Desde cdigo podr accederse a estos recursos por medio de los servicios de la clase {}
ResourceManager del espacio de nombres System.Resources si fueron generados con
resten, o con los mtodos GetManifestResource() de la clase Assembly del espacio de En principio es una definicin de mtodo perfectamente vlida. Sin embargo, como se
nombres System.Reflection. Para hacer referencia a cada uno se usara en principio su parece mucho a una definicin de punto de entrada pero no es vlida como tal, el
nombre de fichero, aunque /res y /linkres permite que tras la ruta de ste se indique compilador generar el mensaje de aviso que sigue para informar de ello al usuario por
separado por una coma cualquier otro identificador a asociarle. Por ejemplo: si acaso ste lo que quera hacer era definir un punto de entrada y se equivoc:
prueba.cs(7,14): warning CS0028: 'A.Main(int)' has the wrong signature
csc /t:library fuente1.cs /res:misrescursos.resources,recursos to be an entry point

Como un tipo especial de recurso que comnmente suele incrustarse en los ejecutables Como se ve, la estructura de los mensajes de aviso es muy similar a la de los mensajes
de los programas es el icono (fichero grfico en formato .ico) con el que desde las de error y slo se diferencia de sta en que incluye warning en vez de error tras el
interfaces grficas de los sistemas operativos se les representar, csc ofrece una opcin indicador de posicin en el fuente. Incluso como a estos, la opcin /fullpaths
especfica llamada /win32icon en cuyo valor puede indicrsele el icono a incrustar: tambin les afecta y provoca que se muestren las rutas de los fuentes al completo.
csc programa.cs /win32icon:programa.ico
Una diferencia importante entre avisos y errores es que la aparicin de mensajes de los
En realidad hay que recordar el uso de ficheros de recursos no es un aspecto introducido segundos durante la compilacin aborta la generacin del binario, mientras que la
en la plataforma .NET sino disponible desde hace tiempo en la plataforma Windows en aparicin de los primeros no (aunque en ambos casos nunca se aborta la compilacin
forma de ficheros .res. Por compatibilidad con este antiguo formato de recursos, csc sino que tras mostrarlos se sigue analizando los fuentes por si pudiesen detectarse ms
incorpora una opcin /win32res que permite incrustarlos de igual forma a como /res errores y avisos) Ahora bien, tambin puede forzarse a que ello ocurra con los de aviso
incrusta los novedosos ficheros .resources. pasando al compilador el flag /warnaserror, con lo que se conseguira que todo
mensaje de aviso se mostrase como error. Ello puede resultar til porque fuerza a
En cualquier caso, hay que sealar que siempre que se aada un fichero de recursos a un escribir los fuentes de la manera ms fiable e inteligentemente posible.
ensamblado la visibilidad que se considerar para los recursos que incluya es public.
En el lado opuesto, puede que haya ciertos tipos de mensajes de aviso de los que no se
desea siquiera que se informe en tanto que la informacin que aportan ya se conoce y se
Configuracin de mensajes de avisos y errores sabe que no afectar negativamente al programa. En esos casos puede usarse la opcin
/nowarn indicando como valores suyos los cdigos asociados a los mensaje de aviso
Cada vez que el compilador detecta algn error en uno de los fuentes a compilar genera que no se desea que se reporten. El cdigo asociado a cada tipo de mensaje de aviso es
un mensaje informando de ello en el que indica en qu fichero de cdigo fuente y en la palabra de la forma CS<cdigo> que se muestra tras warning en el mensaje de aviso.
qu posicin exacta del mismo (lnea y columna) lo ha detectado. Por ejemplo, si en la As, para compilar el prueba.cs del ejemplo anterior sin que se genere el mensaje de
columna 3 de la lnea 7 de un fuente llamado ej.cs se llama a un mtodo con nombre aviso arriba mostrado puede hacerse:
completo A.K() inexistente, se mostrar un mensaje como: csc prueba.cs /nowarn:0028

Jos Antonio Gonzlez Seco Pgina 253 Jos Antonio Gonzlez Seco Pgina 254
El lenguaje de programacin C# Tema 20: El compilador de C# de Microsoft El lenguaje de programacin C# Tema 20: El compilador de C# de Microsoft

Al compilar, por defecto el compilador siempre lee un fichero de respuesta llamado


En realidad los ceros incluidos a la izquierda del cdigo del aviso en los mensajes de csc.rsp ubicado en el directorio del CLR que almacena referencias a todos los
aviso son opcionales, por lo que la compilacin anterior es equivalente a: ensamblados predefinidos. Por tanto, para entender la sintaxis a seguir para escribir este
csc prueba.cs /nowarn:28
tipo de ficheros nada mejor que ver cul es el contenido de este csc.rsp y de paso
saber cules son las opciones que por defecto se aadirn a toda compilacin:
Cada versin del compilador ignorar por compatibilidad las supresiones de avisos
existentes en anteriores versiones del mismo pero eliminados en las nuevas, aunque si # This file contains command-line options that the C#
# command line compiler (CSC) will process as part
se les especifican cdigos no vlidos para ninguna versin emitirn un mensaje de error. # of every compilation, unless the "/noconfig" option
# is specified.
Si desea obtener la lista completa de todos los tipos de mensaje de aviso y error con sus
respectivos cdigos puede consultar dentro de la documentacin del .NET Framework # Reference the common Framework libraries
SDK en Visual Studio.NET Visual Basic and Visual C# Visual C# /r:Accessibility.dll
Language C# Compiler Options Compiler Errors CS0001 to CS9999 /r:Microsoft.Vsa.dll
/r:System.Configuration.Install.dll
/r:System.Data.dll
Si en lugar de desactivar ciertos tipos de avisos uno por uno desea desactivarlos por /r:System.Design.dll
grupos segn su severidad, entonces puede hacerlo a travs de la opcin /warn (o /w) /r:System.DirectoryServices.dll
Esta opcin toma como valor un nmero comprendido entre 0 y 4 que indica cul es el /r:System.dll
nivel de avisos con el que se desea trabajar. Por defecto ste vale 4, lo que significa que /r:System.Drawing.Design.dll
/r:System.Drawing.dll
se mostrarn todos los avisos, pero puede drsele cualquiera de los de la Tabla 16: /r:System.EnterpriseServices.dll
/r:System.Management.dll
Nivel de aviso Avisos mostrados /r:System.Messaging.dll
0 Ninguno /r:System.Runtime.Remoting.dll
1 /r:System.Runtime.Serialization.Formatters.Soap.dll
Slo los ms graves
/r:System.Security.dll
2 Los ms graves y algunos menos graves como por ejemplo los /r:System.ServiceProcess.dll
relativos a ocultaciones de miembros /r:System.Web.dll
3 Los de nivel 2 ms algunos poco graves como los relativos al uso /r:System.Web.RegularExpressions.dll
de expresiones absurdas que siempre produzcan el mismo resultado /r:System.Web.Services.dll
4 /r:System.Windows.Forms.Dll
Todos. Es lo que se toma por defecto /r:System.XML.dll
Tabla 16: Niveles de mensajes de aviso
Del contenido de este fichero es fcil deducir que la estructura de los ficheros de
respuesta es sencilla: las opciones a pasar al compilador se indican en el miso tal cuales
Si est interesado en conocer en concreto el nivel de algn tipo de aviso puede remitirse y pueden incluirse comentarios como lneas que comiencen en #. Adems, como puede
a la descripcin sobre el mismo incluida en la documentacin del SDK antes comentada verse, el fichero de respuesta usado por defecto aade referencias a las libreras de la
BCL de uso ms comn, lo que evita tener que incluirlas constantemente al compilar.
Ficheros de respuesta Tras tomar las opciones de este fichero, el compilador mira si en el directorio desde el
que se le llama hay otro csc.rsp y si es as toma sus opciones. Si por alguna razn no
La lnea de comandos no es la nica forma de pasar informacin al compilador (tanto nos interesase que se tomasen las opciones de dichos ficheros (por ejemplo, para usar
ficheros a compilar como opciones de compilacin), sino que tambin es posible nuevas versiones de tipos incluidos en las libreras que referencian) bastara pasar el
almacenar informacin de este tipo en un fichero y pasrsele al compilador como flag /noconfig al compilar para desactivar esta bsqueda por defecto en ellos, aunque
argumento solamente dicho fichero y no toda la informacin en l contenida. De este hay que sealar que este flag no admite los sufijos + y admitidos por el resto de flags.
modo se facilitara la labor de pasar como parmetros las opciones de uso ms frecuente
ya que bastara slo indicar cul es el nombre de un fichero que las especifica. Al escribir ficheros de respuesta hay que tener cuidado con dos cosas: no es posible
cortar las opciones o nombres de fichero con retornos de carro que provoquen que
A este ficheros se les llama ficheros de respuesta, ya que al pasrselos al compilador ocupen varias lneas; y las opciones son pasadas al compilador en el mismo orden en
su contenido puede verse como la respuesta a cules son los argumentos a usar durante que aparezcan en el fuente, por lo que hay que tener cuidado con cmo se coloquen las
la compilacin. La extensin de estos ficheros suele ser .rsp, y aunque nada obliga a opciones /out y /t por la ya comentada importancia del orden de especificacin.
drsela es conveniente hacerlo como ocurre con todo convenio.
Una vez escrito un fichero de respuesta, para indicar al compilador que ha de usarlo
basta pasrselo como un nombre de fuente ms pero precediendo su nombre del sufijo

Jos Antonio Gonzlez Seco Pgina 255 Jos Antonio Gonzlez Seco Pgina 256
El lenguaje de programacin C# Tema 20: El compilador de C# de Microsoft El lenguaje de programacin C# Tema 20: El compilador de C# de Microsoft

@. Por ejemplo, para compilar A.cs usando las opciones almacenadas en opc.rsp Unhandled Exception: System.Exception: Exception of type System.Exception was thrown.
habra que llamar al compilador con: at A.Main()

csc @opc.rsp A.rsp Sin embargo, si lo compila con:


csc A.cs /debug
Tambin sera posible indicar mltiples ficheros de respuesta, caso en que se tomaran
las opciones de cada uno en el mismo orden en que apareciesen en la llamada a csc. Por Al ejecutarlo se obtendr un mensaje mucho ms detallado en el que se indicar cul es
ejemplo, para compilar A.rsp tomando las opciones de opc1.rsp y luego las de la lnea exacta del cdigo fuente durante cuya ejecucin se produjo la excepcin:
opc2.rsp podra llamarse al compilador con:
Unhandled Exception: System.Exception: Exception of type System.Exception was thrown
csc @opc1.rsp @opc2.rsp A.rsp at A.Main() in E:\c#\Ej\A.cs:line 5

Puede ocurrir que las opciones indicadas en un fichero de respuesta contradigan a Como es fcil deducir, a partir de esta informacin es fcil crear herramientas de
opciones indicadas en otro fichero de respuesta indicado a continuacin o a opciones depuracin -como el depurador de Visual Studio.NET o el CLR Debugger del SDK-
dadas al compilador en la lnea de comandos. Para resolver estas ambigedades el que muestren la lnea exacta del cdigo fuente donde se produjo la excepcin lanzada; y
compilador siempre va procesando los argumentos que se le pasen de izquierda a obviamente estos datos tambin pueden tener muchos otros usos, como permitir ejecutar
derecha y se queda con la ltima especificacin dada a cada opcin. As, en el ejemplo paso a paso los programas mostrando en cada momento cul es la lnea del fuente que se
anterior las opciones del csc.rsp del directorio desde el que se le llam si existiese- ejecutar a continuacin y cosas similares.
tendra preferencia sobre las del csc.rsp del directorio del CLR, las de opc2.rsp
tendran preferencia sobre las de ste, y las de opc1.rsp sobre las de opc2.rsp. Tambin puede usarse /debug como opcin con argumentos en vez de cmo flag, lo
que permite generar una versin recortada de la informacin de depuracin. Si de esta
Tambin pueden incluirse en los ficheros de respuesta opciones @ que incluyan a otros forma se le da el valor full funcionar exactamente igual que al activarla como flag,
ficheros de respuesta, con lo que se tomara sus opciones antes de continuar tomando las pero si se le da el valor pdbonly entonces la informacin de depuracin generada slo
siguientes del fichero que lo incluy, aunque obviamente nunca se admitir que un estar disponible para los depuradores desde los que se haya lanzado la aplicacin, pero
fichero incluido sea el mismo que el que lo incluye o que alguno que incluya a ste, no para los que se le hayan adjuntado dinmicamente una vez lanzada.
pues entonces se formaran ciclos y nunca acabara la bsqueda de opciones.
Por ltimo, respecto a la depuracin de aplicaciones conviene sealar que por defecto el
compilador siempre intenta generar el cdigo lo ms rpidamente posible para facilitar
Opciones de depuracin el desarrollo de aplicaciones, ya que mientras se depuran suele ser necesario realizarles
muchas recompilaciones. No obstante, una vez finalizada la depuracin suele convenir
Sin duda la opcin de depuracin ms importante es el flag /debug, cuya inclusin activar la realizacin de optimizaciones por parte del compilador en el espacio y tiempo
indica al compilador que ha de generar un fichero .pdb con informacin sobre la de ejecucin consumido por el MSIL pasndole la opcin /optimize+ (/o+)
relacin entre el fichero binario generado y las lneas de los fuentes a partir de los que
se gener. Esta informacin es muy til para depurar aplicaciones, pues permite mostrar
la instruccin de cdigo fuente que produjo las excepciones en lugar de mostrar las Compilacin incremental
instrucciones de cdigo nativo en que fue traducida.
La compilacin incremental consiste en slo recompilar en cada compilacin que se
Para entender mejor la utilidad de este fichero .pdb puede escribir el programa: haga de un proyecto aquellos mtodos cuya definicin haya cambiado respecto a la
ltima compilacin realizada, con lo que el proyecto podra compilarse ms rpido que
class A haciendo una compilacin completa normal.
{
public static void Main()
{throw new System.Exception();}
Para que esto sea posible hacerlo hay que llamar al compilador con el flag
} /incremental (/incr), lo que provocar la generacin de un fichero adicional con el
mismo nombre que el binario generado ms una extensin .incr. Por ejemplo, dado:
Si lo compila con:
csc /out:fuente.exe /incremental Fuente.cs
csc A.cs
Se generar un ejecutable fuente.exe y un fichero adicional fuente.exe.incr.
Al ejecutarlo se producir una excepcin y surgir una ventana de seleccin de Aunque pueda parecer redundante incluir en el ejemplo la opcin /out al llamar al
depurador. Si pulsa No en ella ver en la consola un mensaje como el siguiente: compilador, es necesaria porque al menos en la actual versin del compilador es
obligatorio especificarla siempre que se utilice /incr.

Jos Antonio Gonzlez Seco Pgina 257 Jos Antonio Gonzlez Seco Pgina 258
El lenguaje de programacin C# Tema 20: El compilador de C# de Microsoft El lenguaje de programacin C# Tema 20: El compilador de C# de Microsoft

/unsafe: En el Tema 18: Cdigo inseguro ya se explic que la nica utilidad de esta
El fichero .incr generado incluye informacin sobre la compilacin que permitir que opcin es servir al compilador de mecanismo de seguridad gracias al que pueda
posteriores compilaciones que se realicen con /incr activado puedan hacerse de asegurarse de que el usuario sabe lo que hace al compilar cdigo con punteros.
manera incremental. Obviamente, si este fichero se elimina ser reconstruido en la
siguiente compilacin que se haga con /incr, pero dicha compilacin no se realizar de /doc: Esta opcin ya se introdujo en el Tema 19: Documentacin XML, donde se
manera completa por no disponerse del fichero .incr durante ella. explic que se usa para indicar al compilador que es desea generar un fichero XML
con el contenido de los comentarios de documentacin incluidos en los fuentes a
Sin embargo, el hecho de que est disponible un fichero .incr al compilar un proyecto compilar. El nombre de ese fichero ser el que se d como valor a esta opcin.
no implica que se use, pues el compilador puede ignorarlo y realizar una compilacin
completa si detecta que han cambiado las opciones de compilacin especificadas o si Al usar esta opcin hay que tener en cuenta una cosa, y es que para optimizar el
detecta que los fuentes han cambiado tanto que es al menos igual de eficiente hacerla as tiempo que se tarda en realizar compilaciones incrementales, durante ellas esta
que de manera incremental. opcin es ignorada. Por tanto, no tiene mucho sentido combinar /doc y /incr.

En realidad no es bueno hacer siempre las compilaciones incrementalmente sino que


slo es til hacerlo en proyectos formados por mltiples fuentes de pequeo tamao, Otras opciones
mientras que en proyectos con pocos y grandes ficheros se gana poco o nada en tiempo
de compilacin. Adems, los ejecutables generados incrementalmente pueden ocupar Aparte de las opciones comentadas, csc admite unas cuantas ms an no descritas ya
ms que los generados por compilacin completa, por lo slo es recomendable compilar sea porque su uso es muy poco frecuente o porque no encajan correctamente en ninguno
incrementalmente las versiones de prueba de los proyectos pero no las definitivas. de los subepgrafes tratados. Todas estas opciones se recogen finalmente aqu:

/filealign: Los valores dados a esta opcin indican el tamao de las secciones en
Opciones relativas al lenguaje que se dividirn los ficheros binarios resultantes de la compilacin. Puede tomar los
valores 512, 1024, 2048, 4096 8192, y cada seccin en los binarios comenzar en
A lo largo de los anteriores temas se ha ido diseminando diversas opciones de una posicin que sea mltiplo del valor dado a esta opcin.
compilacin relacionadas de manera ms o menos directa con el lenguaje C#. En este
punto haremos recapitulacin de todas ellas mismas y las resumiremos: Por defecto el valor que se le d puede variar dependiendo de la implementacin que
se haga del CLR, aunque darle un valor a medida puede ser til en el diseo de
/define (/d): En el Tema 3: El preprocesador ya se introdujo esta opcin cuyos aplicaciones para dispositivos empotrados con escasa capacidad de almacenamiento
valores recordemos que se utilizan para introducir definiciones de smbolos de ya que puede reducir el tamao de los ficheros generados.
preprocesado al principio de todos los fuentes a compilar.
/bugreport: Dado que es muy difcil disear un compilador 100% libre de errores,
Por ejemplo, si se desea compilar los fuentes A.cs y B.cs como si al principio de Microsoft proporciona a travs de esta opcin un mecanismo que facilita a los
ellos se hubiese incluido las directivas de preprocesado #define PRUEBA y #define usuarios el envo de informacin sobre los errores que descubran en el mismo y
VERSION1 podra llamarse al compilador con: facilita a Microsoft la labor de interpretarla para solucionarlos lo antes posible.

csc /d:PRUEBA;VERSION1 A.cs B.cs El valor que se d a esta opcin es el nombre de con el que se desea que se genere el
fichero con la informacin relativa al error descubierto durante la compilacin. En
/checked: En los temas 4 y 16 se explic que todo desbordamiento que ocurra en dicho fichero csc insertar automticamente la siguiente informacin:
operaciones aritmticas entre variables enteras es tratado por defecto truncando el
resultado. Pues bien, la utilidad de activar esta opcin es precisamente forzar a que Opciones de compilacin utilizadas.
se incluyan en el cdigo generado las comprobaciones necesarias para que en caso Versin del compilador, CLR y sistema operativo usado.
de desbordamiento se lance en su lugar una System.OverflowException. Copia de todos los cdigos fuentes compilados. Como es lgico, para facilitar la
correccin a Microsoft se recomienda enviar el programa ms compacto posible
Obviamente el cdigo compilado con /checked se ejecutar ms lento que el que lo en el que se produzca el error descubierto.
haga sin ella ya que incluir comprobaciones de desbordamiento adicionales. Sin Contenido en hexadecimal de los mdulos y ensamblados no predefinidos a los
embargo, a cambio con ello se consigue detectar con facilidad errores derivados de que se ha referenciado durante la compilacin.
desbordamientos que de otra manera podran pasar inadvertidos. Mensajes de salida mostrados durante la compilacin.

Aparte de toda esta informacin insertada automticamente por el compilador,


durante la generacin del fichero de error tambin se pedir al usuario que indique

Jos Antonio Gonzlez Seco Pgina 259 Jos Antonio Gonzlez Seco Pgina 260
El lenguaje de programacin C# Tema 20: El compilador de C# de Microsoft El lenguaje de programacin C# Tema 20: El compilador de C# de Microsoft

una pequea descripcin sobre el error detectado y cmo cree que podra enteros. Sin embargo, hay que tener en cuenta que los bits menos significativos
solucionarse. Dicha informacin tambin ser aadida de manera automtica al de esta direccin pueden ser redondeados. Por ejemplo, si escribimos:
fichero de error que se cree.
csc fichero.cs /baseaddress:0x11110001
Un ejemplo cmo generar informacin relativa a un error verdico que se produce al
El compilador tratar esta llamada tal y como si se le hubiese pasado:
compilar un programa error.cs con la Beta 1 del .NET SDK Framework es:
csc fichero.cs /baseaddress:0x11110000
csc error.cs /bugreport:ErrorUsing.cs
Si no se da valor a esta opcin, las libreras se instalarn en el rea de memoria
Tras contestar a las preguntas que el compilador har al usuario sobre el error que se estime conveniente en cada implementacin del CLR.
encontrado, el contenido del fichero generado es el siguiente:
### C# Compiler Defect Report, created 07/12/00 20:14:36 /codepage: Por defecto el compilador acepta fuentes escritos en Unicode, UTF-
### Compiler version: 7.00.9030 8 o usando la pgina de cdigos por defecto del sistema operativo. Si se desea
### Common Language Runtime version: 1.00.2914.16 compilar fuentes escritos en otras pginas de cdigo hay que indicar como valor
### Operating System: Windows NT 5.0.2195 Service Pack 2 de esta opcin el identificador de ella.
### User Name: Administrador
### Compiler command line
csc.exe error.cs /bugreport:ErrorUsing.cs Un uso tpico de esta opcin es permitir compilar fuentes escritos en espaol con
### Source file: 'e:\c#\ej\error.cs' un editor de textos de MS-DOS (como edit.com), caso en que hay que darle el
using System; valor 437 para que acepte los caracteres especiales tales como acentos o ees.
public class R1:IDisposable
{
/utf8output: Su inclusin indica que el compilador ha de mostrar los mensajes
public static void Main() usando el juego de caracteres UTF-8, lo que es til cuando se utilizan ciertos
{ sistemas operativos internacionales en los que por defecto no se muestren
using (R1 r1 = new R1()) correctamente dichos mensajes por la ventana de consola.
{
}
}
Para poder leerla en esos casos se recomienda usar este flag al compilar y
redirigir la salida a un fichero como muestra el siguiente ejemplo donde se
public void Dispose() compila A.cs redirigiendo los mensajes de compilacin a salida.txt y
{} mostrndolos en UTF-8:
}
### Compiler output csc A.cs /utf8output > salida.txt
error.cs(7,3): error CS1513: } expected
error.cs(7,26): error CS1002: ; expected /help (/?): Muestra un mensaje de ayuda resumiendo cules son las opciones
error.cs(12,9): error CS1518: Expected class, delegate, enum, interface, or struct
error.cs(14,1): error CS1022: Type or namespace definition, or end-of-file expected admitidas por el compilador y para qu sirven. Toda opcin o fichero a compilar
### User description especificado junto opcin son totalmente ignorados.
No detecta la instruccion using
/nologo: Indica que no se desea que al ejecutar el compilador se genere el
### User suggested correct behavior mensaje que incluye informacin sobre la versin del compilador y el copyright
Posiblemente no haya sido implementada en esta version del compilador
de Microsoft sobre el mismo que por defecto se muestra.
Ntese que aunque el error detectado en el ejemplo es verdico, en versiones del
compilador posteriores a la Beta 1 no se produce porque ya fue corregido. Suele usarse cuando la compilacin se solicita desde una aplicacin o fichero de
procesamiento por lotes, pues oculta la ejecucin del compilador al usuario y
/baseaddress: Esta opcin slo tiene sentido cuando se solicita la generacin
ello puede venir bien para evitar que ste conozca cmo funciona la aplicacin o
de una librera e indica cul es la direccin de memoria en que se prefiere que para conseguir un funcionamiento ms elegante y transparente de la misma.
sta se cargue cuando sea enlazada dinmicamente. Ntese que se ha dicho
librera, pues si el fichero generado es de cualquier otro tipo ser ignorada.
Acceso al compilador desde Visual Studio.NET
El valor que se d a esta opcin puede indicarse tanto en hexadecimal como en
octal o decimal siguiendo las reglas usadas en C# para la escritura de literales Como se explic en su momento en el Tema 2: Introduccin a C#, a las opciones de
compilacin de un proyecto se accede desde VS.NET a travs de las pginas de

Jos Antonio Gonzlez Seco Pgina 261 Jos Antonio Gonzlez Seco Pgina 262
El lenguaje de programacin C# Tema 20: El compilador de C# de Microsoft El lenguaje de programacin C# Tema 20: El compilador de C# de Microsoft

propiedades del mismo, las cuales tiene el aspecto mostrado en la Ilustracin 9 y se /win32icon Common Properties General Application Icon
obtienen seleccionando el proyecto en el Solution Explorer y pulsando sobre View Tabla 17: Controles asociados a opciones de compilacin
Property Pages en el men principal de Visual Studio.

Como puede observar, desde VS.NET no es posible acceder a muchas de las opciones
del compilador en lnea de comandos. En los casos de /codepage, /fullpaths, /lib,
/help, /nologo, /recurse y /utf8output esto es lgico ya que son opciones que
pierden su sentido desde dentro en una interfaz grfica. Hay otros casos en que ello se
debe a que se ofrecen desde el men principal de VS.NET otros mecanismos
alternativos para especificarlas, como son los indicados en la Tabla 18:

Opcin Mecanismo de acceso


/bugreport Help Customer Feedback
/resource Aadir el recurso a la solucin a travs de Project Add Existing
Item. Configurarle en su ventana de propiedades la propiedad Build
Action con el valor Embedded Resource.
/reference Aadir la referencia a la solucin con Project Add Reference

Tabla 18: Acceso a opciones fuera de las pginas de propiedades

Finalmente, queda un grupo de opciones que no estn disponibles simplemente porque


la implementacin de actual de VS.NET no las contempla. Son @, /linkresource,
/noconfig, /nowarn y /win32res. As mismo, el valor module de /t tampoco es
Ilustracin 9: Pginas de propiedades del proyecto en Visual Studio.NET soportado, por lo que VS.NET no permite trabajar con mdulos.

Para la mayora de opciones admitidas por csc.exe se incluye en estas pginas


controles tales como cajas de texto y listas desplegables que permiten configurarlas de
una manera visual, cmoda e intuitiva. En la Tabla 17 se resume en orden alfabtico
cul es el control que en concreto se asocia en estas pginas a cada opcin:

Opcin Control visual


/baseaddress Configuration Properties Advanced Base Address
/nostdlib Configuration Properties Advanced Do no use
mscorlib
/checked Configuration Properties Build Check for Arithmetic
Overflow/Underflow
/debug Configuration Properties Build Generate Debugging
Information
/define Configuration Properties Build Conditional
Compilation Constants
/doc Configuration Properties Build XML Documentation
File
/filealign Configuration Properties Build File Alignment
/incremental Configuration Properties Advanced Incremental Build
/main Common Properties General Startup Object
/optimize Configuration Properties Build Optimize code
/out Common Properties General Assembly Name
/target Common Properties General Output Type
/unsafe Configuration Properties Build Allow unsafe code
blocks
/warn Configuration Properties Build Warning Level blocks
/warnaserror Configuration Properties Build Treat Warnings As
Errors

Jos Antonio Gonzlez Seco Pgina 263 Jos Antonio Gonzlez Seco Pgina 264
El lenguaje de programacin C# Tema 21: Novedades de C# 2.0 El lenguaje de programacin C# Tema 21: Novedades de C# 2.0

En esta clase no se han concretando ni el tipo del campo privado valor ni el del nico
Tema 21: Novedades de C# 2.0 parmetro del mtodo EstablecerValor() En su lugar se le especificado un parmetro tipo
T que se concretar al utilizar la clase. Por ejemplo, al crear un objeto suyo:
Introduccin A<int> obj = new A<int>();

El 24 de Octubre de 2003 Microsoft hizo pblico el primer borrador de lo que sera la Esto creara un objeto de la clase genrica A con el parmetro tipo T concretizado con el
versin 2.0 del lenguaje C#, incluida en la nueva versin del .NET Framework conocida argumento tipo int. La primera vez que el CLR encuentre esta concretizacin de T a int
con el nombre clave Whidbey. En ella se introduca una importante novedad en el CLR realizar un proceso de expansin o instanciacin del genrico consistente en generar
consistente en proporcionar soporte para tipos genricos que se pudiesen usar como una nueva clase con el resultado de sustituir en la definicin genrica toda aparicin de
plantillas en base a la que definir otros tipos. Esto lgicamente implicaba que a los los parmetros tipos por los argumentos tipo. Para el ejemplo anterior esta clase sera:
lenguajes .NET de Microsoft en primer lugar, y presumiblemente el resto despus, se
les hiciesen tambin modificaciones orientadas a aprovechar esta nueva funcionalidad. public class A<int>
{
En este tema se explican las novedades para ello incluidas en la versin 2.0 de C#, as int valor;
public void EstablecerValor(int valor)
como otras novedades no directamente relacionadas con los genricos que tambin {
incorpora: los iteradores para facilitar la implementacin de las interfaces IEnumerable this.valor = valor;
e IEnumerator, los mtodos annimos y otros mecanismos destinados a facilitar el }
trabajo con los delegados, la capacidad de dividir las definiciones de las clases entre }
varios ficheros a travs de clases parciales, la posibilidad de asignar null a los tipos
valor a travs de los nuevos tipos valor anulables, etc. A los tipos con parmetros tipo, como A<T>, se les llama tipos genricos cerrados; a
los generados al concretrseles algn parmetro tipo se le llama tipos construidos; y a
En principio, las modificaciones introducidas en C# se han diseado con la idea de los generados al concretrseles todos tipos genricos abiertos. La relacin establecida
mantener el mximo nivel de compatibilidad con cdigos escritos para las anteriores entre ellos es similar a la establecida entre las clases normales y los objetos: al igual que
versiones del lenguaje versiones 1.X-. Por ello, las nuevas palabras con significado clases sirven de plantillas en base a las que crear objetos, los tipos genricos cerrados
especial introducidas (where, yield, etc.) no se han clasificado como reservadas, de actan como plantillas en base a las que crear tipos genricos abiertos. Por eso, en el
modo que seguirn siendo vlidos los identificadores que se hubiesen declarados con C++ tradicional se llamaba plantillas a las construcciones equivalentes a los genricos.
sus nombres. Slo se han introducido unas mnimas incompatibilidades relacionadas
con la sintaxis de los genricos que se describen en el epgrafe Ambigedades del tema. La expansin la hace el CLR en tiempo de ejecucin, a diferencia de lo que sucede en
otros entornos (pe, C++) en los que se realiza al compilar. Esto tiene varias ventajas:

Genricos Ensamblados ms pequeos: Como slo almacenan el tipo genrico cerrado, que
el CLR ya expandir en tiempo de ejecucin, su tamao es ms pequeo y se evita
Concepto el problema del excesivo inflado del cdigo binario generado (code bloat)

Adems, para evitar el inflado de la memoria consumida, el CLR reutiliza gran parte
C# 2.0 permite especificar los tipos utilizados en las definiciones de otros tipos de datos
del MSIL generado para la primera expansin de un genrico por un tipo referencia
y de mtodos de forma parametrizada, de manera que en vez de indicarse exactamente
en las siguientes expansiones del mismo por otros tipos referencia, ya que todas las
cules son se coloque en su lugar un parmetro parmetro tipo- que se concretar en
referencias son al fin y al cabo punteros que en memoria se representan igual.
el momento en que se vayan a usar (al crear un objeto de la clase, llamar al mtodo,)
A estas definiciones se les llama genricos, y un ejemplo de una de ellas es el siguiente:
Metadatos ricos: Al almacenarse los tipos genricos cerrados en los ensamblados,
public class A<T> se podrn consultar mediante reflexin y ser aprovechados por herramientas como el
{ IntelliSense de Visual Studio.NET para proporcionar ayuda sobre su estructura.
T valor;
public void EstablecerValor(T valor) Implementacin fcil: Como es el propio CLR quien realiza gran parte del trabajo
{
necesario para dar soporte a los genricos, la inclusin de los mismos en cualquiera
this.valor = valor;
} de los lenguajes .NET se simplifica considerablemente.
}

Jos Antonio Gonzlez Seco Pgina 265 Jos Antonio Gonzlez Seco Pgina 266
El lenguaje de programacin C# Tema 21: Novedades de C# 2.0 El lenguaje de programacin C# Tema 21: Novedades de C# 2.0

Usos de los genricos


public void Encolar(T valor)
{}
Los genricos no son una novedad introducida por C# en el mundo de la programacin,
sino que otros lenguajes como Ada, Eiffel o C++ (plantillas) ya las incluyen desde hace public T Desencolar()
tiempo. Su principal utilidad es, como su propio nombre indica, facilitar la creacin de {}
}
cdigo genrico que pueda trabajar con datos de cualquier tipo. Esto es especialmente
til para crear tipos que acten como colecciones (pilas, colas, listas, etc.), cosa que C#
Entonces la extraccin de objetos de la cola no requerira de ningn tipo de conversin
1.X slo permita crear definindolos en base a la clase base comn object. Por ejemplo,
y sera tan cmoda y clara como sigue:
una cola que admitiese objetos de cualquier tipo haba que declararla como sigue:
Cola<string> miCola = new Cola<string>();
public class Cola
miCola.Encolar(Esto es una prueba);
{
string valorDesencolado = miCola.Desencolar();
object[] elementos;
public int NmeroElementos;
Si ahora por equivocacin el programador solicitase almacenar un objeto cuyo tipo no
public void Encolar(object valor); fuese ni string ni convertible a l, obtendra un error al compilar informndole de ello y
{} evitando que el fallo pueda llegar al entorno de ejecucin. Adems, el rendimiento del
cdigo es muy superior ya que no requerir conversiones de referencias a/desde object.
public object Desencolar() Si realiza pruebas podr comprobar que la utilizacin de genricos ofrece mejoras en el
{}
} rendimiento entorno al 20% para los tipos referencia, y al 200% para los tipos valor!

El primer problema de esta solucin es lo incmoda y proclive a errores que resulta su


utilizacin, pues a la hora de extraer valores de la cola habr que convertirlos a su tipo Sintaxis
real si se quieren aprovechar sus miembros especficos. Es decir:
El CLR de .NET 2.0 permite definir genricamente tanto clases como estructuras,
Cola miCola = new Cola(); interfaces, delegados y mtodos. Para ello basta con indicar tras el identificador de las
miCola.Encolar(Esto es una prueba); mismas su lista de sus parmetros genricos entre smbolos < y > separados por comas.
string valorDesencolado = (string) miCola.Desencolar(); Con ello, dentro de su definicin (miembros de las clases, cuerpos de los mtodos, etc.)
se podr usar libremente esos parmetros en cualquier sitio en que se espere un nombre
Aparte de que el programador tenga que escribir (string) cada vez que quiera convertir de un tipo. La siguiente tabla muestra un ejemplo para cada tipo de construccin vlida:
alguna de las cadenas extradas de miCola a su tipo concreto, qu ocurrir si por error
introduce un valor que no es ni string ni de un tipo convertible a string (por ejemplo, un Ejemplo declaracin Ejemplo uso
int) y al extraerlo sigue solicitando su conversin a string? Pues que el compilador no se public class Nodo<T>
dar cuenta de nada y en tiempo de ejecucin saltar una InvalidCastException. { class Nodo8BitAvanzado: Nodo<byte>
public T Dato; {}
Para resolver esto podra pensarse en derivar un tipo ColaString de Cola cuyos mtodos public Nodo<T> Siguiente;
pblicos trabajasen directamente con cadenas de textos (Encolar(string valor) y string }
public struct Pareja<T,U>
Desencolar()) Sin embargo, no es una solucin fcil de reutilizar ya que para cualquier { Pareja<int, string> miPareja;
otro tipo de elementos (pe, una cola de ints) habra que derivar una nueva clase de Cola. public T Valor1; miPareja.Valor1 = 1;
public U Valor2; miPareja.Valor2 = Hola;
Otro problema de ambas soluciones es su bajo rendimiento, puesto que cada vez que se }
almacene un objeto de un tipo referencia en la cola habr que convertir su referencia a interface IComparable<T> class A: IComparable<Persona>
una referencia a object y al extraerlo habr que volverla a transformar en una referencia { {
a string. Y para los tipos valor todava es peor!, en tanto que habr que realizar boxing int CompararCon(T otroValor); public int CompararCon(Persona persona)
} {}
y unboxing, procesos que son mucho ms lentos que las conversiones de referencias. }
delegate void Tratar<T>(T valor);
Si por el contrario se hubiese definido la cola utilizando genricos tal y como sigue: Tratar<int> objTratar = new Tratar<int>(F);
}
public class Cola<T>
{ public void F(int x)
T[] elementos; {}
public int NmeroElementos; void intercambiar<T>(ref T valor1,

Jos Antonio Gonzlez Seco Pgina 267 Jos Antonio Gonzlez Seco Pgina 268
El lenguaje de programacin C# Tema 21: Novedades de C# 2.0 El lenguaje de programacin C# Tema 21: Novedades de C# 2.0

ref T valor2) Si se solicitase la expansin A<int>, el tipo construido resultante acabara teniendo dos
{ decimal d1 = 0, d2 = 1; mtodos de idntica signatura. Aunque en principio, el compilador de C# 2.0 debera de
T temp = valor1; this.intercambiar<decimal>(ref d1, ref d2);
valor1 = valor2;
asegurar que tras cualquier expansin siempre se generen tipos genricos abiertos
this.intercambiar(ref d1, ref d2);
valor2 = temp; vlidos, produciendo errores de compilacin en caso contrario, por el momento no lo
} hace y deja compilar clases como la anterior. Sencillamente, si llamamos al mtodo F()
Tabla 19: Ejemplos de declaracin de tipos y miembros genricos
de un objeto A<int>, la versin del mtodo que se ejecutar es la primera. Es decir, en las
sobrecargas los parmetros tipo tienen menos prioridad que los concretos.

Ntese que todos los ejemplos de nombres de parmetros genricos hasta ahora vistos Donde no resulta conveniente controlar que los parmetros genricos puedan dar lugar a
son nica letra maysculas (T, U, etc.) Aunque obviamente no es obligatorio, sino que mtodos no vlidos es en las redefiniciones de operadores de conversin, en concreto en
se les puede dar cualquier identificador vlido, es el convenio de nomenclatura utilizado lo referente al control de que las expansiones puedan dar lugar a redefiniciones de las
en la BCL del .NET Framework 2.0 y el que por tanto se recomienda seguir. Lo que s conversiones predefinidas (como las de a/desde object), puesto que si se hiciese los
es obligatorio es no darles nunca un nombre que coincida con el del tipo o miembro al parmetros tipo nunca se podran utilizar en las conversiones. Por ello, simplemente se
que estn asociados o con el de alguno de los miembros de ste. ignoran las conversiones a medida que al expandirse puedan entrar en conflicto
con las predefinidas. Por ejemplo, dado el siguiente cdigo:
Fjese adems que la segunda llamada del ejemplo de utilizacin del mtodo genrico
intercambiar() no explicita el tipo del parmetro genrico. Esto se debe a que C# puede using System;
realizar inferencia de tipos y deducir que, como para todos parmetros del tipo T se ha
especificado un valor decimal, T debe concretarse como decimal. public class ConversionGenerica<T>
{
public static implicit operator T(ConversionGenerica<T> objeto)
{
Limitaciones T obj = default(T);
Console.WriteLine("Operador de conversin implcita");
return obj;
En principio, dentro de los tipos genricos se puede declarar cualquier miembro que se }
pueda declarar dentro de un tipo normal, aunque existe una limitacin: no se pueden }
declarar puntos de entrada (mtodos Main())ya que a stos los llama el CLR al iniciar la
ejecucin del programa y no habra posibilidad de concretizarles los argumentos tipo. public class Principal
{
public static void Main()
Por su parte, los parmetros tipo se pueden usar en cualquier sitio en que se espere un {
tipo aunque tambin con ciertas limitaciones: No pueden usarse en atributos, alias, ConversionGenerica<Principal> objeto1=new ConversionGenerica<Principal>();
punteros o mtodos externos, ni en los nombres de las clases bases o de las interfaces Principal objeto2 = objeto1; // Conversin no predefinida (a Principal)
implementadas. Sin embargo, excepto en el caso de los punteros, en estos sitios s que
se pueden especificar tipos genricos cerrados; e incluso en los nombres de las clases o Console.WriteLine("Antes de conversin no predefinida");
de las interfaces base, tambin se pueden usar tipos genricos abiertos. Por ejemplo: ConversionGenerica<object> objeto3 = new ConversionGenerica<object>();
object objeto4 = objeto3; // Conversin predefinida (a object)
// class A<T>: T {} // Error. No se puede usar T como interfaz o clase base
}
}
class A<T> {}

interface I1<V> {} El resultado de su ejecucin demuestra que la versin implcita de la conversin solo se
ejecuta ante la conversin no predefinida, puesto que su salida es la siguiente:
class B<T>: A<T>, I1<string> {} // OK.
Operador de conversin implcita
Debe tenerse cuidado al definir miembros con parmetros tipos ya que ello puede dar Antes de conversin no predefinida
lugar a sutiles ambigedades tal y como la que se tendra en el siguiente ejemplo:
Donde nunca habr ambigedades es ante clases como la siguiente, ya que sea cual sea
class A<T> el tipo por el que se concretice T siempre ser uno y por tanto nunca se podr dar el caso
{ de que la segunda versin de F() coincida en signatura con la primera:
void F(int x, string s) {}
void F(T x, string s) {} class A<T>
}
{
void F(int x, string s) {}

Jos Antonio Gonzlez Seco Pgina 269 Jos Antonio Gonzlez Seco Pgina 270
El lenguaje de programacin C# Tema 21: Novedades de C# 2.0 El lenguaje de programacin C# Tema 21: Novedades de C# 2.0

void F(T x, T s) {} Esta inconsistencias son fciles de solucionar en entornos donde la expansin se realiza
} en tiempo de compilacin, pues el compilador informa de ellas. Sin embargo, en los que
se hace en tiempo de ejecucin seran ms graves ya que durante la compilacin
As mismo, tambin se admitiran dos mtodos como los siguientes, ya que se considera pasaran desapercibidas y en tiempo de ejecucin podran causar errores difciles de
que los parmetros tipo forman parte de las signaturas: detectar. Por ello, C# en principio slo permite que con los objetos de tipos genricos se
realicen las operaciones genricas a cualquier tipo: las de object. Por ejemplo, el cdigo
class A
{
que sigue sera perfectamente vlido ya que tan slo utiliza miembros de object:
void F<T>(int x, string s) {}
void F(int x, string s) {} public static bool RepresentacionesEnCadenaIguales<T,U>(T objeto1, U objeto2)
} {
return objeto1.ToString() == objeto2.ToString();
Y al redefinir mtodos habr que mantener el mismo nmero de parmetros tipo en la }
clase hija que en la clase padre para as conservar la signatura. Esto, como en el caso de
los parmetros normales, no implica mantener los nombres de los parmetros tipo, sino Obviamente, tambin compilar un cdigo en el que los parmetros genricos con los
slo su nmero. Por tanto, cdigos como el siguiente sern perfectamente vlidos: que se realicen operaciones no comunes a todos los objects se conviertan antes a tipos
concretos que s las admitan, aunque entonces si se le pasasen argumentos de tipos no
public class A vlidos para esa conversin saltara una InvalidCastException en tiempo de ejecucin.
{ Por ejemplo, el siguiente mtodo compilar pero la ejecucin fallar en tiempo de
protected virtual void F<T, U>(T parmetro1, U parmetro2) ejecucin cuando se le pasen parmetros no convertibles a int.
{}
} public static int Suma<T,U>(T valor1, U valor2)
{
public class B:A return ((int) (object) valor1) + ((int) (object) valor2);
{ }
protected override void F<X, Y>(X parmetro1, Y parmetro2)
{}
} Ntese que por seguridad ni siquiera se permite la conversin directa de un parmetro
tipo a cualquier otro tipo que no sea object y ha sido necesario hacerla indirectamente.

Restricciones Lgicamente, C# ofrece mecanismos con los que crear cdigos genricos que a la vez
sean seguros y flexibles para realizar otras operaciones aparte de las que vlidas para
Probablemente a estas alturas ya est pensado que si en tiempo de diseo no conoce el cualquier object. Estos mecanismos consisten en definir restricciones en el abanico de
tipo concreto de los parmetros genricos, cmo podr escribir cdigo que los use y argumentos tipo vlidos para cada parmetro tipo, de modo que as se puedan realizar
funcione independientemente de su concretizacin? Es decir, dado un mtodo como: con l las operaciones vlidas para cualquier objeto de dicho subconjunto de tipos.

T Opera<T>(T valor1, T valor2) Las restricciones se especifican con clusulas where <parmetroGenrico>:<restricciones>
{ tras la lista de parmetros de los mtodos y delegados genricos, o tras el identificador
int resultadoComparacin = valor1.CompareTo(valor2); de las clases, estructuras e interfaces genricas; donde <restricciones> pueden ser:
if (resultadoComparacin>0)
return valor1-valor2 ;
else if(resultadoComparacin<0) Restricciones de clase base: Indican que los tipos asignados al parmetro genrico
return Math.Pow(valor1, valor2) ; deben derivar, ya sea directa o indirectamente, del indicado en <restricciones>. As,
else en el cdigo genrico se podrn realizar con seguridad todas las operaciones vlidas
return 2.0d; para los objetos dicho tipo padre, incluidas cosas como lanzarlos con sentencias
} throw o capturarlos en bloques catch si ese padre deriva de Exception. Por ejemplo:

Si se le llamase con Opera(2.0d, 3.2d) no habra problema, pues los objetos double tanto using System;
cuentan con un mtodo CompareTo() vlido, como tienen definida la operacin de resta,
se admiten como parmetros del mtodo Pow() de la clase Math y van a generar valores class A
de retorno de su mismo tipo double. Pero, y si se le llamase con Opera(hola, adis)? {
public int Valor;
En ese caso, aunque los objetos string tambin cuentan con un mtodo CompareTo() }
vlido, no admiten la operacin de resta, no se puede pasar como parmetros a Pow() y
el valor que devolvera return 2.0d; no coincidira con el tipo de retorno del mtodo. class B:A
{

Jos Antonio Gonzlez Seco Pgina 271 Jos Antonio Gonzlez Seco Pgina 272
El lenguaje de programacin C# Tema 21: Novedades de C# 2.0 El lenguaje de programacin C# Tema 21: Novedades de C# 2.0

public static int IncrementarValor<T>(T objeto) where T:A constructor la ltima. Por ejemplo, el tipo por el que se sustituya el parmetro genrico
{ del siguiente mtodo deber derivar de la clase A, implementar las interfaces I1 e I2 y
return ++objeto.Valor;
}
tener constructor pblico sin parmetros:

static void Main() void MiMtodo<T> (T objeto) where T: A, I1, I2, new()
{ {}
Console.WriteLine(B.IncrementarValor(new B())); // Imprime 1
Console.WriteLine(B.IncrementarValor(new A())); // Imprime 1 Las restricciones no se heredan, por lo que si de una clase genrica con restricciones
// Console.WriteLine(B.IncrementarValor(new C())); // Error se deriva otra clase genrica cuyos parmetros tipo se usen como parmetros de la clase
} padre, tambin habrn de incluirse en ella dichas restricciones para asegurase de que
}
cualquier argumento tipo que se le pase sea vlido. Igualmente, en las redefiniciones
class C {} habr que mantener las restricciones especficas de cada mtodo. O sea:

Esta restriccin adems permite asegurar que el argumento tipo siempre ser un class A {}
tipo referencia, por lo que con podr utilizar en los contextos en que slo sean
class B<T> where T:A
vlidos tipos referencia, como por ejemplo con el operador as. As mismo, tambin {}
permite realizar conversiones directas del parmetro tipo a su tipo base sin tenerse
que pasar antes por la conversin a object antes vista. /* No vlido, T debe ser de tipo A para poder ser pasado como argumento tipo de B, y
dicha restriccin no la hereda automticamente el T de C.
Restricciones de interfaz: Son similares a las anteriores, slo que en este caso lo class C<T>:B<T>
que indican es que los tipos que se asignen al parmetro tipo deben implementar las {} */
interfaces que, separadas mediante comas, se especifiquen en <restricciones> As se class C<T>:B<T> where T:A // Vlido
podrn usar en todos aquellos contextos en los que se esperen objetos que las {}
implementen, como por ejemplo en sentencias using si implementan IDisposable.
Ntese que todas estas restricciones se basan en asegurar que los argumentos tipo
Restricciones de constructor: Indican que los tipos por los que se sustituya el tengan determinadas caractersticas (un constructor sin parmetros o ciertos miembros
parmetro genrico debern disponer de un constructor pblico sin parmetros. Se heredados o implementados), pero no en las propias caractersticas de esos tipos. Por lo
declaran especificando new() en <restricciones>, y sin ellas no se permite instanciar tanto, a travs de parmetros tipo no podr llamarse a miembros estticos ya que
objetos del parmetro tipo dentro de la definicin genrico. Es decir: no hay forma de restringir los miembros estticos que podrn tener los argumentos tipo.

// Correcto, pues se indica que el tipo por el que se concretice T deber de tener un Finalmente, cabe sealar que en realidad tambin existe una cuarta forma de restringir
// constructor sin parmetros el abanico de argumentos tipos de un tipo genrico, que adems es mucho ms flexible
class A<T> where T:new() que las anteriores y permite aplicar cualquier lgica para la comprobacin de los tipos.
{
public T CrearObjeto() Consiste en incluir en el constructor esttico del tipo genrico cdigo que lance alguna
{ excepcin cuando los argumentos tipo especificados no sean vlidos, abortndose as la
return new T(); expansin. Por ejemplo, un tipo genrico C<T> que slo admita como argumentos tipos
} objetos de las clases A o B podra crearse como sigue:
}
class C<T>
/* Incorrecto, ya que ante el new T() no se sabe si el tipo por el que se concretice T
{
tendr un constructor sin parmetros
static C()
class B<T>
{
{
if ( !(typeof(T) == typeof(A)) && !(typeof(T) == typeof(B)))
public T CrearObjeto()
throw new ArgumentException(El argumento tipo para T debe ser A o B);
{
}
return new T();
}
}
}
*/ O si se quisiese que tambin los admitiese de cualquier clase derivada de stas, podra
aprovecharse como sigue el mtodo bool IsAssignableForm(Type tipo) de los objetos
Cada genrico puede tener slo una clusula where por parmetro genrico, aunque en Type, que indica si el objeto al que se aplica representa a un tipo al que se le pueden
ella se pueden mezclar restricciones de los tres tipos separadas por comas. Si se hace, asignar objetos del tipo cuyo objeto System.Type se le indica como parmetro:
stas han de aparecer en el orden en que se han citado: la de clase base primero y la de

Jos Antonio Gonzlez Seco Pgina 273 Jos Antonio Gonzlez Seco Pgina 274
El lenguaje de programacin C# Tema 21: Novedades de C# 2.0 El lenguaje de programacin C# Tema 21: Novedades de C# 2.0

class C<T> Valores por defecto


{
static C()
{ Cuando se trabaja con tipos genricos puede interesar asignarles el valor por defecto de
if ( !(typeof(B).IsAssignableFrom(typeof(T))) && su tipo, pero cul ser ste? Es decir, si el parmetro genrico se sustituye por un
!(typeof(A).IsAssignableFrom(typeof(T)))) tipo referencia, el valor por defecto de sus objetos sera null y valdran cdigos como:
throw new ArgumentException(El argumento tipo para T debe ser A o B);
}
void F<T>()
}
{
T objeto = null ;
El nico problema de esta forma de restringir el abanico de tipos es que desde el cdigo }
del tipo genrico no se podr acceder directamente a los miembros especficos del tipo
argumento, sino que antes habr que convertirlos explcitamente a su tipo. Por ejemplo: Sin embargo, si se sustituyese por un tipo valor no sera vlido, por lo que este tipo de
asignaciones nunca se admitirn salvo que haya establecido una restriccin de clase
using System;
base que asegure que el argumento tipo sea siempre un tipo referencia.
public class A
{ No obstante, C# 2.0 permite representar el valor por defecto de cualquier parmetro tipo
public void Mtodo() con la sintaxis default(<parmetroTipo>), lo que dejara al ejemplo anterior como sigue:
{
Console.WriteLine("Ejecutado Mtodo() de objeto A"); void F<T>()
} {
} T objeto = default(T);
}
public class B
{
public void Mtodo() En cada expansin, el CLR sustituir la expresin default(T) por el valor por defecto del
{ tipo que se concrete para T (0, null, false,...), dando lugar a mtodos como:
Console.WriteLine("Ejecutado Mtodo() de objeto B");
} Expansin para int Expansin para string Expansin para bool
}
void F<int>() void F<string>() void F<bool>()
{ { {
class C<T>
int objeto = 0; int objeto = null; int objeto = false;
{
} } }
static C()
{
if ( !(typeof(T) == typeof(A)) && !(typeof(T) == typeof(B))) Ntese pues que para comprobar si un cierto argumento tipo es un tipo valor o un tipo
throw new ArgumentException("El argumento tipo para T debe ser A o B"); referencia bastar con comprobar si su default devuelve null. As por ejemplo, para crear
} un tipo genrico que slo admita tipos referencia se podra hacer:
public void LlamarAMtodo(T objeto)
class GenricoSloReferencias<T>
{
{
if (objeto is A)
static GenricoSloReferencias()
(objeto as A).Mtodo(); // Hay que hacer conversin explcita
{
else
if (default(T)!=null)
(objeto as B).Mtodo(); // Hay que hacer conversin explcita
throw new ArgumentException(T debe ser un tipo referencia);
}
}
}
}
class Principal
{ Por su parte, y a diferencia de lo que ocurre con el operador de asignacin, el operador
static void Main() de igualdad (==) s que puede aplicarse entre instancias de parmetros tipo y null, an
{ cuando estos almacenen tipos valor y dicha comparacin no sea vlida. En dichos casos
C<A> objetoCA = new C<A>(); la comparacin simplemente retornara false. Igualmente, tambin se les podr aplicar el
objetoCA.LlamarAMtodo(new A());
operador de desigualdad (!=), que siempre devolver true para los tipo valor.
C<B> objetoCB = new C<B>();
objetoCB.LlamarAMtodo(new B());
}
}

Jos Antonio Gonzlez Seco Pgina 275 Jos Antonio Gonzlez Seco Pgina 276
El lenguaje de programacin C# Tema 21: Novedades de C# 2.0 El lenguaje de programacin C# Tema 21: Novedades de C# 2.0

Ambigedades }

Y en otro fichero llamado clientes-ampliacin.cs:


El que los delimitadores de los argumentos tipo sean los mismos que los operadores de
comparacin mayor que y menor que y utilicen como el mismo carcter separador public partial class Clientes: I1
que se usa para separar los argumentos de las llamadas a mtodos (la coma ,) puede dar {}
lugar a ambigedades. Por ejemplo, una llamada como la siguiente:
interface I2
F(G<A,B>(7)); {
void G();
Podra interpretarse de dos formas: Como una llamada a un mtodo F() con el resultado }
de evaluar G<A como primer argumento y el resultado de B>(7) como segundo, o como
[MiAtributo2(Test2)] public partial class Clientes: I2
una llamada a F() con el resultado de una llamada G<A,B>(7) a un mtodo genrico G() {
void I1.F()
Lo que hace el compilador es interpretar como llamadas a mtodos genricos cualquier {}
aparicin del carcter > donde a este le sigan alguno de estos caracteres: (, ), ], >, ;, :, ,?, . public void G()
,. En el resto de casos sern tratados como operadores de comparacin. Por tanto, en el {}
}
ejemplo anterior la expresin ser interpretada como una llamada a un mtodo genrico,
y para que se interpretase de la otra forma habra rescribirse como F(G<A,B>7);
Si al compilar se especifican ambas ambos ficheros simultneamente, como en:

csc /t:library clientes.cs clientes-ampliacin.cs


Tipos parciales
El compilador considerar que la definicin de la clase Clientes es la siguiente:
C# 2.0 da la posibilidad de distribuir las definiciones de los tipos en mltiples ficheros
tales que si al compilar se pasan todos juntos al compilador, ste automticamente los [MiAtributo1, MiAtributo2(Test1), MiAtributo2(Test2)] public class Clientes: I1, I2
fusionar en memoria y generar el MSIL correspondiente a su unin. A estos tipos se {
les conoce como tipos parciales y pueden ser definiciones tanto de clases como de public int X;
void I1.F()
estructuras e interfaces, pero no de enumeraciones o delegados ya que estas suelen ser {}
definiciones tan sencillas que dividirlas resultara muy poco o nada til. public void G()
{}
Los tipos parciales facilitan separar los cdigos obtenidos automticamente mediante }
generadores de cdigo (como VS.NET 2005) de las modificaciones se les realicen a
mano tras su generacin, pues permiten regenerarlos en cualquier momento sin que con Ntese que en una parte de una definicin parcial se podrn referenciar identificadores
ello se pierdan dichos cambios. Adems, tambin son tiles para agilizar el desarrollo (miembros, interfaces implementadas explcitamente, etc.) no declarados en ella sino en
y mantenimiento de las tipos de datos, pues permiten que varias personas puedan estar otras partes de la definicin, puesto que mientras al compilar se le pasen al compilador
trabajando simultneamente en diferentes secciones de un mismo tipo de dato. las partes donde estn definidos, la fusin producir una clase vlida. No obstante, hay
que sealar que esto tiene pequeas algunas limitaciones y no es aplicable a:
Para definir un tipo parcial simplemente con basta declararlo varias veces en el mismo o
en diferentes ficheros, aadindoles un nuevo modificador partial y manteniendo Modificador unsafe: Por seguridad, aplicarlo a la definicin de una parte de un tipo
siempre el mismo nombre completo, parmetros tipo, modificadores de visibilidad y no implicar que se considere aplicado al resto y puedan utilizarse en ellas punteros,
clase base. Las restricciones de los parmetros tipos no tienen porqu aparecer en todas sino que deber de aplicarse por separado a cada parte en la que se vaya a hacer uso
las partes, pero si lo hace deben ser siempre las mismas. Por ejemplo, en un fichero de caractersticas inseguras.
clientes.cs se podra tener:
Directiva using: En cada parte de una definicin parcial se puede utilizar diferentes
interface I1 directivas using que importen diferentes espacios de nombres y definan diferentes
{ alias. Al analizar cada parte, el compilador tan slo interpretar las directivas using
void F(); especificadas en ella, admitindose incluso que en varias partes se definan alias con
}
el mismo nombre para clases o espacios de nombres diferentes.
[MiAtributo1] [MiAtributo2(Test1)] public partial class Clientes
{ El uso de tipos parciales no obstante introduce el problema de que el mantenimiento de
public int X; cdigo particionados puede ser ms complicado al estar distribuida su implementacin a

Jos Antonio Gonzlez Seco Pgina 277 Jos Antonio Gonzlez Seco Pgina 278
El lenguaje de programacin C# Tema 21: Novedades de C# 2.0 El lenguaje de programacin C# Tema 21: Novedades de C# 2.0

lo largo de mltiples ficheros. Sin embargo, herramientas como la Vista de Clases de using System;
VS.NET 2005 solucionan esto proporcionando una vista unificada de la estructura de using System.Collections;
estos tipos con el resultado de la fusin de todas sus partes. class PruebaIteradores
{
public IEnumerator GetEnumerator()
Iteradores {
yield return 1;
yield return "Hola";
Aprovechando las ventajas proporcionadas por los genricos, en la BCL del .NET 2.0 se }
ha optimizado la implementacin de las colecciones introduciendo en un nuevo espacio
de nombres System.Collections.Generic dos nuevas interfaces llamadas IEnumerable<T>
public IEnumerable AlRevs
e IEnumerator<T> que las colecciones podrn implementar para conseguir que el acceso {
a las colecciones sea mucho ms eficiente que con las viejas IEnumerable e IEnumerator get
al evitarse el boxing/unboxing o downcasting/upcasting que el tipo de retorno object de {
la propiedad Current de IEnumerator implicaba. Adems, en ellas se ha optimizado el yield return "Hola";
diseo eliminando el tan poco utilizado mtodo Reset() y hacindoles implementar en su yield return 1;
}
lugar la estandarizada interfaz IDisposable. En resumen, estn definidas como sigue: }
public interface IEnumerable<T>
static void Main()
{
{
IEnumerator<T> GetEnumerator();
PruebaIteradores objeto = new PruebaIteradores();
}
foreach(object valor in objeto)
Console.WriteLine(valor);
public interface IEnumerator<T>: IDisposable
{
foreach(object x in objeto.AlRevs)
T Current { get; }
Console.WriteLine(x);
bool MoveNext();
}
}
}

Ntese que en realidad C# 1.0 ya proporcionaba a travs del patrn de coleccin un La clase PruebaIteradores tendrn un mtodo GetEnumerator() que devolver un objeto
mecanismo con que implementar colecciones fuertemente tipadas. Sin embargo, era IEnumerator generado en base a las instrucciones yield que retornar como primer
algo especfico de este lenguaje, oculto y desconocido para muchos programadores y no elemento un 1 y como segundo la cadena Hola, por lo que podr ser recorrida a travs
completamente soportado por otros lenguajes .NET (por ejemplo, Visual Basic.NET y de la instruccin foreach. Del mismo modo, su propiedad AlRevs devolver un objeto
su sentencia For Each) Por el contrario, estas nuevas interfaces genricas proporcionan que tambin dispone de dicho mtodo y por tanto tambin podr recorrese a travs de
una solucin ms compatible y mejor formulada. dicha sentencia, aunque en este caso hace el recorrido al revs. Por tanto, su salida ser:

C# 2.0 proporciona adems un nuevo mecanismo con el que es resultar mucho sencillo 1
crear colecciones que implementando directamente estas interfaces: los iteradores. Son Hola
mtodos que para generar objetos que implementen todas estas interfaces se apoyan en Hola
1
una nueva sentencia yield. Han de tener como tipo de retorno IEnumerable, IEnumerator,
IEnumerable<T> e IEnumerator<T>, y su cuerpo indicarn los valores a recorrer con
Ntese que aunque los iteradores se puedan usar para definir mtodos que devuelvan
sentencias yield return <valor>; en las que si se el tipo de retorno del iterador es genrico,
tanto objetos IEnumerable como IEnumerator, ello no significa que dichos tipos sean
<valor> deber ser de tipo T. Asimismo, tambin se marcar el fin de la enumeracin y
intercambiables. Es decir, si en el ejemplo anterior hubisemos definido la propiedad
forzar a que MoveNext() siempre devuelva false mediante sentencias yield break;.
AlRevs como de tipo IEnumerator, el cdigo no compilara en tanto que lo que foreach
espera es un objeto que implemente IEnumerable, y no un IEnumerator.
A partir de la definicin de un iterador, el compilador anidar dentro de la clase en que
ste se defini una clase privada que implementar la interfaz de su tipo de retorno. El
En realidad, si el tipo de retorno del iterador es IEnumerator o IEnumerator<T>, la clase
cdigo del iterador no se ejecutar al llamarle, sino en cada llamada realizada al mtodo
interna que se generar implementar tanto la versin genrica de esta interfaz como la
MoveNext() del objeto que devuelve, y slo hasta llegarse a algn yield return. Luego la
que no lo es. Lo mismo ocurre para el caso de IEnumerable e IEnumerable<T>, en el que
ejecucin se suspender hasta la siguiente llamada a MoveNext(), que la reanudar por
adems, el compilador opcionalmente (el de C# de Microsoft s lo hace) tambin podr
donde se qued. Por tanto, sucesivas llamadas a MoveNext() irn devolviendo los valores
implementar las interfaces IEnumerator e IEnumerator<T>. Sin embargo, implementar
indicados por los yield return en el mismo orden en que se stos se ejecuten. As, en:

Jos Antonio Gonzlez Seco Pgina 279 Jos Antonio Gonzlez Seco Pgina 280
El lenguaje de programacin C# Tema 21: Novedades de C# 2.0 El lenguaje de programacin C# Tema 21: Novedades de C# 2.0

las interfaces IEnumerator no implica que se implementen las IEnumerable, y de ah el


problema descrito en el prrafo anterior. public class Nodo<T>
{
public Nodo<T> Izquierdo, Derecho;
Por otro lado, hay que sealar que en el cdigo que se puede incluir dentro de los public T Valor;
iteradores existen algunas limitaciones destinadas a evitar rupturas descontroladas de la }
iteracin: no se permiten sentencias return, ni cdigo inseguro ni parmetros por
referencia, y las sentencias yield no se puede usar dentro de bloques try, catch o finally. public class rbolBinario<T>
{
Nodo<T> raz;
Internamente, a partir de la definicin del iterador el compilador generar una clase
interna que implementar la interfaz del tipo de retorno de ste y sustituir el cdigo public IEnumerable<T> Inorden{ get { return recorrerEnInorden(this.raz); } }
del miembro de la coleccin donde se define el iterador por una instanciacin de dicha
clase a la que le pasar como parmetro el propio objeto coleccin a recorrer (this) En IEnumerable<T> recorrerEnInorden(Nodo<T> nodo)
ella, el cdigo del iterador se transformar en una implementacin del MoveNext() de {
Nodo<T> nodoIzquierdo = nodo.Izquierdo;
IEnumerator basada en un algoritmo de mquina de estado, y la posible limpieza de los
if (nodoIzquierdo!=null)
recursos consumido por la misma se har en el Dispose() O sea, se generar algo como: foreach(T valor in recorrerEnInorden(nodoIzquierdo))
yield return valor;
public class <coleccin>:<interfaz>
{ yield return raz.Valor;
public virtual <interfaz> GetEnumerator()
{ Nodo<T> nodoDerecho= nodo.Derecho;
return GetEnumerator$<nmeroAleatorionico>__IEnumeratorImpl impl = if (nodoDerecho!=null)
new GetEnumerator$<nmeroAleatorionico>__IEnumeratorImpl(this); foreach(T valor in recorrerEnInorden(nodoDerecho))
yield return valor;
} }

class GetEnumerator$<nmeroAleatorionico>__IEnumeratorImpl:<interfaz> // Implementacin de recorridos en Preorden y PostOrden y dems miembros


{ }
public <coleccin> @this;
<tipoElementosColeccin> $_current;

string <interfaz>.Current { get { return $_current; } } Mejoras en la manipulacin de delegados


bool <interfaz>.MoveNext() Inferencia de delegados
{
// Implementacin de la mquina de estados
} Mientras que en C# 1.X siempre era necesario indicar explcitamente el delegado del
objeto o evento al que aadir cada mtodo utilizando el operador new, como en:
void IDisposable.Dispose()
{ miObjeto.miEvento += new MiDelegado(miCdigoRespuesta);
// Posible limpieza de la mquina de estados
}
En C# 2.0 el compilador es capaz de inferirlo automticamente de la definicin del
}
} delegado en que se desea almacenar. As, para el ejemplo anterior bastara con escribir:
miObjeto.miEvento += miCdigoRespuesta;
Ntese que para cada foreach se generar un nuevo objeto de la clase interna, por lo que
el estado de estos iteradores automticamente generados no se comparte entre foreachs Sin embargo, asignaciones como la siguiente en la que el mtodo no es asociado a un
y por tanto el antiguo mtodo Reset() de IEnumerator se vuelve innecesario. Es ms, si delegado no permiten la deduccin automtica del mismo y fallarn al compilar:
la interfaz de retorno del iterador fuese IEnumerator, la implementacin realizada por el
compilador en la clase interna para el obsoleto mtodo de la misma Reset() lanzar una object o = miCdigoRespuesta;
NotSupportedException ante cualquier llamada que explcitamente se le realice. No
obstante, hay que tener cuidado con esto pues puede implicar la creacin de numerosos Aunque explicitando la conversin a realizar tal y como sigue s que compilar:
objetos en implementaciones recursivas como la siguiente, en la que se aprovechan los
iteradores para simplificar la implementacin de los recorridos sobre un rbol binario: object o = (MiDelegado) miCdigoRespuesta;

using System.Collections.Generic;

Jos Antonio Gonzlez Seco Pgina 281 Jos Antonio Gonzlez Seco Pgina 282
El lenguaje de programacin C# Tema 21: Novedades de C# 2.0 El lenguaje de programacin C# Tema 21: Novedades de C# 2.0

En general, la inferencia de delegados funciona en cualquier contexto en que se espere mtodos que almacena a travs suya habr que pasarles valores cualesquiera) As, la
un objeto delegado. Esto puede observarse en el siguiente ejemplo: asignacin del ejemplo anterior podra compactarse an ms y dejarla en:

using System; miObjeto.miEvento += delegate { Console.WriteLine (Evento producido en miObjeto); };

class A En ambos casos, a partir de estas instrucciones el compilador definir dentro de la clase
{
delegate void MiDelegado(string cadena);
en que hayan sido incluidas un mtodo privado similar al siguiente:

public static void Main() public void __AnonymousMethod$00000000(object parmetro1, int parmetro2)
{ {
// En C# 1.X: MiDelegado delegado = new MiDelegado(miMtodo); Console.WriteLine (Evento producido en miObjeto);
MiDelegado delegado = miMtodo; }

// En C# 1.X: delegado.EndInvoke(delegado.BeginInvoke("Hola", Y tratar la asignacin del mtodo annimo al evento como si fuese:
// new AsyncCallback(mtodoFin), null));
delegado.EndInvoke(delegado.BeginInvoke("Hola", mtodoFin, null)); miObjeto.miEvento += new MiDelegado(this,__AnonymousMethod$00000000);
}

static void miMtodo(string cadena)


No obstante, la sintaxis abreviada no se puede usar con delegados con parmetros out,
{ puesto que al no poderlos referenciar dentro de su cuerpo ser imposible asignarles en el
Console.WriteLine("MiMtodo(string {0})", cadena); mismo un valor tal y como la semntica de dicho modificador requiere.
}
Fjese que aunque a travs de += es posible almacenar mtodos annimos en un objeto
static void mtodoFin(IAsyncResult datos)
{
delegado, al no tener nombre no ser posible quitrselos con -= a no ser que antes se
// hayan almacenado en otro objeto delegado, como en por ejemplo:
}
} MiDelegado delegado = delegate(object prametro1, int parmetro2)
{ Console.WriteLine (Evento producido en miObjeto); };
Mtodos annimos miObjeto.miEvento += delegado;
miObjeto.miEvento -= delegado;

C# 2.0 permite asociar cdigo a los objetos delegados directamente, sin que para ello el Los mtodos annimos han de definirse en asignaciones a objetos delegados o eventos
programador tenga que crear mtodos nicamente destinados a acoger su cuerpo y no a, para que el compilador pueda determinar el delegado donde encapsularlo. Por tanto, no
como sera lo apropiado, reutilizar o clarificar funcionalidades. Se les llama mtodos ser vlido almacenarlos en objects mediante instrucciones del tipo:
annimos, pues al especificarlos no se les da un nombre sino que se sigue la sintaxis:
object annimo = delegate(object prametro1, int parmetro2)
delegate(<parmetros>) {<instrucciones>}; { Console.WriteLine (Evento producido en miObjeto); }; // Error

Y ser el compilador quien internamente se encargue de declarar mtodos con dichos Aunque s si se especificarse el delegado mediante conversiones explcitas, como en:
<parmetros> e <instrucciones> y crear un objeto delgado que los referencie. Estas
<instrucciones> podrn ser cualesquiera excepto yield, y para evitar colisiones con los object annimo = (MiDelegado) delegate(object parametro1, int parmetro2)
{ Console.WriteLine (Evento producido en miObjeto); };
nombres de mtodos creados por el programador el compilador dar a los mtodos en
que internamente las encapsular nombres que contendrn la subcadena reservada __.
Los mtodos annimos tambin pueden ser pasados como parmetros de los mtodos
Ntese que con esta sintaxis no se pueden aadir atributos a los mtodos annimos.
que esperen delegados, como en por ejemplo:
Con mtodos annimos, las asignaciones de mtodos a delegados podra compactarse class A
an ms eliminndoles la declaracin explcita del mtodo de respuesta. Por ejemplo: {
delegate void MiDelegado();
miObjeto.miEvento += delegate(object parmetro1, int parmetro2) public void MiMtodo()
{ Console.WriteLine (Evento producido en miObjeto); }; {
LlamarADelegado(delegate() { Console.Write(Hola); });
Incluso si, como es el caso, en el cdigo de un mtodo annimo no se van a utilizar los }
void LlamarADelegado(MiDelegado delegado)
parmetros del delegado al que se asigna, puede omitirse especificarlos (al llamar a los {
delegado();

Jos Antonio Gonzlez Seco Pgina 283 Jos Antonio Gonzlez Seco Pgina 284
El lenguaje de programacin C# Tema 21: Novedades de C# 2.0 El lenguaje de programacin C# Tema 21: Novedades de C# 2.0

} VariablesExternas objeto = null;


} int valor = d(ref objeto);
Console.WriteLine(valor={0}, objeto.Valor={1}, valor, objeto.Valor);
Ntese que si el mtodo aceptase como parmetros objetos del tipo genrico Delegate, valor = d(ref objeto);
Console.WriteLine(valor={0}, objeto.Valor={1}, valor, objeto.Valor);
antes de pasarle el mtodo annimo habra que convertirlo a algn tipo concreto para }
que el compilador pudiese deducir la signatura del mtodo a generar, y el delegado no }
podra tomar ningn parmetro ni tener valor de retorno. Es decir:
Cuya salida es:
class A
{ valor=3, objeto.Valor=102
public void MiMtodo() valor=4, objeto.Valor=103
{
LlamarADelegado((MiDelegado) delegate { Console.Write("Hola"); });
}
Fjese que aunque las variables x y o son locales al mtodo F(), se mantienen entra las
llamadas que se le realizan a travs del objeto delegado d ya que han sido capturadas
void LlamarADelegado(Delegate delegado) por el mtodo annimo que ste almacena.
{
MiDelegado objeto = (MiDelegado) delegado; A las variables capturadas no se les considera fijas en memoria, por lo que para poderlas
objeto("LlamarADelegado");
}
manipular con seguridad en cdigo inseguro habr que encerrarlas en la sentencia fixed.
}
Debe sealarse que la captura de variables externas no funciona con los campos de las
Captura de variables externas estructuras, ya que dentro de un mtodo annimo no se puede referenciar al this de las
mismas. Sin embargo, la estructura siempre puede copiarse desde fuera del mtodo
En los mtodos annimos puede accederse a cualquier elemento visible desde el punto annimo en una variable local para luego referenciarla en el mismo a travs de dicha
de su declaracin, tanto a las variables locales de los mtodos donde se declaren como a copia. Eso s, debe sealarse que en un campo de la estructura no se podr realizar esta
los miembros de sus clases. A dichas variables se les denomina variables externas, y se copiar ya que ello causara ciclos en la definicin de sta.
dice que los mtodos annimos las capturan ya que almacenarn una referencia a su
valor que mantendrn entre llamadas. Esto puede observarse en el siguiente ejemplo:
Covarianza y contravarianza de delegados
using System;
Los delegados tambin son ms flexibles en C# 2.0 porque sus objetos admiten tanto
delegate int D(ref VariablesExternas parmetro); mtodos que cumplan exactamente con sus definiciones, con valores de retorno y
class VariablesExternas
parmetros de exactamente los mismos tipos indicados en stas, como mtodos que los
{ tomen de tipos padres de stos. A esto se le conoce como covarianza para el caso de los
public int Valor = 100; valores de retorno y contravarianza para el de los parmetros. Por ejemplo:
static D F() using System;
{
VariablesExternas o = new VariablesExternas(); public delegate Persona DelegadoCumpleaos(Persona persona, int nuevaEdad);
int x = 0;
D delegado = delegate(ref VariablesExternas parmetro) public class Persona
{ {
if (parmetro==null) public string Nombre;
parmetro = o;
else private int edad;
parmetro.Valor++; public int Edad
return ++x; {
}; get { return this.edad; }
x += 2; }
o.Valor+=2;
return delegado; public event DelegadoCumpleaos Cumpleaos;
}
public Persona(String nombre, int edad)
static void Main() {
{ this.Nombre = nombre;
D d = F(); this.edad = edad;

Jos Antonio Gonzlez Seco Pgina 285 Jos Antonio Gonzlez Seco Pgina 286
El lenguaje de programacin C# Tema 21: Novedades de C# 2.0 El lenguaje de programacin C# Tema 21: Novedades de C# 2.0

} Esto les permite sealar cuando almacenan un valor desconocido o inaplicable, lo que
puede resultar muy til a la hora de trabajar con bases de datos ya que en stas los
public void CumplirAos()
{
campos de tipo entero, booleanos, etc. suelen permitir almacenar valores nulos. As
Cumpleaos(this, this.edad+1); mismo, tambin evita tener que definir ciertos valores especiales para los parmetros o
this.edad++; el valor de retorno de los mtodos con los que expresar dicha semntica (pe, devolver -1
} en un mtodo que devuelva la posicin donde se haya un cierto elemento en una tabla
para indicar que no se ha encontrado en la misma), cosa que adems puede implicar
}
desaprovechar parte del rango de representacin del tipo valor o incluso no ser posible
public class Empleado:Persona de aplicar si todos los valores del parmetro o valor de retorno son significativos.
{
public uint Sueldo;
Sintaxis
public Empleado(string nombre, int edad, uint sueldo):base(nombre, edad)
{
this.Sueldo = sueldo; La versin anulable de un tipo valor se representa igual que la normal pero con el sufijo
} ?, y se le podrn asignar tanto valores de su tipo subyacente (el tipo normal, sin el ?)
} como null. De hecho, su valor por defecto ser null. Por ejemplo:
public class Covarianza
{ int? x = 1;
x = null;
static void Main()
{ En realidad el uso de ? no es ms que una sintaxis abreviada con la que instanciar un
Empleado josan = new Empleado("Josan", 25, 500000); objeto del nuevo tipo genrico Nullable<T> incluido en el espacio de nombres System de
josan.Cumpleaos += mostrarAos; la BCL con su parmetro genrico concretizado al tipo subyacente. Este tipo tiene un
josan.CumplirAos();
}
constructor que admite un parmetro del tipo genrico T, por lo que en realidad el
cdigo del ejemplo anterior es equivalente a:
static Persona mostrarAos(Persona josan, int nuevaEdad)
{ Nullable<int> x = new Nullable<int>(1); // Tambin valdra Nullable<int> x = new int?(1);
Console.WriteLine("{0} cumpli {1} aos", josan.Nombre, nuevaEdad); x = null;
return josan;
} El tipo Nullable proporciona dos propiedades a todos los tipos anulables: bool HasValue
}
para indicar si almacena null, y T Value, para obtener su valor. Si una variable anulable
valiese null, leer su propiedad Value hara saltar una InvalidOperationException. A
Ntese que aunque en el ejemplo el delegado se ha definido para operar con objetos del continuacin se muestra un ejemplo del uso de las mismas:
tipo Persona, se le han pasado objetos de su subtipo Empleado y devuelve un objeto
tambin de dicho tipo derivado. Esto es perfectamente seguro ya que en realidad los using System;
objetos del tipo Empleado siempre tendrn los miembros que los objetos del Persona y class Anulables
por lo tanto cualquier manipulacin que se haga de los mismos en el cdigo ser {
sintcticamente vlida. Si lo ejecutamos, la salida que mostrar el cdigo es la siguiente: static void Main()
{
Josan compli 26 aos int? x = null;
int? y = 123;
MostrarSiNulo(x, "x");
MostrarSiNulo(y, "y");
Tipos anulables }

Concepto static void MostrarSiNulo(int? x, string nombreVariable)


{
if (!x.HasValue)
En C# 2.0 las variables de tipos valor tambin pueden almacenar el valor especial null, Console.WriteLine("{0} es nula", nombreVariable);
como las de tipos referencia. Por ello, a estas variables se les denomina tipos anulables. else
Console.WriteLine("{0} no es nula. Vale {1}.", nombreVariable, x.Value);
}
}

Jos Antonio Gonzlez Seco Pgina 287 Jos Antonio Gonzlez Seco Pgina 288
El lenguaje de programacin C# Tema 21: Novedades de C# 2.0 El lenguaje de programacin C# Tema 21: Novedades de C# 2.0

int z = (int) y;
En l, el mtodo MostrarSiNulo() primero comprueba si la variable vale null, para si as
simplemente indicarlo y si no indicar cul su valor. Su salida ser: Lo que s se permite tambin es realizar implcitamente entre tipos anulables todas
aquellas conversiones que seran vlidas entre sus versiones no anulables. Por ejemplo:
x es nula
y no es nula. Vale 123. double d = z;
double d2 = (double) y; // Se puede hacer la conversin directamente a double
En realidad nada fuerza a utilizar HasValue para determinar si una variable anulable vale d2 = (int) y; // o indirectamente desde int
z = (int) d;
null, sino que en su lugar se puede usar el habitual operador de igualdad. Del mismo
y = (int?) d2; // Se puede hacer la conversin directamente a int?
modo, en vez de leer su valor a travs de de la propiedad Value se puede obtener y = (int) d2; // o indirectamente desde int
simplemente accediendo a la variable como si fuese no anulable. As, el anterior mtodo
MostrarSiNulo() podra rescribirse como sigue, con lo que quedar mucho ms legible:
Operaciones con nulos
static void MostrarSiNulo(int? x, string nombreVariable)
{
if (x==null) Obviamente, la posibilidad de introducir valores nulos en los tipos valor implica que se
Console.WriteLine("{0} es nula", nombreVariable); tengan que modificar los operadores habitualmente utilizados al trabajar con ellos para
else que tengan en cuenta el caso en que sus operandos valgan null.
Console.WriteLine("{0} no es nula. Vale {1}.", nombreVariable, x);
} Para los operadores relacionales, esto implicara en principio la introduccin de una
lgica trivaluada (valores true, false y null), como en las bases de datos SQL. Sin
Finalmente, hay que sealar que el tipo subyacente puede ser a su vez un tipo anulable, embargo, en tanto que ello suele atentar contra la intuitividad y provocar problemas
siendo vlidas declaraciones del tipo int??, int???, etc. Sin embargo, no tiene mucho psicolgicos a los programadores, se ha preferido simplificar el funcionamiento de
sentido hacerlo ya que al fin y al cabo los tipos resultantes seran equivalentes y estos operadores y hacer que simplemente devuelvan true si sus dos operadores valen
aceptaran el mismo rango de valores. Por ejemplo, una instancia del tipo int??? podra null y false si solo uno de ellos lo hace, pero jams devolvern null. Por ejemplo:
crearse de cualquier de estas formas:
int? i = 1;
int??? x = 1; int? j = 2;
x = new int???(1); int? z = null;
x = new int???(new int??(1));
x = new int???(new int??(new int?(1))); Console.WriteLine(i > j); // Imprime False, al ser el valor de i menor que el de j
Console.WriteLine(i > z); // Imprime False, al ser z null.
Y leerse como sigue: Console.WriteLine(i > null); // Imprime False. El compilador incluso avisa de que este
// tipo de comparaciones siempre retornan false por ser uno
Console.WriteLine(x); // de sus operandos null.
Console.WriteLine(x.Value); Console.WriteLine(null > null); // Imprime False. dem al caso anterior.
Console.WriteLine(x.Value.Value);
Console.WriteLine(x.Value.Value.Value); Sin embargo, para el caso de los operadores lgicos s que se ha optado por permitir la
devolucin de null por similitud con SQL, quedando sus tablas de verdad definidas
como sigue segn esta lgica trivaluada:
Conversiones
x y x && y x || y
C# 2.0 es capaz realizar implcitamente conversiones desde un tipo subyacente a su
versin anulable, gracias alo que cualquier valor del tipo subyacente de una variable true true true true
anulable se puede almacenar en la misma. Por ejemplo: true false false true

int x = 123; true null null true


int? y = x; false false false false

Lo que no es posible realizar implcitamente es la conversin recproca (de un tipo false null false null
anulable a su tipo subyacente) ya que si una variable anulable almacena el valor null null null null null
este no ser vlido como valor de su tipo subyacente. Por tanto, estas conversiones han
Tabla 20: Tabla de verdad de los operadores lgicos en lgica trivaluada
de realizarse explcitamente, tal y como a continuacin se muestra:

Jos Antonio Gonzlez Seco Pgina 289 Jos Antonio Gonzlez Seco Pgina 290
El lenguaje de programacin C# Tema 21: Novedades de C# 2.0 El lenguaje de programacin C# Tema 21: Novedades de C# 2.0

Y en el caso de los aritmticos tambin se permite la devolucin de null, aunque en este El operador ?? es asociativo por la derecha, por lo que puede combinarse como sigue:
caso su implementacin es mucho ms intuitiva y simplemente consiste en retornar null
si algn operador vale null y operar normalmente si no (como en SQL) Por ejemplo: using System;

int? i = 1; class OperadorFusin


int? j = 2; {
int? z = null; static void Main()
{
Console.WriteLine(i + j); // Imprime 3, que es la suma de los valores de i y j
Console.WriteLine(i + z); // No imprime nada, puesto que i+z devuelve null. string s = null, t = null;
Console.WriteLine(s ?? t ?? "s y t son cadenas nulas");
t = "Ahora t no es nula";
En cualquier caso, debe tenerse en cuenta que no es necesario preocuparse por cmo Console.WriteLine(s ?? t ?? "s y t son cadenas nulas");
se comportarn los operadores redefinidos ante las versiones anulables de las s = "Ahora s no es nula";
estructuras, pues su implementacin la proporcionar automticamente el compilador. Console.WriteLine(s ?? t ?? "s y t son cadenas nulas");
Por ejemplo, si se ha redefinido el operador + para una estructura, cuando se aplique }
entre versiones anulables del tipo simplemente se mirar si alguno de los operandos es }
null, devolvindose null si es as y ejecutndose la redefinicin del operador si no.
Siendo el resultado de la ejecucin del cdigo el siguiente:
s y t son cadenas nulas
Operador de fusin (??) Ahora t no es nula
Ahora s no es nula
Para facilitar el trabajo con variables anulables, C# 2.0 proporciona un nuevo operador
de fusin ?? que retorna su operando izquierdo si este no es nulo y el derecho si lo es.
Este operador se puede aplicar tanto entre tipos anulables como entre tipos referencia o Modificadores de visibilidad de bloques get y set
entre tipos anulables y tipos no anulables. Ejemplos de su uso seran los siguientes:
C# 2.0 permite definir diferentes modificadores de visibilidad para los bloques get y set
int z; de las propiedades e indizadores, de manera que podrn tener propiedades en las que el
int? enteronulo = null; bloque get sea pblico pero el set protegido. Por ejemplo:
int? enterononulo;
string s = null; class A
{
z = enteronulo ?? 123; // Vlido entre tipo anulable y no anulable. z=123 string miPropiedad;
enterononulo = enteronulo ?? 123; // enterononulo = 123. public string MiPropiedad
z = (int) (enteronulo ?? enterononulo); // Vlido entre tipos anulables. z=123 {
Console.WriteLine(s ?? "s nula"); // Escribe s nula. get { return miPropiedad; }
protected set { miPropiedad = value; }
Lo que obviamente no se permite es aplicarlo entre tipos no anulables puesto que no }
tiene sentido en tanto que nunca ser posible que alguno de sus operandos valga null. }
Tampoco es aplicable entre tipos referencia y tipos valor ya que el resultado de la
Lgicamente, la visibilidad de los bloques get y set nunca podr ser superior a los de la
expresin podra ser de tipo valor o de tipo referencia dependiendo de los operandos que
propia propiedad o indizador al que pertenezcan. Por ejemplo, una propiedad protegida
en concreto se les pase en cada ejecucin de la misma, y por tanto no podra
nunca podr tener uno de estos bloques pblico.
almacenarse con seguridad en ningn tipo de variable salvo object. Por tanto, todas las
siguientes asignaciones son incorrectas (excepto las de las inicializaciones, claro):
Adems, aunque de este modo se puede configurar la visibilidad del bloque get o del
int x = 1; bloque set de una cierta propiedad o indizador, no se puede cambiar la de ambos. Si
int? y = 1; interesase, para qu se dio a la propiedad o indizador ese modificador de visibilidad?
int z = 0;
string s = null;

z = 1 ?? 1; // No vlido entre tipos valor


Clases estticas
z = x ?? 123; // De nuevo, no vlido entre tipos valor
Para facilitar la creacin de clases que no estn destinadas a ser instanciadas o derivadas
z = s ?? x; // No vlido entre tipos referencia y tipos valor. (abstract sealed) sino tan slo a proporcionar ciertos mtodos estticos (clases fbrica,
z = s ?? y; // Ni aunque el tipo valor sea anulable.
utilizadas para crear ciertos tipos de objetos, clases utilidad que ofrezcan acceso a

Jos Antonio Gonzlez Seco Pgina 291 Jos Antonio Gonzlez Seco Pgina 292
El lenguaje de programacin C# Tema 21: Novedades de C# 2.0 El lenguaje de programacin C# Tema 21: Novedades de C# 2.0

ciertos servicios de manera cmoda, sin tener que instanciar objetos, etc.), C# 2.0 {
Mtodo();
permite configurar dicha semntica en la propia declaracin de las mismas incluyendo }
en ellas el modificador static. As se forzar a que el compilador asegure el seguimiento static void Mtodo()
de dicha semntica. Por ejemplo, el cdigo que a continuacin se muestra ser vlido: {
Console.WriteLine("Josan.System.A.Mtodo()");
A.Mtodo();
static public class ClaseEsttica }
{ }
static public object CrearObjetoTipoA() }
{}
class A
static public object CrearObjetoTipoB() {
{} static void Mtodo()
} {
Console.WriteLine("A.Mtodo()");
Por el contrario, el siguiente cdigo no compilara ya que la clase ClaseEstticaConError }
se ha definido como esttica pero tiene un mtodo no esttico llamado MtodoInstancia, }
se la est intentando instanciar, y se est intentando derivar de ella una clase Hija:
Resulta imposible llamar desde el mtodo Mtodo() de la clase Josan.A a su homnimo en
static public class ClaseEstticaConError la clase A del espacio de nombres global, por lo que la llamada A.Mtodo() producir un
{ bucle recursivo infinito y la salida del programa ser del tipo:
static public object CrearObjetoTipoA()
{} Josan.System.A.Mtodo()
Josan.System.A.Mtodo()
static public object CrearObjetoTipoB() Josan.System.A.Mtodo()
{} ...

public void MtodoInstancia(string texto) // Error: mtodo no esttico.


{ Para solucionar esto, C# 2.0 introduce la posibilidad de hacer referencia al espacio de
Console.WriteLine(MtodoInstancia({0}), texto); nombres global a travs de un alias predefinido denominado global y un calificador ::
} que se usa de la forma <inicio>::<referencia> y permite indicar el punto de <inicio> en la
jerarqua de espacios de nombres a partir del que se ha de resolver la <referencia> al tipo
static void Main() o espacio de nombres indicada. Estas referencias se podrn usar en cualquier parte en la
{
// Error: Se est instanciado la clase esttica que se espere un nombre de tipo o espacio de nombres, tal como una sentencia using, la
ClaseEstticaConError objeto = new ClaseEstticaConError(); creacin de un objeto con new,... As, el ejemplo anterior podra resolverse como sigue:
objeto.MtodoInstancia(Test);
} using System;
}
namespace Josan
class Hija: ClaseEstticaConError {} // Error: Se est derivando de clase esttica {
class A
{
static void Main(string[] args)
Referencias a espacios de nombres {
Mtodo();
}
Alias global y calificador :: static void Mtodo()
{
En C# 1.1 puede producirse conflictos a la hora de resolver las referencias a ciertas Console.WriteLine("Josan.System.A.Mtodo()");
global::A.Mtodo();
clases cuando en anidaciones de espacios de nombres existan varias clases y/o espacios }
de nombres homnimos. Por ejemplo, en el siguiente cdigo: }
}
using System;
class A
namespace Josan {
{ public static void Mtodo()
class A {
{ Console.WriteLine("A.Mtodo()");
static void Main(string[] args) }

Jos Antonio Gonzlez Seco Pgina 293 Jos Antonio Gonzlez Seco Pgina 294
El lenguaje de programacin C# Tema 21: Novedades de C# 2.0 El lenguaje de programacin C# Tema 21: Novedades de C# 2.0

} }

Y ahora ya s que se obtendra la salida buscada: Para usar ambas clases a la vez en un fichero extern.cs bastara escribirlo como sigue:

Josan.System.A.Mtodo() extern alias X;


A.Mtodo() extern alias Y;

class Extern
{
Alias externos static void Main()
{
C# 2.0 va un paso ms all en lo referente a resolver conflictos de ambigedades en los X::MiClase objeto = new X::MiClase();
objeto.F();
nombres de los identificadores respecto a C# 1.X. Ahora no solo se pueden usar clases
con el mismo nombre siempre y cuando se hallen en espacios de nombres diferentes Y::MiClase objeto2 = new Y::MiClase();
sino que tambin se permite usar clases con el mismo nombre y espacio de nombres que objeto2.F();
se hallen en diferentes ensamblados. Para ello se usan los denominados alias externos. }
}
Antes de las importaciones de espacios de nombres (using) se puede incluir lneas con
Y compilar todo el conjunto con:
la siguiente sintaxis:
extern alias <nombreAlias>; csc extern.cs /r:X=miclase1.dll /r:Y=miclase2.dll

Con esto se estar diciendo al compilador que durante las comprobaciones de sintaxis Al ejecutarlo podr comprobarse que la salida demuestra que cada llamada se ha hecho
admita a la izquierda del calificador :: las referencias al alias de espacio de nombres a uno de los diferentes tipos MiClase:
cuyo nombre se le indica.. Cada una de estas referencias se correspondern con uno o F() de mi miclase1.cs
varios ensamblados externos dentro de cuyos espacios de nombres se intentarn resolver F() de mi miclase2.cs
la referencia indicada a la derecha del ::. La correspondencia entre estos ensamblados y
sus alias se indicarn al compilador de C# mediante parmetros que se podrn pasar en Ntese que es perfectamente vlido asociar un mismo alias a varios ensamblados y
la lnea de comandos al hacerles referencia mediante la opcin /r siguiendo la sintaxis varios alias a un mismo ensamblado. Lo que no se puede es incluir varias definiciones
<nombreAlias>=<rutaEnsamblado> para los valores de la misma; o en el caso de VS.NET de alias en una misma opcin /r, sino que para ello habra que utilizar opciones /r
2005, desde la ventana de propiedades de la referencia al ensamblado en la solucin. diferentes tal como se ha hecho en el ejemplo. Es decir, la siguiente llamada al
compilador no sera correcta:
Por ejemplo, se puede tener un fichero miclase1.cs con el siguiente contenido:
csc extern.cs /r:X=miclase1.dll,Y=miclase2.dll
using System;

public class MiClase Supresin temporal de avisos


{
public void F()
{ Al conjunto de directivas del preprocesador de C# se ha aadido una nueva en C# 2.0
Console.WriteLine("F() de mi miclase1.cs"); que permite desactivar la emisin de determinados avisos durante la compilacin de
} determinadas secciones del cdigo fuente as como volverlo a activar. Su sintaxis es:
}
#pragma warning <estado> <cdigoAviso>

Y otro miclase2.cs con una clase homnima a MiClase:


Donde <cdigoAviso> es el cdigo del aviso a desactivar o reactivar, y <estado> valdr
using System; disable o restore segn si lo que se desea es desactivarlo o rehabilitarlo. Por ejemplo, al
compilar el siguiente cdigo el compilador slo informar de que no se usa la variable
public class MiClase b, pero no se dir nada de la variable debido a que se le ha suprimido dicho mensaje de
{ aviso (su cdigo es el 649) a travs de la directiva #pragma warning:
public void F()
{
Console.WriteLine("F() de mi miclase2.cs"); class ClaseConAvisos
} {
# pragma warning disable 649

Jos Antonio Gonzlez Seco Pgina 295 Jos Antonio Gonzlez Seco Pgina 296
El lenguaje de programacin C# Tema 21: Novedades de C# 2.0 El lenguaje de programacin C# Tema 21: Novedades de C# 2.0

public int a; campos no era fcil en C# 1.X ya que al ser stas objetos de tipo referencia, en realidad
# pragma warning restore 649 sus campos almacenaban referencias a los datos de la tabla y no los propios datos.
public int b;
} C# 2.0 soluciona este problema dando la posibilidad de definir en las estructuras
campos de tipo tabla que se almacenar ocupando posiciones de memoria contiguas.
En cualquier caso, hay que sealar que como normal general no se recomienda hacer Para ello, basta preceder del modificador fixed la definicin del campo. Por ejemplo:
uso de esta directiva, ya que el cdigo final debera siempre escribirse de manera que
public struct Persona
no genere avisos. Sin embargo, puede venir bien durante la depuracin de aplicaciones {
o la creacin de prototipos o estructuras iniciales de cdigo para facilitar el aislamiento public unsafe fixed char Nombre[100];
de problemas y evitar mensajes de aviso ya conocidos que aparecern temporalmente. public int Edad;
}

Atributos condicionales De este modo, los objetos de la estructura Persona siempre ocuparn 104 bytes (100 por
la tabla correspondiente al nombre da persona y 4 por los bytes del tipo int de la edad)
El atributo Conditional que en C# 1.X permita especificar si compilar ciertas llamadas a
mtodos en funcin de valores de constantes de preprocesador ha sido ahora ampliado No obstante, hay que sealar que este uso del modificador fixed slo funciona en las
para tambin poderse aplicar a la utilizacin de atributos. En este caso, si la utilizacin definiciones de campos que sean tablas unidimensionales planas (vectores) de
del atributo para un determinado fichero no se compila por no estar definida la constante estructuras en contextos inseguros, y no en campos de clases, ni en contextos seguros,
de la que depende, ninguna clase del mismo a la se aplique lo almacenar entre sus ni con tablas multidimensionales o dentadas.
metadatos. As, si tenemos un fichero test.cs con la siguiente definicin de atributo:

using System; Modificaciones en el compilador


using System.Diagnostics;
Control de la versin del lenguaje
[Conditional(DEBUG)]
public class TestAttribute : Attribute {}
A partir de la versin 2.0 del .NET Framework, el compilador de C# proporciona una
Y con l compilamos un fichero miclase.cs con el contenido: nueva opcin /langversion que permite restringirle las caractersticas del lenguaje que
se permitirn usar durante la compilacin para solo permitir las de una cierta versin o
#define DEBUG estndar del lenguaje. Por ahora, los valores que esta opcin admite son los siguientes:
[Test]
class MiClase {} Valor Descripcin
default Utilizar las caractersticas de la versin ms actual del lenguaje para la que el
En el ensamblado resultante la clase MiClase incluir el atributo Test entre sus metadatos compilador se haya preparado. Es lo que se toma por defecto
por estar definida la constante DEBUG dentro del fichero en que se usa el atributo. Pero ISO-1 Usar las caractersticas de la versin 1.X del lenguaje estandarizada por ISO.
por el contrario, si adems compilsemos otro fichero miclase2.cs como el siguiente: Por lo tanto, no se permitir el uso de genricos, mtodos annimos, etc.17
#undef DEBUG Tabla 21: Identificadores de versiones del lenguaje
[Test]
class MiClase2 {}
Por ejemplo, dado el siguiente fichero version2.cs que usa tipos parciales:
Entonces la MiClase2 del ensamblado resultante no almacenara el atributo Test entre sus
partial class Version2
metadatos por no estar definida la constante DEBUG en el fichero donde se declar. {
static void Main()
{
Incrustacin de tablas en estructuras
}
}
Al interoperar con cdigo nativo puede ser necesario pasar a ste estructuras de un
tamao fijo ya que el cdigo nativo puede haber sido programado para esperar un cierto Si lo intentamos compilar como sigue:
tamao concreto de las mismas. Hacer esto con estructuras que tengan tablas como
17
En la Beta 1 del .NET Framework 2.0 no se detecta el uso de tipos anulables.

Jos Antonio Gonzlez Seco Pgina 297 Jos Antonio Gonzlez Seco Pgina 298
El lenguaje de programacin C# Tema 21: Novedades de C# 2.0 El lenguaje de programacin C# Tema 21: Novedades de C# 2.0

A partir de .NET 2.0, el compilador de C# da la opcin de enviar automticamente a


csc version2.cs /langversion:iso-1 Microsoft los errores internos del mismo que durante su uso pudiesen surgir para as
facilitar su deteccin y correccin a Microsoft (errores de csc.exe y no del cdigo que
Obtendremos un mensaje de error indicndonos que el uso de tipos parciales no est se pretenda compilar) Por ejemplo, si se intenta compilar el siguiente cdigo con el
permitido en la versin 1.X estndar de C#: compilador de C# 2.0 de la Beta 1 del Microsoft.NET Framework SDK 2.0:
version2.cs(1,1): error CS1644: Feature 'partial types' cannot be used
using System;
because it is not part of the standardized ISO C# language
using System.Collections;
specification
class Error
{
Control de la plataforma de destino IEnumerator GetEnumerator()
{
try {}
Dado que a partir de la versin 2.0 de Microsoft.NET se soportan las arquitecturas de catch { yield break; }
64 bits de Intel y AMD, el compilador de C# admite una nueva opcin /platform por finally {}
medio de la que se le puede indicar el tipo de plataforma hacia la que se desea dirigir el }
cdigo que se compila. Los valores que admite son los siguientes:
static void Main()
{}
Valor Significado }
Anycpu Compilar para cualquier plataforma. Es lo que por defecto se toma
X86 Compilar para los procesadores de 32 bits compatibles con Intel El compilador sufrir un error interno que impedir finalizar el proceso de compilacin:
X64 Compilar para los procesadores de 64 bits compatibles con AMD
Itanium Compilar para los procesadores de 64 bits Intel Itanium error CS0583: Internal Compiler Error (0xc0000005 at address
56D1EEC4): likely culprit is 'TRANSFORM'.
Tabla 22: Identificadores de plataforma de destino
An internal error has occurred in the compiler. To work around
this problem, try simplifying or changing the program near the
Gracias a esta opcin, si el cdigo se dirige hacia una plataforma de 64 bits y se intenta locations listed below. Locations at the top of the list are
ejecutar bajo una plataforma de 32 bits saltar un mensaje de error como el siguiente: closer to the point at which the internal error occurred.

error.cs(6,14): error CS0584: Internal Compiler Error: stage


'TRANSFORM' symbol 'Error.GetEnumerator()'
error.cs(6,14): error CS0584: Internal Compiler Error: stage 'BIND'
symbol 'Error.GetEnumerator()'
error.cs(6,14): error CS0584: Internal Compiler Error: stage 'COMPILE'
symbol 'Error.GetEnumerator()'
error.cs(6,14): error CS0584: Internal Compiler Error: stage 'COMPILE'
symbol 'Error.GetEnumerator()'
error.cs(6,14): error CS0584: Internal Compiler Error: stage 'COMPILE'
symbol 'Error.GetEnumerator()'
Por el contrario, si se dirige a plataformas de 32 bits y se intenta ejecutar en una de 64 error.cs(4,7): error CS0584: Internal Compiler Error: stage 'COMPILE'
bits, automticamente Windows simular la ejecucin del mismo en un entorno de 32 symbol 'Error'
error.cs(114297930,1): error CS0584: Internal Compiler Error: stage
bits a travs de su funcionalidad WOW (Windows On Windows) 'COMPILE' symbol '<global namespace>'
error.cs: error CS0586: Internal Compiler Error: stage 'COMPILE'
Aunque en principio lo ideal es realizar el cdigo lo ms independiente posible de la error CS0587: Internal Compiler Error: stage 'COMPILE'
plataforma, se da esta opcin porque cuando se opera con cdigo inseguro se pueden error CS0587: Internal Compiler Error: stage 'BEGIN'
tener que realizar suposiciones sobre las caractersticas concretas de la plataforma hacia
la que se dirige el cdigo, fundamentalmente en lo que respecta al tamao ocupado en Para realizar el envo del error a Microsoft se puede usar la opcin /errorreport del
memoria por ciertos tipos de datos que se manipulen mediante aritmtica de punteros. compilador, la cual admite los siguientes valores:

Valor Descripcin
Envo automtico de errores a Microsoft None No enviar el error a Microsoft. Es lo que se hace por defecto.
Prompt Preguntar si enviar el error. Esto har que durante la compilacin se muestre
al usuario una ventana como la siguiente en la que se le pide permiso para la

Jos Antonio Gonzlez Seco Pgina 299 Jos Antonio Gonzlez Seco Pgina 300
El lenguaje de programacin C# Tema 21: Novedades de C# 2.0 El lenguaje de programacin C# Tema 21: Novedades de C# 2.0

realizacin del envo y se le de la opcin de inspeccionar la informacin que


csc test.cs /warnaserror:28
se enviar a Microsoft por si desea asegurarse de que no se vayan a enviar
datos sensibles sobre el equipo o el cdigo fuente compilado (puede que se
les enve parte del mismo para facilitarles la deteccin del error): As mismo, a VS.NET se le ha aadido la posibilidad de configurar los avisos a tratar
como errores y los avisos a filtrar a travs de la hoja de propiedades del proyecto. En
concreto, desde sus controles Configuration Properties Build Treat Warnings
as Errors y Configuration Properties Build Suppress specific warnings.

Visibilidad de los recursos

Las opciones /linkres y /res permiten ahora especificar la visibilidad de los recursos
que se enlazarn o incrustarn en el ensamblado generado, de modo que se pueda
configurar no permitir acceder a los mismos desde otros ensamblados. Para ello, tras el
identificador del recurso se puede incluir una de estas partculas separada por una coma:

Valor Descripcin
public Podr accederse a los recursos del ensamblado libremente desde cualquier
otro ensamblado. Es lo que se toma por defecto
private Slo podr accederse a los recursos desde el propio ensamblado
Send Enviar automticamente la notificacin a Microsoft, sin necesidad de tener Tabla 24: Indicadores de visibilidad de recursos
que confirmarlo a travs de la ventana que la opcin anterior muestra.
Tabla 23: Opciones para el envo de errores a Microsoft Por ejemplo:

csc test.cs /linkres:misrecursos.resources,recursos.cs,private


Lo ideal es combinar esta opcin con la opcin /bugreport ya conocida, para que as
el informe del error sea ms rico y la deteccin de sus causas sea ms sencilla. Como en
este caso la informacin que se enviar a Microsoft es mayor y por consiguiente puede
que se tarde mucho ms envirsela a travs de Internet (sobre todo si se usa un mdem
Firma de ensamblados
convencional para acceder a la Red), se informar antes al usuario de esta circunstancia
y se le preguntar si est seguro de realizar el envo. Si confirma, aparecer una ventana El compilador de C# 2.0 incorpora opciones relacionadas con la firma de ensamblados
como la siguiente con informacin sobre el progreso del envo del informe de error: que hasta ahora venan siendo proporcionada de manera externa al mismo, pues como al
fin y al cabo compilar un ensamblado con firma o sin ella no es ms que una opcin de
compilacin, la forma ms lgica de indicarlo es a travs de opciones del compilador.

Para poder instalar un ensamblado en el GAC es necesario que tenga un nombre seguro;
es decir, que est firmado. Este proceso de firma consiste en utilizar una clave privada
para cifrar con ella el resultado de aplicar un algoritmo de hashing al contenido del
ensamblado, e incluir en su manifiesto la clave pblica con la que luego poder descifrar
la firma y comprobar que el ensamblado no se ha alterado y procede de quien se espera.

Para generar la pareja de claves pblica-privada se puede utilizar la herramienta sn.exe


(singing tool) del SDK, que se puede usar como sigue para generar aleatoriamente un
par de claves y guardarlas en un fichero (por convenio se le suele dar extensin .snk):

sn k misClaves.snk
Concretizacin de avisos a tratar como errores
Luego, la firma del ensamblado se realizar especificando durante la compilacin la ruta
La opcin /warnaserror permite ahora que se le concreten los avisos que se desea que del fichero en el que se almacenan las claves a utilizar para realizar la firma a travs de
se traten como errores. Por ejemplo, para decirle que slo considere como errores los la nueva opcin /keyfile incluida en el compilador de C# 2.0 para estos menesteres:
avisos con cdigo CS0028:

Jos Antonio Gonzlez Seco Pgina 301 Jos Antonio Gonzlez Seco Pgina 302
El lenguaje de programacin C# Tema 21: Novedades de C# 2.0 El lenguaje de programacin C# Documentacin de referencia

csc test.cs /keyfile:misClaves.snk


Documentacin de referencia
En lugar de especificar las claves como fichero, tambin es posible instalarlas en un
contenedor pblico y referenciar al mismo durante la compilacin. Para instalarlas en
el contenedor se usa de nuevo la herramienta sn.exe, indicndole ahora la opcin i Bibliografa
seguida de la ruta del fichero y el nombre del contenedor donde instalarla. Por ejemplo:
La principal y ms exhaustiva fuente de informacin sobre C# es la C# Language
sn i misClaves.snk contenedorJosan Specification, originalmente escrita por Anders Hejlsberg, Scott Wiltamuth y Peter
Golde. Incluye la especificacin completa del lenguaje, y su ltima versin siempre
Y al compilar se referenciara al contenedor con la opcin /keycontainer de csc: puede descargarse gratuitamente de http://www.msdn.microsoft.com/vcsharp/language.

csc test.cs /keyname:contenedorJosan Sin embargo, si lo que busca son libros que expliquen el lenguaje con algo menos de
rigurosidad pero de forma ms amena, sencilla de entender y orientada a su aplicacin
El uso de las opciones /keyfile y /keycontainer son ahora la prctica recomendada prctica, puede consultar la siguiente bibliografa:
por Microsoft para la firma de los ensamblados, en detrimento de los antiguos atributos
de ensamblado AssemblyKeyFile y AssemblyKeyName que antes se usaban para estas A programmers introduction to C# escrito por Eric Gunnerson y publicado por
labores. De hecho, si se insiste en seguir utilizndolos el compilador emitir mensajes Apress en 2000.
de aviso informando de la inconveniencia de hacerlo. Lgicamente, en los ensamblados C# and the .NET Framework, escrito por Andrew Troelsen y publicado por Apress
generados utilizando estas opciones ya no estarn presentes dichos atributos. en 2001
C# Essentials, escrito por Beb Albahari, Peter Drayton y Brand Merril y
Las opciones /keyfile y /keycontainer se pueden combinar con /delaysign para publicado por OReilly en 2000.
conseguir que el proceso de firma no se haga al completo, sino que en vez de calcularse C# Programming with the Public Beta, escrito por Burton Harvey, Simon
y guardarse la firma del ensamblado entre sus metadatos slo se reserve en los mismos Robinson, Julian Templeman y Karli Watson y publicado por Wrox Press en 2000.
el espacio necesario para posteriormente inclursela y se les aada la clave pblica con Inside C#, escrito por Tom Archer y publicado por Microsoft en 2000
la que se podrn instalar en el GAC para realizarles pruebas. A esto se le conoce como
Presenting C#, escrito por Christoph Wille y publicado por Sams Publishing en
firma parcial, y para posteriormente realizar la firma completa al ensamblado se puede
2000.
utilizar la utilidad sn.exe para refirmarlo, ya sea en base a un fichero de claves o a un
Professional C#, escrito por Simon Robinson, Burt Harvey, Craig McQueen,
contenedor pblico tal y como a continuacin se muestra con los siguientes ejemplos:
Christian Nagel, Morgan Skinner, Jay Glynn, Karli Watson, Ollie Cornes, Jerod
sn.exe R test.dll misClaves.snk Moemeka y publicado por Wrox Press en 2001.
sn.exe Rc test.dll contenedorJosan Programming C#, escrito por Jesse Liberty y publicado por OReilly en 2001

Nuevamente, en .NET 1.X la firma retrasada de los ensamblados se realizaba a travs de De entre todos estos libros quizs el principalmente recomendable tras leer esta obra
un atributo de ensamblado (AssemblyDelaySign), cosa que ahora no se recomienda y pueda ser Professional C#, pues es el ms moderno y abarca numerosos conceptos
que provocar la aparicin de mensajes de aviso durante la compilacin si se utiliza. sobre la aplicacin de C# para acceder a la BCL.

Por otra parte, en relacin con los libros publicados en 2000 hay que sealar que fueron
publicados para el compilador de C# incluido en la Beta 1 del SDK, por lo que no tratan
los aspectos nuevos introducidos a partir de la Beta 2 y puede que contengan cdigo de
ejemplo que haya quedado obsoleto y actualemente no funcione.

Informacin en Internet sobre C#

Aunque la bibliografa publicada sobre C# al escribir estas lneas es relativamente


escasa, no ocurre lo mismo con la cantidad de material online disponible, que cada vez
va inundando ms la Red. En esta seccin se recogen los principales portales, grupos de
noticias y listas de distribucin dedicados al lenguaje. Seguramente cuando lea estas
lneas habrn surgido muchos ms, puede usar la lista ofrecida para encontrar enlaces a
los nuevos a partir de los que aqu se recogen.

Jos Antonio Gonzlez Seco Pgina 303 Jos Antonio Gonzlez Seco Pgina 304
El lenguaje de programacin C# Documentacin de referencia El lenguaje de programacin C# Documentacin de referencia

Portales microsoft.public.dotnet.framework.aspnet
microsoft.public.dotnet.framework.aspnet.mobile
Si busca un portal sobre C# escrito en castellano el nico que le puedo recomendar es microsoft.public.dotnet.framework.aspnet.webservices
El Rincn en Espaol de C# (http://tdg.lsi.us.es/~csharp), que es el primero dedicado microsoft.public.dotnet.framework.clr
a este lenguaje escrito en castellano. Ha sido desarrollado por profesores de la Facultad microsoft.public.dotnet.framework.component_services
de Informtica y Estadstica de Sevilla, y entre los servicios que ofrece cabe destacar microsoft.public.dotnet.framework.documentation
sus aplicaciones de ejemplo, FAQ, seminario on-line y lista de distribucin de correo. microsoft.public.dotnet.framework.interop
microsoft.public.dotnet.framework.odbcnet
Si no le importa que el portal est en ingls, entonces es de obligada visita el .NET microsoft.public.dotnet.framework.perfomance
Developers Center (http://www.msdn.microsoft.com/net) de Microsoft, ya que al ser microsoft.public.dotnet.framework.remoting
los creadores del C# y la plataforma .NET su informacin sobre los mismos suele ser la microsoft.public.dotnet.framework.sdk
ms amplia, fiable y actualizada. Entre los servicios que ofrece cabe destacar la microsoft.public.dotnet.framework.setup
posibilidad de descargar gratuitamente el .NET Framework SDK y Visual Studio microsoft.public.dotnet.framework.windowsforms
.NET18, sus numerosos vdeos y artculos tcnicos, y sus ejemplos de desarrollo de
microsoft.public.dotnet.languages.csharp
software profesional de calidad usando estas tecnologas.
microsoft.public.dotnet.languages.jscript
Aparte del portal de Microsoft, otros portales dedicados a C# que pueblan la Red son: microsoft.public.dotnet.languages.vb
microsoft.public.dotnet.languages.vb.upgrade
C# Corner (http://www.c-sharpcorner.com) microsoft.public.dotnet.languages.vc
C# Help(http://www.csharphelp.com) microsoft.public.dotnet.languages.vc.libraries
C# Station (http://www.csharp-station.com) microsoft.public.dotnet.samples
Codehound C# (http://www.codehound.com/csharp) microsoft.public.dotnet.scripting
csharpindex.com (http://www.csharpindex.com) microsoft.public.dotnet.vsa
Developersdex (http://www.developersdex.com/csharp) microsoft.public.dotnet.xml
.NET Wire (http://www.dotnetwire.com) microsoft.public.vsnet.debuggin
microsoft.public.vsnet.documentation
microsoft.public.vsnet.enterprise.tools
Grupos de noticias y listas de correo microsoft.public.vsnet.faqs
microsoft.public.vsnet.general
Microsoft ha puesta a disposicin de los desarrolladores numerosos grupos de noticias microsoft.public.vsnet.ide
dedicados a resolver dudas sobre C#, .NET y Visual Studio.NET. Los ofrecidos en microsoft.public.vsnet.samples
castellano son: microsoft.public.vsnet.servicepacks
microsoft.public.vsnet.setup
microsoft.public.vsnet microsoft.public.vsnet.visual_studio_modeler
microsoft.public.es.csharp microsoft.public.vsnet.vsa
microsoft.public.vsnet.vsip
Respecto a los proporcionados en ingls, sealar que aunque algunos de ellos se microsoft.public.vsnet.vss
recogen en la opcin Online Community de la pgina de inicio de VS.NET, la lista
completa da a da crece cada vez ms y en el momento de escribir estas lneas era: En realidad, de entre todos estos grupos de noticias slo estn exclusivamente dedicados
a C# microsoft.public.es y csharp microsoft.public.dotnet.languages.csharp, pero a
microsoft.public.dotnet.academic medida que vaya adentrandose en el lenguaje descubrir que los dedicados a los
microsoft.public.dotnet.distributed_apps diferentes aspectos de .NET y VS.NET tambin le resultarn de incalculable utililidad.
microsoft.public.dotnet.faqs
microsoft.public.dotnet.general En lo referente a listas de correo, si busca una lista en castellano la ms recomendable
microsoft.public.dotnet.framework es la del Rincn en Espaol de C# (http://tdg.lsi.us.es/csharp) antes mencionada;
microsoft.public.dotnet.framework.adonet mientras que si no le importa que estn en ingls, entonces puede consultar las ofrecidas
por DevelopMentor (http://www.discuss.develop.com)
18
El ltimo slo para subscriptores MSDN Universal, aunque los no subcriptores pueden pedirlo en este
portal gratuitamente y Microsoft se lo enviar por correo ordinario.

Jos Antonio Gonzlez Seco Pgina 305 Jos Antonio Gonzlez Seco Pgina 306
El lenguaje de programacin C# Documentacin de referencia

Jos Antonio Gonzlez Seco Pgina 307

Das könnte Ihnen auch gefallen