ß k.
ì
• • • •
rr ::::
G#
La biblia de
C#
Jeff Ferguson, Brian Patterson, Jason Beres, Pierre Boutquin y Meeta Gupta
Todos los nombres propios de programas, sistemas operativos, equipos hardware, etc. que aparecen en este libro son marcas registradas de sus respectivas compañías u organizaciones.
R e s e r v a d o s t od os los d e r e c h o s de esta ob ra esta
K1 c o n t e n i d o
p r o t e g i d o p o r la l e v . q u e
establece penas de prisión v o multas, a d e m a s de las c o r r e s p o n d i e n t e s i n d e m n i z a c i o n e s p o r daños v perjuicios, para quienes reprodujeren, plagiaren, distribuveren o c o m u n ic a s e n publi c a m e n t e . en to d o o en parte, u n a o b ra literaria, artística
o
científica,
o
su
transformación,
in te rp reta c ió n o e j e c u c ió n artística lijada en cualquier
tipo
de
soporte
o comunicada
a
t r a v é s d e c u a l q u i e r m e d i o , s i n la p r e c e p t i v a autorización
Copyright <' 2003 by Anaya Multimedia. Original English language edition copyright ( 2002 by Hungry Minds. Inc. All rights reserved including the right o f reproduction in whole or in part in any form. This edition published by arrangement with the original publisher. Hungry Minds. Inc. Edición española: (
E DI C IO NE S A NA YA M U L T I M E D I A ( G R U P O ANAYA. S . A ) . 200 Juan Ignacio Luca de Tena. 15. 2X027 Madrid Depósito legal: M. 3.033 - 2003 ISBN: 84-415-14X4-4 Printed in Spain Imprime: Imprime Artes Gráfi cas Guemo. S.L. Febrero. 32. 2X022 Madrid.
Para mi fam ilia y amigos. J e f f h'erguson liste libro está dedicado a mi tío, Brian Weslon, al que no pareció ’tarle cuando fui de visita v pa se todo el día con su TRS-HO Moda! II. Brian Patterson A N itin , que fue la motivación. M eeta ( iupta
Agradecimientos Jeff Ferguson: Pocos libros de este t amaño y extensión son el fruto de un solo individuo y éste no es una excepción. Estoy en deuda con mu ch a gente por su ayuda y apoyo mientras escribía este libro. En primer lugar, debo dar las g r a cias a mis padres por la educación que recibí. Sin sus paternales consejos no me habría convertido en la persona que soy y no habría podido compl et ar ninguno de mis trabajos. Siempre os estaré agradecido, no sólo a vosotros, sino a toda la familia por el amo r y apoyo que siempre he recibido. Me gu st arí a dar las gracias a todo el mundo de Wiley por su dirección en la elaboración de este material. Gracias. Andrea Boucher, Sharon Cox. Eric Nevvman y Chris Webb. por gui arme por el intimidador mundo de la publicación de libros técnicos. Gra ci as también a Rol f Crozier. que discutió conmigo este proyecto en primer lugar en sus primeros días. Debo dar las gracias especialmente a mi colega Bob k n u ts o n. que revisó los borr adores del material de este libro. Graci as a Greg Frankenfield y a Paul Fridman por crear una excelente o rg an i zación consultora bas ada en Microsoft que me permite t raba jar en los provectos de mis clientes j un to en los míos. El crecimiento técnico que he ex peri ment ado durante mi estancia en Magenic ha sido incalculable. Esto es par a que continúe el éxito de Magenic. Graci as a todo el mundo de las listas de correo y grupos de noticias de D O T N E T en Internet. Estoy aprendiendo mucho sobre N E T Framcvvork y C# simplemente leyendo vuestros correos. Los envíos de acá para allá del banter me han dado una mayor comprensión de c ómo encajan todas estas nuevas piezas.
Brian Patterson: Me gust arí a dar las gracias a mi esposa. Aimee. por p er do narme todas esas horas que pasé escondido en el ordenador par a que pudiera completar este libro. Un agradecimiento especial a Steve Cisco por su duro t r a b a jo en este libro, que abrió camino par a el resto de nosotros; a Sharon Cox. la editora de adquisiciones, que siempre me mant uvo en el buen camino; al editor de proyecto. Eric Nevvman. por a gua nt ar todos mis regates; y al editor de la serie. Michael Lañe Th omas . que revisó todos y cada uno de los capítulos, haciendo algunas sugerencias muy buenas y propor ci onando una apreciable comprensión de Microsoft y N E T Framcvvork Pierre Boutquin: Se necesitó mucho t rabajo p ar a crear este libro v no sólo de la gente que aparece en la portada. Debo dar las graci as especialmente al equipo de Wiley por su tremendo esmero por producir un libro de calidad. Los revisores se merecen casi todo el crédito por hacerme parecer un escritor competente. Por último, este trabaj o no h abr ía sido posible sin el apoyo de mi familia y amigos: Sandra. Andrea. Jennifer y Paul. Tindy y Doel. Marcel y Diana Ban. Mar garet Fekete. y John y Nadine Marshall. Meeta Gupta: Agradezco a Anita que me diera la oportunidad. Pero mi mavor agradecimiento es par a Nitin por. bueno, por todo.
6
Sobre los autores Jeff Ferguson es consejero superior de Magenic Technologies, una c o m p a ñía consul tora de software dedicada a resolver probl emas empresariales usando exclusivamente herramientas y tecnología de Microsoft. Ha sido p ro g ra ma do r de software profesional desde 1989 y ha desarrollado software par a Unix. DOS y W i n d o w s e m p l e a n d o C. C + + y C#. P u e d e e n v i a r un e - m a i l a J e f f en JeffF <7 mageni c.com (no olv ide incluir las tres "F" en el nombre de la dirección). Brian Patterson actualmente t rabaj a par a Affína. Inc.. como jefe del equipo técnico, donde suele t raba jar con C ++ en H P -UX o en el desarrollo de Wi ndows con cualqui er versión de los lenguajes de Visual Studio. Brian ha estado es cri biendo par a varias publicaciones sobre Visual Basic desde 1994 y ha co-escrito varios libros relacionados con NET. incluy'cndo Mi g r a t m g lo Visual Basic .N E T y N E T Ent erpri se De vel opme nl wilh VB.NET. Puede encontr ársele g e ne r al mente contri buyendo en los grupos de noticias de M S D N o puede ponerse en contacto con el por e-mail en BrianDPattersonY/ msn.com. Jason Beres ha sido p ro gr a ma do r de software durante 10 años. Actualmente es asesor en Florida del Sur y trabaja exclusivamente con tecnología de Microsoft. Jas on tiene los certificados MCT. M C S D y M C D B A de Microsoft. C uando no está enseñando, as es orando o escribiendo, esta for mateando su disco duro, i nsta lando los últimos productos beta de Microsoft y poniéndose al día de los últimos episodios de "Star T re k" . Pierre Boutquin es arquitecto superior de software en la tesorería de uno de los principales bancos canadienses, donde ayuda a desarrol lar software puntero par a la prev ención de riesgos de mercado. Tiene más de una d écada de experi en cia introduciendo sistemas comput er izados basados en el PC con un exhaustivo conocimiento del diseño de si stemas distribuidos, al mac enami en to de datos. Vi sual Basic. Visual C ++ y SQL. Ha co-escrito muchos libros sobre programación y ha contribuido con material sobre VB. C O M + . X M L y SQL a otros libros. Koshka y Sasha. sus dos adorables gatos de Birmania, ocupan casi todo el tiempo libre de Pierre. Mientras los acaricia, suele pensar en lo hermoso que sería e ncon trar más tiempo par a volver al ajedrez o mantenerse informado sobre Bélgica, su país natal. Puede cont act ar con él en boutquinc/ hotmail.com. Meeta Gupta tiene una licenciatura en ingeniería informática. Los sistemas de redes son lo que más le gusta. Actualmente t rabaj a en NII T Ltd.. donde diseña, desarrolla y escribe libros sobre temas muy diversos. Ha co-escrito libros sobre TCP/ I P. A+ Certification. A S P . N E T y PHP. Tambi én tiene una a mplia experien cia diseñando y desarrollando v arias ILT. Aparte de escribir. Meeta ha realizado cursos sobre C++. Sybase. Wi ndows NT. Unix y H T M L par a una audiencia div ersa, desde estudiantes hasta clientes corporativ os.
NIIT es una compañ ía de soluciones globales TI que produce productos de enseñanza multimedia personal izados y tiene más de 2. 000 centros de enseñanza por todo el mundo. NII T tiene más de 4. 000 empleados en 37 países y tiene acuerdos estratégicos con varias de las principales corporaciones, incluidos Microsoft y AT&T.
Sobre el editor de la serie Michael Lañe Thomas es un activo p r o g r a m a d o r de c omuni dade s y un analista de la industria informática que actualmente pasa la mayor parte de su tiempo difundiendo el evangelio de Microsoft . NET par a Microsoft. Mientras t r a b a j ab a con más de media docena de editoriales. Michael ha escrito numerosos artículos técnicos y ha escrito o participado en casi 20 libros sobre numerosos temas técnicos, incluyendo Visual Basic. Visual C ++ y tecnologías N E T Es un prolífico defensor de la certificación de p rog ramas de Microsoft y ya ha con se guido su MCSD. MCSE+I. MCT. M C P + S B y M C D B A Ademas de sus escritos técnicos, también puede es cuchar a Michael en las ondas de vez en cuando, incluidos dos p rog ra ma s de radio s emanales en las c a d e nas Entercom (http://wvvvv.entercom.com/) y mas a menudo en la ciudad de k a n s a s en News Radio C)8 0 K M B Z (http://vvvvvv.kmbz.com/). Tambi én puede encontrarse con el en Internet haciendo un MS D N We b ca st (http://wwvv.microsoft.com/usa/ webeasts/) debatiendo sobre NET. la nueva generación de tecnologías aplicadas a la Red. Michael empezó su tray ectoria técnica en su época universitaria en la University o f Kansas. donde ganó sus galones y un par de títulos. Tras un breve trabajo como técnico y asesor comercial p ar a Global Online Japan. con base en Tokio, regreso a los Estados Unidos para ascender por la escalera corporativa. Ha o c u pado puestos v ariados. incluyendo el de encargado de la IT. ingeniero de campo, instructor, consultor independiente e incluso un breve trabajo como C I O interino de una exitosa punto-com. aunque él cree que su actual papel como evangelista de N E T p ar a Microsoft es el mejor del lote. Puede c ont act ar con él vía e-mail en mi thomas a nncrosoft.com.
8
índice
A g r a d e c i m i e n t o s .................................................................................................................... Sobre los a u t o r e s ................................................................................................................... Sobre el editor de la s e r i e ....................................................................................................
6 7 K
I n t r o d u c c i ó n ...........................................................................................................................
29
Quién debería leer este l i b r o ............................................................................................. Cómo está organizado este l i b r o ...................................................................................... Parte 1: Fundament os del lenguaje C # .............................................................. Parte 11: Programación orientada a objetos con C# .................................... Parte III: C# a v a n z a d o ........................................................................................... Parte IV: Desarrollando soluciones N E T usando C # ................................. Parte V: C# y N E T Framcwork ......................................................................... Parte VI: Apéndices ................................................................................................ Cómo usar este l i b r o ............................................................................................................ Normas usadas en este libro .............................................................................................
30 30 30 31 31 31 31 31 32 32
P a r t e I. F u n d a m e n t o s del l e n g u a j e C # ..................................................................
35
1. I n t r o d u c c i ó n a C # .........................................................................................................
37
N E T Framework .................................................................................................................. Desarrollo W e b ...................................................................................................................... Desarrollo de a p l i ca c io ne s........................................................................................... Entorno común de ejecución ............................................................................................. Bibliotecas de clase N E T ...........................................................................................
38 38 3^ 40 41
9
Lenguajes de progr amaci ón N E T ............................................................................. E n t o r n o A S P . N E T ........................................................................................................... Historia de C. C ++ y C # ..................................................................................................... Introducción a ( ’•'*'.................................................................................................................. Caract erí st icas del l e n g u a j e ......................................................................................... C l a s e s ............................................................................................................................ Tipos de d a t o s ............................................................................................................. F u n c i o n e s ..................................................................................................................... Variables ...................................................................................................................... Inter faces ..................................................................................................................... A t r i b u t o s ...................................................................................................................... Cómo compi lar C # .......................................................................................................... Lenguaje intermedio de Microsoft (MS IL) ...................................................... M e t a d a t o s ..................................................................................................................... E n s a m b l a d o s ............................................................................................................... R e s u m e n ...................................................................................................................................
42 43 43 45 45 45 46 47 47 4X 49 49 49 51 51 52
2. E s c r i b i r su p r i m e r p r o g r a m a en C # .......................................................................
55
Cómo escoger un e d i t o r ....................................................................................................... La aplicación I lello W o r l d ................................................................................................. Como const rui r una clase ............................................................................................. El método Mai n() ............................................................................................................ Cómo escribir en la c o n s o l a ......................................................................................... Compilación y ejecución del p r o g r a m a .......................................................................... Las p al abr as clave y los i d e n t i fi ca do r es ................................................................. Uso de espacios en b l a n c o ........................................................................................... Cómo iniciar progr amas con la función M a i n ( ) ......................................................... Corno comentar el c ó d i g o .................................................................................................... Cómo usar comentarios de una línea ....................................................................... Usar comentarios regulares ......................................................................................... Como generar documentación X M L a partir de c om e n t a r i o s ........................... < c > ................................................................................................................................. • code •........................................' .................................................................................. • example - ................................................................................................................... • exception • ................................................................................................................ • list • .............................................................................................................................. param • ........................................................................................................................ paramreí' - .................................................................................................................. • permission • .............................................................................................................. remarks • .................................................................................................................... returns ■ ..................................................................................................................... see • .............................................................................................................................. • secaIso • .....................................................................................................................
55 56 56 57 57 58 59 61 62 64 64 64 65 67 68 68 68 69 70 70 71 71 71 71 72
10
s ummary • .................................................................................................................. < v a l u c > ......................................................................................................................... R e s u m e n ....................................................................................................................................
73 73 73
3. T r a b a j a r con v a r i a b l e s ..................................................................................................
75
Cómo dar nombre a sus v a r i a b l e s ................................................................................... Asignación de un tipo a una v a r i a b l e ............................................................................. Cómo aplicar t amaño a sus v a r i a b l e s ...................................................................... Como declarar sus v a r i a b l e s .............................................................................................. Uso de valores por defecto en las v a r i a b l e s .................................................................. Asignación de valores a v a r i a b l e s ................................................................................... Uso de matrices de v a r i a b l e s ............................................................................................. Declaración de matrices u n i d i m e ns io na l es ............................................................. Como trabaj ar con los valores de las matrices u ni d ime ns iona les ............. Inicialización de valores de elementos de m a t r i z ........................................... Declaración de matrices mul ti di mens ional es ......................................................... Uso de matrices r e c t a n g u l a r e s ............................................................................... Definición de matrices e s c a l o n a d a s .................................................................... Tip os de valor y de referencia .......................................................................................... Como convertir tipos de variable ..................................................................................... Conversiones i m p l í c i t a s ................................................................................................ Conv ersiones expl í ci t as ................................................................................................. Como trabajar con c a d e n a s ................................................................................................ Uso de caracteres especiales en c a d e n a s .................................................................. Desactivación de los caracteres especiales en c a d e n a s ....................................... Como acceder a caracteres individuales en la c a d e n a ........................................ Declaración de e n u m er a ci o ne s .......................................................................................... R e s u m e n ....................................................................................................................................
75 76 78 78 79 XI XI 82 83 84 85 85 87 88 89 89 90 92 92 94 95 95 96
4. E x p r e s i o n e s ........................................................................................................................
99
Como usar los o p e r a d o r e s ................................................................................................... Uso de expresiones p r i m a r i a s ............................................................................................ Como usar los li teral es.................................................................................................. Literales b o o l e an os .................................................................................................... Como usar los literales enteros en notaciones decimales y hexadecimales ............................................................................................. Como usar los literales reales para v alores de coma f l o t a n t e .................... Como usar los literales de carácter para asignar valores de c a r a c t e r .... Como usar los literales de cadena para incrustar c a d e n a s .......................... Como usar los literales n u i l ................................................................................... Uso de i d e nt i f i c ad o r es .................................................................................................... Expresiones entre p a r é n t e s i s ........................................................................................ C omo llamar a métodos con expresiones de acceso a m i e m b r o s ....................
99 100 100 101 101 103 104 104 104 105 105 106
11
Como l lamar a métodos con expresiones de i n v o c a c i ó n ........................... Cómo especi fi car elementos de mat ri z con expresiones de acceso a elementos ................................................................................................... Cómo acceder a objetos con la pal abr a clave this ....................................... Cómo acceder a objetos con la p al ab ra clave b a s e ..................................... Cómo us ar los o per adores postfijo de incremento y de d e c r c m e n t o ...... Creación de nuevos tipos de referencia con el op er ad or n c w ................... Cómo devolver i nformación sobre el tipo con el op er ador t y p e o f ......... C ómo us ar oper adores ehccked y unchecked ................................................ Las expresiones u ñ a r í a s .............................................................................................. Cómo devolver valores de operando con el oper ado r unario m á s .......... Cómo devolver valores de oper ando con el oper ad or unario m e n o s ...... Expresiones negativas booleanas con el oper ad or de negación lógica .. El oper ad or de compl ement o bit a b i t .............................................................. C ómo prefijar oper adores de incremento y d e c r e m e n t o ............................. Los operadores a r i t m é t i c o s ........................................................................................ Cómo a si gnar nuevos valores con el oper ador de a s i g n a c i ó n ................. Uso del o pe r ad or m u lt ip l ic ac i ón ........................................................................ Uso del o per ador di \ i s i o n .................................................................................... Uso del oper ador r e s t o .......................................................................................... Uso del oper ador s u m a .......................................................................................... Uso del oper ador resta .......................................................................................... Los operadores de d e s p l a z a m i e n t o .......................................................................... Cómo mover bits con el operador de desplazamiento a la izquierda .... Cómo mover bits con el operador de desplazamiento a la d e r e c h a ...... Cómo c o m p a r a r expresiones con operadores r e l a c i ó n a l e s ...................... Cómo c om p r o b a r la igualdad con el oper ador de igualdad ...................... Como c o m p r o b a r la desigualdad con el oper ad or de d e s i g u a l d a d ......... Cómo c om p r o b a r valores con el oper ador menor que .............................. Como c om p r o b a r v alores con el operador mayor que .............................. Cómo c om p r o b a r v alores con el o per ador menor o igual q u e ................ Como c om p r o b a r v alores con el operador mayor o igual q u e ................ Op eradores lógicos e n t e r o s ....................................................................................... Cómo calcul ar valores booleanos con el oper ad or A N D .......................... Como calcul ar valores booleanos con el oper ador exclusivo OR ......... Como ca lcul ar valores booleanos con el operador O R ............................. Op eradores condicionales l ó g i c o s ........................................................................... C ompar ac ión de valores booleanos con el oper ador A N D condicional Co mp ar ac i ón de valores booleanos con el o per ador OR condicional ... Co mp ar ac i ón de v alores booleanos con el oper ado r lógico c o nd i ci o na l ..................................................................................................... El orden de las operaciones ....................................................................................... R e s u m e n ...........................................................................................................................
12
106 107 108 109 109
1 10 110
110 113 113 113 113 114 114 115 116 116 117 118 118 119
120 120 121 122 122
122 123 123 124 124 124 125 125 126 126 126 127 127 127 129
5. C ó m o c o n t r o l a r el flujo del c ó d i g o ......................................................................
131
Instrucciones de C# ............................................................................................................. Instrucciones par a declarar variables l o c a l e s ....................................................... Como usar instrucciones de selección par a seleccionar la ruta del código La instrucción i f ......................................................................................................... La instrucción s w i t c h ............................................................................................... Cómo usar instrucciones de iteración para ejecutar instrucciones i n c r u s t a d a s ............................................................................................................. La instrucción vvhile................................................................................................. La instrucción d o ....................................................................................................... La instrucción f o r ...................................................................................................... La instrucción f o r e a c h ............................................................................................. Instrueeiones de salto para moverse por el c o d i g o .............................................. La instrucción break ................................................................................................ La instrucción c o n t i n u é ............................................................................................ La instrucción goto ................................................................................................... Cómo usar instrucciones par a realizar cálculos matemáticos con s e g u r i d a d ......................................................................................................... R e s u m e n ....................................................................................................................................
131 132 133 134 13? 137 138 13X 139 142 142 H3 1“13 144 145 14^
6. C ó m o t r a b a j a r con m é t o d o s .......................................................................................
149
La estructura de un método ............................................................................................... Tipo d e v u e l t o .................................................................................................................... Nombre del método ........................................................................................................ Lista de p a r á m e t r o s ........................................................................................................ Cuerpo del m é t o d o ........................................................................................................... Como llamar a un m é t o d o ................................................................................................... Tipos de p a r á m e t r o s ............................................................................................................. Parámetros de e n t r a d a .................................................................................................... Parámetros de s a l i d a ...................................................................................................... Parámetros de r e fer enci a............................................................................................... Matrices de p a r á m e t r o s ................................................................................................. Sobrecarga de m é t o d o s ........................................................................................................ Métodos v i r t u a l e s .................................................................................................................. Métodos s o b r e c a r g a d o s ................................................................................................. R e s u m e n ...................................................................................................................................
*49 1?** 1 1 1? I 1^1 1?? 1-^5 1^6 1— 160 162 163 164 166
7. A g r u p a c i ó n de d a t o s u s a n d o e s t r u c t u r a s ........................................................
169
Cómo declarar una e s t r u c t u r a .......................................................................................... Cómo usar estructuras en el c ó d i g o ................................................................................ Cómo definir métodos en e s t r u c t u r a s ............................................................................ Cómo usar métodos c o n s t r u c t o r e s ............................................................................ Como llamar a métodos desde estructuras ............................................................
170 171 173 174 177
13
Cómo definir propiedades en e s t r u c t u r a s ..................................................................... Cómo definir indizadores en e s t r u c t u r a s ...................................................................... Cómo definir interfaces en e s t r u c t u r a s ........................................................................ Cómo usar los tipos simples de C# como e s t r u c t u r a s ............................................. R e s u m e n ...................................................................................................................................
178 179 181 182 185
P a r t e I I . P r o g r a m a c i ó n o r i e n t a d a a o b j e t o s con C # .......................................
187
8... E s c r i b i r códi go o r i e n t a d o a o b j e t o s ...................................................................
1 89
Clases y o b j e t o s ..................................................................................................................... Terminología del diseño de s oftware orientado a objetos ...................................... A b s t r a c c i ó n ....................................................................................................................... Tipos de datos abstractos ............................................................................................ E n c a p s u l a c i ó n ................................................................................................................... H e r e n c i a ............................................................................................................................. He renci a s i m p l e ......................................................................................................... Herencia m ú l t i p l e ...................................................................................................... P o li m o r f i sm o ..................................................................................................................... R e s u m e n ...................................................................................................................................
192 192 193 193 195 197 197 198 199 202
9. C l a s e s de 0
...................................................................................................................
205
Como declarar una c l a s e ................................................................................................... El método M a i n ...................................................................................................................... Como u sar ar gument os de línea de c o m a n d o s ...................................................... Cóm o devol ver valores ................................................................................................. El cuerpo de la c l a s e ............................................................................................................ Cómo usar c o n s t a n t e s ................................................................................................... Como usar c a m p o s ......................................................................................................... Como us ar métodos ....................................................................................................... Cómo usar propiedades ................................................................................................ Descri pt ores de acceso g e t ................................................................................... Descriptores de acceso s e t ................................................................................... Propiedades de sólo lectura y de sólo e s c r i t u r a ............................................ C ómo us ar e x e n t o s ......................................................................................................... Como usar i n d i / a d o r e s .................................................................................................. Como us ar o p e r a d o r e s .................................................................................................. Como usar c o n s t r u c t o r e s .............................................................................................. Como usar d e s t r u c t o r e s ................................................................................................ Como usar los tipos de c l a s e ....................................................................................... Como us ar la pal ab ra clave this como identificado!'................................................. El modificador static ............................................................................................................ Cómo usar campos e s t á t i c o s ....................................................................................... Como usar constantes e s t a t i c a s .................................................................................
205 206 207 207 209 209 210 212 212 215 216 216 217 217 222 222 223 226 226 228 228 230
14
Como usar métodos e s t á t i c o s .......................................................................................... 230 R e s u m e n ................................................................................................................................... .... 232 10. C ó m o s o b r e c a r g a r o p e r a d o r e s ...........................................................................
237
Operadores ú na n o s s o b r e c a r g a b l e s .................................................................................... 238 Cómo sobre carga r el unario m á s ................................................................................... 238 Cómo so br ecar ga r el unario m e n o s .............................................................................. 240 Como s obre carg ar complementos bit a b i t ................................................................. 242 C ómo s ob re c ar ga r el incremento p r e f i j o ................................................................ .... 244 Cómo s ob re car ga r el decrcmento p r e f i j o ............................................................... ....245 Cómo sobre carga r los operadores truc y tal s e .........................................................246 Operadores binarios s o b r e c a r g a b l e s ...................................................................................248 Ope radorcs de conversión s obrecargabl es ......................................................................25 1 Operadores que no pueden s o b r e c a r g a r s e .......................................................................253 R e s u m e n ................................................................................................................................... ....253 11. H e r e n c i a de c l a s e ...................................................................................................... ....2 5 7 Cómo c ompilar con clases m ú l t i p l e s .............................................................................. ....258 Cómo especificar una clase base en C # ...................................................................... ....259 Á m b i t o ....................................................................................................................................... ....260 C ómo reutilizar identificadores de mi embros en las clases d e r i v a d a s ............... ....261 C ómo t raba jar con métodos h e r e d a d o s .............................................................................263 Métodos virtuales y de r e e m p l a z o ............................................................................ ....263 P o li m or f is m o........................................................................................................................... ....265 Métodos a b s t r a c t o s ........................................................................................................ ....267 Clases base: Cómo t raba jar con propiedades e mdizadores h e r e d a d o s .................268 Cómo usar la pal ab ra clave b a s e ...................................................................................269 Como acceder a campos de clase base con la p al ab ra clave base ..................270 Clases selladas ...........................................................................................................................270 Cont enci ón y d e l e g a c i ó n .........................................................................................................270 La clase de objeto . N E T ..................................................................................................... ....277 Cómo usar boxing y unboxi ng par a convertir a tipo object y desde el tipo o b j e c t ............................................................................................................... ....279 R e s u m e n ................................................................................................................................... ....281 P a r t e I I I . C # a v a n z a d o ...................................................................................................... ... 283 12..C ó m o t r a b a j a r con e sp aci os de n o m b r e ............................................................. ... 285 C ómo declarar un espacio de n o m b r e ............................................................................ ....286 Como declarar un espacio de nombre en varios archiv os f u e n t e ..............................287 C omo u sar clases en un espacio de n o m b r e ......................................................................288 C omo ayu da r a los espacios de nombre mediante la p al ab ra clave u s i n g ......... ... 290 Cómo crear alias de nombres de clase con la p al abr a clave u s i n g ................ ... 290
15
Cómo declarar directivas de espacio de nombre con la pal ab ra clave u s i n g ............................................................................................................. Un rápido recorrido por los espacios de nombre de N E T .................................... R e s u m e n ...................................................................................................................................
293 295 298
13. I n t e r f a c e s .......................................................................................................................
301
Cómo definir una i n t e r f a z .................................................................................................. Cómo definir métodos de i n t e r f a z ............................................................................. Cómo definir propiedades de i n t e r f a z ...................................................................... Cómo definir indizadores de i n t e r f a z ....................................................................... Cómo definir eventos de i n t e r f a z .............................................................................. Cómo derivar a partir de interfaces b a s e ..................................................................... Cómo usar la palabra clave new para reutilizar i dentificadores........................... Cómo implementar interfaces en clases y e s t r u c t u r a s ............................................ Cómo i mplementar métodos de interfaz con el mismo n o m b r e ............................ Cómo acceder a miembros de i n t e r f a z .......................................................................... Consultar a un objeto por una interfaz .................................................................... Cómo acceder a una interfaz en un o b j e t o ............................................................. Declaraciones de interfaz y pal abr as clave de á m b i t o ....................................... Cómo implementar interfaces definidas por N E T Framework ........................... Cómo implementar foreach mediante IEnumerable I E n u m e r a t o r ................. Cómo implement ar l impieza mediante [ D i s p o s a b l e ............................................ R e s u m e n ...................................................................................................................................
303 303 304 304 305 305 307 308 310 311 311 314 316 317 317 322 325
14. E n u m e r a c i o n e s ...........................................................................................................
329
Cómo declarar una enumeraci ón .................................................................................... Cómo usar una e n u m e r a c i ó n ............................................................................................. Cómo usar operadores en valores de e n u m e r a c i ó n .................................................. Cómo usar la clase N E T S y s t e m . E n u m ...................................................................... Cómo recuperar nombres de e n u m e r a c i ó n ............................................................ Como c o mp ar a r valores de e n u m e r a c i ó n ............................................................... Como descubri r el tipo subyacente en tiempo de e j e c u c i ó n ............................ Como recuperar todos los valores de enumeración ........................................... Análisis de cadenas par a recuperar valores de e n u m e r a c i ó n ........................ R e s u m e n ....................................................................................................................................
331 333 335 337 337 339 341 341 342 343
15. E v e n t o s y d e l e g a d o s .................................................................................................
345
Como C ómo Cómo Como Cómo Cómo
346 346 347 348 348 350
16
definir d e l e g a d o s ....................................................................................................... definir ev e n t o s ........................................................................................................... instalar ev e n t o s .......................................................................................................... d es encad enar ev e n t o s ............................................................................................. unirlo todo ................................................................................................................... es tan da ri zar un diseño de e v e n t o .......................................................................
Como us ar descriptores de acceso de e v e n t o s ........................................................... Como us ar modificadores de e v e n t o s ............................................................................ Eventos e s t á t i c o s ............................................................................................................. Eventos \ i r t u a l e s ............................................................................................................. Eventos de r e e m p l a z o .................................................................................................... Eventos a b s t r a c t o s .......................................................................................................... R e s u m e n ....................................................................................................................................
353 354 354
16. C o n t r o l de e x c e p c i o n e s .........................................................................................
359
Como especificar el procesamiento de e x c e p c i o n e s ................................................ Como ca ptur ar e x c e p c i o n e s .............................................................................................. Cómo usar la palabra clave t r y .................................................................................. Como at r ap ar clases específicas de e x c e p c i o n e s ............................................... Como liberar recursos después de una excepción .............................................. La clase exception ................................................................................................................ Introducción a las excepciones definidas por N E T Framework Out O fMemory Except ion .............................................................................................. StackOverflow E x c e p t i o n .............................................................................................. N u l l R e f er e nc e Ex ce pt i on ............................................................................................... TypelnitializationException.......................................................................................... ln\ alidCast E xpre ss ion.................................................................................................... ArrayTx pcMi smat eh Except ion .................................................................................. I ndcxOut OfRange Except ion ....................................................................................... Di vi deByZer oExcept ion................................................................................................ Overflow E x c e p t i o n ........................................................................................................ Cómo trabajar con sus propias e x c e p c i o n e s ............................................................... Cómo definir sus propias e xc e p c i on e s ........................................................................... Cómo iniciar sus e x c e p c i o n e s .................................................................................... Cómo usar excepciones en constructores y p r o p i e d a d e s ................................. R e s u m e n ...................................................................................................................................
361 362 362 362 364 365 365 366 366 367 368 368 369 369 370 370 371 372 373 374 376
17. C ó m o t r a b a j a r con a t r i b u t o s ................................................................................
379
At r ibut os ................................................................................................................................... Como trabajar con atributos de N E T F r a m e w o r k .................................................... S\ stem Diagnostics Conditional A t t r i b u t e ............................................................... System.SerializableAttribute c l a s s ............................................................................ System. Obsolete Attribute c l a s s ................................................................................. Como escribir sus propias clases de a t r i b u t o .............................................................. Cómo restringir el uso de a t r i b u t o s ........................................................................... Cómo permitir multiples valores de a t r i b u t o .......................................................... Cómo asignar parámetros de a t r i b u t o ...................................................................... Ejemplo explicativo de las clases de a t r i b u t o ........................................................ R e s u m e n ...................................................................................................................................
380 383 384 386 388 390 390 391 392 394 396
355 355 3^5
17
18. C ó m o u t i l i z a r v e r s i o n e s en sus c l a s e s ...........................................................
399
El probl ema de las v e r s i o n e s ............................................................................................ C omo sol uci onar el probl ema de las v e r s i o n e s .......................................................... Mediante el mo di fi cador nevv..................................................................................... Mediante el modi fi cador o v e r r i d e ............................................................................. R e s u m e n ...................................................................................................................................
399 402 402 404 406
19. C ó m o t r a b a j a r con códi go no s e g u r o ..............................................................
409
Conceptos básicos de los punteros ................................................................................. Tipos de p u n t e r o .............................................................................................................. Como compilar código no s e g u r o .................................................................................... Cómo especificar punteros en modo no s e g u r o ................................................... Como acceder a los v alores de los miembros mediante p u n t e r o s ................. Cómo usar punteros par a fijar variables a una dirección e s p e ci f i c a.................. Sintaxis del elemento de matriz p u n t e r o ................................................................. Como compar ar punteros ............................................................................................ Cálculo con p u n t e r o s ...................................................................................................... Cómo usar el operador s i z e o f ..................................................................................... Cómo asignar espacio de la pila para la m e m o r i a ..................................................... R e s u m e n ...................................................................................................................................
410 411 412 413 414 415 416 41 7 417 418 419 419
20. C o n s t r u c t o r e s a v a n z a d o s de C # ........................................................................
423
Operadores implícitos y conv ersiones no válidas ...................................................... Inicialización de estructuras .............................................................................................. Cómo inicializar estructuras ........................................................................................ Cómo resolver los problemas con la inicialización .............................................. Clases d e r i v a d a s .................................................................................................................... Cómo pasar clases d e r i v a d a s ..................................................................................... Como resolver problemas que surgen cuando se pasan clases derivadas .. Cómo usar no enteros como elementos de m a t r i z ..................................................... R e s u m e n ...................................................................................................................................
424 425 426 427 429 429 430 431 434
P a r t e IV. C ó m o d e s a r r o l l a r soluciones . N E T u s a n d o C # ............................
435
21... C ó m o c o n s t r u i r a p l i c a c i o n e s W i n d o w s F o r m s ............................................
437
Arqui tect ura de W i n d o w s F o r m s ....................................................................................... La clase F o r m ................................................................................................................... La clase A p p l i c a t i o n ....................................................................................................... Cómo crear la primera aplicación W i n d o w s F o r m s ................................................... Como compilar una aplicación Wi nd o ws Fo rm s ......................................................... Ensamblados: cómo añadir información de versión a las aplicaciones W i n d o w s F o r m s ........................................................................................................... Assemblv T i t l e ...................................................................................................................
438 438 438 439 440
18
441 442
AssembK D e s c r i p t i o n ..................................................................................................... As semb lyC on fí gu rat i on................................................................................................. AssembK C o m p a n y .......................................................................................................... AsscmbK P r o d u c t ............................................................................................................ AssembK Copy r i g h t ........................................................................................................ As scmbK T r a d c m a r k ...................................................................................................... A s s c m b K C u l t u r e ............................................................................................................. As semb K V e r s i ó n ............................................................................................................. El objeto Application con mas d e t a l l e ............................................................................ Eventos A p p l i c a t i o n ........................................................................................................ Cómo t raba jar con eventos en el c ó d i g o ................................................................ Propiedades Application ............................................................................................... AllovvQuit..................................................................................................................... C o m m o n A p p D a t a R e g i s t r y .................................................................................... C o m m o n A p p D a t a P a t h ............................................................................................. Company Ñ a m e ........................................................................................................... C u rr e nt Cu l tu re ............................................................................................................ C u r r e n t l n p u t L a n g u a g e ............................................................................................. E x e c u t a b l e P a t h ........................................................................................................... l.ocall s e r A p p D a t a P a t h ......................................................................................... Mes sagc Loop ............................................................................................................. P r o d u c t N a m e .............................................................................................................. P r o d u ct V e rs i o n ........................................................................................................... SafeTopLev e l Ca p t i o n F o r ma t ................................................................................. S t a r t u p P a t h .................................................................................................................. I sei A p p D a t a P a t h .................................................................................................... r ser App Data Registry ............................................................................................. Métodos A pp li cat ion....................................................................................................... AddM e s s a g e F i l t e r ..................................................................................................... DoEvents ..................................................................................................................... E x i t ................................................................................................................................. E x i t T h r e a d ................................................................................................................... O l e R e q u i r e d ................................................................................................................. O n T h r e a d E x c e p t i o n ................................................................................................. Remov e M c s s a g c F i l t e r ............................................................................................. R u n .................................................................................................................................
443 443 443 443 444 444 444 44.^ 447 447 448 449 450 450 450 4M 4^1 45 1 452 452 4^2 45.' 453 453 453 453 4M 454 454 457 457 457 457 458 460 461
Como añadir controles al f o r m u l a r i o ............................................................................... Jerarquía de las clases de c o n t r o l ............................................................................. Como trabajar con controles en un f o r m u l a r i o ..................................................... Como trabajar con r e c u r s o s .............................................................................................. Como trabajar con recursos de c a d e n a ................................................................... Cómo trabajar con recursos b i n a r i o s ........................................................................ R e s u m e n ...................................................................................................................................
461 462 462 465 465 468 468
19
22. C ó m o c r e a r apl icaci ones W e b con W e b F o r m s ..........................................
471
Fundament os de las aplicaciones ASP.NET W e b ...................................................... Nuevas características de A S P . N E T ....................................................................... Ejecución en el entorno N E T F r a m e w o r k ...................................................... Presentación de WebForms .................................................................................. Integración con Visual Studio . N E T ................................................................... Presentación de los controles de s e r v i d o r ........................................................ Controles de usuario y c o m p u e s t o s .................................................................... Controles más usados en W e b F o r m s ....................................................................... Control L a b e l .............................................................................................................. Control T e x t B o x ........................................................................................................ Controles ChcckBox y C h e c k B o x L i s t ............................................................... Controles RadioButton y R a d i o B u tt o n L i s t ....................................................... Control L i s t B o x ......................................................................................................... Control D r o p D o w n l . i s t ........................................................................................... Control H y p c r L i n k .................................................................................................... Controles Table. Tabl eRow y T a b l e C e l l ............................................................ Control I m a g e Bu t to n................................................................................................ Controles Button y LinkButton ............................................................................ Cómo crear y configurar una aplicación W e b ............................................................. Como crear un nuevo p r o y e c t o .................................................................................. Cómo agregar controles al W e b F o r m ...................................................................... Cómo controlar e v e n t o s ................................................................................................ Viajes de ida y v u e l t a ............................................................................................... Controladores de e x e n t o s ....................................................................................... Cómo controlar la devolución de datos ............................................................. Cómo usar el estado de v i s t a ................................................................................ R e s u m e n ...................................................................................................................................
472 472 472 472 473 473 474 474 474 475 475 476 477 477 477 478 479 479 479 480 483 487 487 489 491 491 492
23. P r o g r a m a c i ó n de bases de d at o s con A D O . N E T ......................................
495
Clases Dataset y otras clases r e l a c i o n a d a s ................................................................ Compatibilidad con OLE DB SQL S e r v e r .................................................................... Operaciones de bases de datos comunes mediante A D O . N E T ........................... Operaciones que no devuelven filas ......................................................................... Operaciones de datos que devuelven entidades de fila única ........................ Ope raciones de datos que afectan a las entidades de fila ú n i c a .................... Ope raciones de introducción de datos que afectan a las entidades de fila ú n i c a ..................................................................................................... Operaciones de actualización que afectan a entidades de fila ú n i c a ..................................................................................................... Operaciones de borrado que afectan a las entidades de fila única .......... Operaci ones de datos que devuelven conjuntos de f i l a s .................................... Operaciones de datos que afectan a conjuntos de f i l a s .......................................
496 497 499 500 504 509
20
509 514 5 15 517 520
Op eraci ones que no devuelven datos jerárquicos ............................................... R e s u m e n ...................................................................................................................................
522 527
24. C ó m o t r a b a j a r con a r c h i v o s y con el r e g i s t r o de W i n d o w s .................
529
C omo acceder a a r c h i v o s ................................................................................................... Acceso b i n a r i o .................................................................................................................. B i n a r y W r i t e r ............................................................................................................... B i n a r y R e a d e r ............................................................................................................. C omo super visar los cambi os de a r c h i v o ............................................................... Como usar la supervisión de a r c h i v o s ............................................................... Como codificar FileSv s t e m W a t c h e r ................................................................... Corno manipular a r c h i v o s ............................................................................................. Cómo copiar a r c h i v o s .............................................................................................. Como eliminar a r c h i v o s .......................................................................................... Cómo t rasladar a r c h i v o s ......................................................................................... Como acceder al r e g is t ro .................................................................................................... Como leer claves del r e g i s t r o ...................................................................................... Como escribir clav es de registro .............................................................................. Como enumerar claves del r e g i s t r o .......................................................................... R e s u m e n ...................................................................................................................................
529 530 530 531 533 534 538 539 539 540 >-+2 -"'44 -"'44 546 549 551
25. C ó m o a c c e d e r a s ec ue nc i as de d a t o s .............................................................
555
Jerarquía de clases de E/S de d a t o s ............................................................................... Como usar s e c u e n c i a s ................................................................................................... Como usar e s c r i t o r e s ..................................................................................................... Como usar l e c t o r e s ........................................................................................................ Cómo trabajar con secuencias ......................................................................................... E/S s i n c r ó n i c a ................................................................................................................... E/S a s i n c r ó n i c a ................................................................................................................. Como leer de forma a s i n c r ó n i c a .......................................................................... Como escribir de forma a s i n c r ó n i c a ................................................................... Escritores y lectores ............................................................................................................ Cómo escribir secuencias con B i na ry Wr i te r ......................................................... Cómo leer de secuencias con B i n a r y R e a d e r ........................................................ Como escribir X ML con un formato correcto mediante la secuencia X m l W r i t e r .................................................................................................................... R e s u m e n ...................................................................................................................................
555 556 557 557 558 558 563 564 568 570 570 572
26. C ó m o d i b u j a r con G D I + .........................................................................................
577
Como trabajar Como trabajar Cómo trabajar Como usar
577 586 591 591
con g r á f i c o s ............................................................................................... con Image en G D 1 + ................................................................................ con lápices y p i n c e l e s ............................................................................ la clase P e n ................................................................................................
573 575
21
Cómo us ar la clase B r u s h ............................................................................................ R e s u m e n ...................................................................................................................................
593 597
27. C ó m o c o n s t r u i r servicios W e b ...........................................................................
599
F uncionamiento de los servicios W e b ............................................................................ Servicios Web y Visual Studio N E T ............................................................................. Lenguaje de descripción de serv icio Web ( W S D L ) ................................................. Como usar el Protocolo de acceso simple a objetos ( S O A P ) .............................. C omo crear servicios Web con Visual Studio . NET ................................................ Como us ar Visual Studio N E T para acceder a un servicio Web ....................... R e s u m e n ...................................................................................................................................
600 602 605 607 609 612 614
28. C ó m o u s a r C# en A S P . N E T .................................................................................
617
Como crear un serv icio W e b ............................................................................................. Cómo crear una base de datos par a un servicio W e b ........................................ Concept os del sistema de gestión de bases de datos r e l a c i ó n a l e s .......... Tipos de datos de S QL S e r v e r ............................................................................. Como crear bases de datos y t a b l a s ................................................................... Como recuperar d a t o s ............................................................................................ Cómo insertar, actualizar y eliminar datos ....................................................... Como usar procedimientos a l m a c e n a d o s ......................................................... Como crear la estructura de la base de d a t o s ................................................ Cómo usar la plantilla Servicio Web A S P . N E T ..................................................... Cómo agregar controles de datos al serv icio W e b .............................................. Como codificar el servicio W e b ................................................................................. Como crear un cliente de serv icio W e b ......................................................................... Como crear un nuevo provecto de aplicación Web A S P . N E T ....................... Como agregar una referencia Web .................................................................... C omo i mple me nt ar los métodos del serv icio Web ............................................... Como implementar la aplicación ..................................................................................... Implementacion de provectos en Visual Studio N E T ........................................ Cómo usar un prov ecto de implementacion para implementar una aplicación ...................................................................................................... Como implementar un prov ecto usando la opcion Copi ar prov e c t o .............. R e s u m e n ...................................................................................................................................
61 X 618 619 619 620 621 621 621 622 623 624 627 629 630 63 1 632 635 635 635 637 637
29. C ó m o c o n s t r u i r c o nt r ol e s p e r s o n a l i z a d o s ..........................................................
641
Biblioteca de control de W i n d o w s ................................................................................... P r o p i e d a d e s ........................................................................................................................ M é t o d o s ............................................................................................................................... C a m p o s ................................................................................................................................ E v e n t o s ................................................................................................................................ Aprender con un ejemplo ....................................................................................................
641 642 644 645 645 646
22
Como crear un tempori zador de cuenta a t r á s ....................................................... C ómo crear una p rue ba de carga C o u n t D o w n ..................................................... Como usar una biblioteca de c l a s e s ................................................................................ C ómo crear una clase par a calcul ar el efecto de v i e n t o ................................... R e s u m e n ....................................................................................................................................
646 651 653 653 656
30. C ó m o c o n s t r u i r a p l i c a c i o n e s m ó v i l e s .............................................................
659
La red inalámbrica ................................................................................................................ Introducción al Mobile Internet Toolkit ................................................................... Emuladores ........................................................................................................................ N o k i a .............................................................................................................................. Pocket P C .................................................................................................................... Microsoft Mobile E x p l o r e r ..................................................................................... Cómo crear un calculador de edades ............................................................................ Funciones de los dispositivos m ó v i l e s ............................................................................ Funcionamiento de los controles móviles ..................................................................... Cómo usar el control C a l e n d a r .................................................................................. Cómo usar el control I m a g e ........................................................................................ Paginación en dispositivos m óv i le s .................................................................................. R e s u m e n ....................................................................................................................................
659 660 660 660 660 661 661 666 667 667 668 670 672
P a r t e V. C# y . N E T F r a m e w o r k .................................................................................
6 73
31. C ó m o t r a b a j a r con e n s a m b l a d o s ........................................................................
675
Ensamblados ........................................................................................................................... Cómo encontrar ensamblados c a r g a d o s .................................................................. Nombres seguros de e n s a m b l a d o ............................................................................. Cómo asignar la información de v e r s i ó n ........................................................... Cómo asignar la información de referencia cultural .................................... Cómo as ignar la información de c l a v e .............................................................. Cómo t rab aj ar con la clase A s s e m b l y ..................................................................... Cómo e ncontr ar la información de ubicación del ens amb la do ................. Como encontr ar puntos de ent rada del ens ambl ado .................................... Como c argar e n s a m b l a d o s .................................................................................... Cómo trabaj ar con información de tipo de e n s a m b l a d o .............................. C omo g enerar código nativo par a e n s a m b l a d o s ......................................................... R e s u m e n ...................................................................................................................................
675 676 678 680 681 682 682 682 683 684 688 689 691
32. R e f l e x i ó n ........................................................................................................................
693
La clase Typc ......................................................................................................................... Cómo recuperar información de t i p o ....................................................................... Cómo recuperar tipos mediante el n o m b r e ...................................................... Cómo recuper ar tipos mediante instancias ......................................................
694 694 694 695
23
C ómo recuper ar tipos en un e n s a m b l a d o ......................................................... Cómo interrogar a objetos ........................................................................................... Cómo generar código dinámico mediante la reflexión ...................................... R e s u m e n ...................................................................................................................................
696 697 700 702
33. S u b p r o c e s a m i e n t o en C # .......................................................................................
705
S ub p r o c e s a m i e n t o .................................................................................................................. Multitarea p r e f e r e n t e ..................................................................................................... Prioridades de s ubproc es o y b l o q u e o ...................................................................... Multiprocesamiento s i mé tr ic o..................................................................................... Cómo usar los recursos: cuantos más. m e j o r ........................................................ Dominios de aplicación ................................................................................................. Ventajas de las aplicaciones de varios s u b p r o c e s o s ........................................... Aplicaciones con procesos l a r g o s ....................................................................... Aplicaciones de sondeo y e s c u c h a ..................................................................... Botón C a n c e l a r ........................................................................................................... Como crear aplicaciones m u l t i p r o c e s o .......................................................................... Cómo crear nuevos s u b p r o c e s o s .............................................................................. Prioridad de los s u b p r o c e s o s ....................................................................................... Estado del s u b p r o c e s o .................................................................................................. Cómo unir s u b p r o c e s o s ........................................................................................... Cómo sincronizar s u b p r o c e s o s ............................................................................. Sondeo y e s c u c h a .................................................................................................................. R e s u m e n ...................................................................................................................................
705 706 707 707 708 709 710 710 710 710 711 712 7 15 716 719 72 1 722 723
34. C ó m o t r a b a j a r con C O M .......................................................................................
727
Introducción al Contenedor al que se puede llamar en tiempo de ejecución .... Cómo crear ensamblados N E T a partir de componentes COM ........................ Cómo usar la utilidad T l b i m p ....................................................................................... C omo crear un component e C O M ...................................................................... Cómo u sar el ensamblado de intcroperabilidad desde C # .......................... Cómo hacer referencia a la DLL COM desde C # ............................................. Cómo manej ar errores de i n t c r o p e r a b i l i d a d ................................................................ Cómo usar la invocación de p l a t a f o r m a ....................................................................... R e s u m e n ...................................................................................................................................
728 729 729 731 735 739 740 743 744
35. C ó m o t r a b a j a r con servicios C O M + ..............................................................
747
El espacio de nombres S y s t e m. En t er pr i se Se r vi ce s .................................................. La clase Ser\ i c e d C o m p o n e n t ........................................................................................... Cómo registrar clases con C O M + .................................................................................. Cómo usar atributos para clases C O M + ...................................................................... Application A c c e s s C o n t r o l ........................................................................................... ApplicationActivation.....................................................................................................
748 752 754 756 757 757
24
App li cat i on ID.................................................................................................................... A p p l i c a t i o n N a m e ............................................................................................................. Ap pl i cat i onQu eui ng........................................................................................................ A u t o C o m p I c t c ................................................................................................................... C o mp on e nt A c c es s C ' o nt r o l........................................................................................... ConstructionEnabled ...................................................................................................... JustlnTimeActivation ..................................................................................................... L oa dB al a nci ngSuppor t ed.............................................................................................. Sceurit\ Role ..................................................................................................................... Como procesar transacciones .......................................................................................... Propiedades A C ' I D .......................................................................................................... Como escribir componentes de t r a n s a c c i o n e s ............................................................ Como acceder al contexto de o b j e t o s ............................................................................ R e s u m e n ...................................................................................................................................
7^8 758 758 759 759 759 760 760 761 761 762 763 7ío 768
36. C ó m o t r a b a j a r con los servicios re m o t o s de . N E T .................................
771
Introducción al entorno remoto ........................................................................................ Como crear un ensambl ado de servidor r e m o t o ........................................................ Como crear un servidor r e m o t o ....................................................................................... Como especificar canales y p u e r t o s ......................................................................... Como especificar un formato de c a n a l .................................................................... Espacio de nombres S v s te m . R u n ti m e. R e m o t mg . C h a n n e ls . T c p ....... ....... Espacio de nombres S y s t e m . R u n t i m e . R e m o t i n g C h a n n e l s . H t t p .............. C omo acti var el objeto r e m o t o ................................................................................... Como registrar objetos con RegisterW’e l l k n o w n S e r v i c e T y p e ................. C omo registrar objetos con el método C o n f i g u r e .......................................... Como escribir el cliente remoto ....................................................................................... R e s u m e n ...................................................................................................................................
771 773 775 777 778 779 779 78 1 783 785 790 794
37. C # y s e g u r i d a d . N E T ...............................................................................................
79 7
S e g u n d a d de e o d i g o ............................................................................................................. Directiva de seguridad de c o d i g o .............................................................................. Permisos de c o d i g o ........................................................................................................ S e g un da d de u s u a r i o ............................................................................................................ Seguridad NE T y basada en funciones ....................................................................... Como asignar las funciones Window s ..................................................................... Pri nci pal es.......................................................................................................................... Permisos de acceso a c o d i g o ........................................................................................... Como crear una sencilla solicitud de codigo de p e r m i s o ................................... Denegación de p e r m i s o s ............................................................................................... Como usar permisos basados en a t r i b u t o s ............................................................. Directiva de s e g u r i d a d ........................................................................................................ Niveles de directiva de s e g u n d a d .............................................................................
798 79c) 799 800 802 802 806 806 807 809 810 811 811
25
Gr up os de c o d i g o ............................................................................................................. Conj untos de permisos con n o m b r e .......................................................................... Cómo alterar directivas de s e g u r i d a d ...................................................................... R e s u m e n ...................................................................................................................................
812 812 813 814
P a r t e VI. A p é n d i c e s ..........................................................................................................
817
A p é n di c e A. M a n u a l de X M L ......................................................................................
819
Obj etivos de diseño de X M L ............................................................................................. Objetiv o 1: X M L debe ser fácilmente utilizable en I n t e r n e t ............................ Objetivo 2: X ML debe admitir una amplia variedad de a p l i c a c i o n e s ........... Objetivo 3: XML debe ser compatible con S G M L .............................................. Objetivo 4: Debe ser sencillo escribir pr og ramas que procesen documentos X M L ................................................................................................ Objetivo 5: El numero de características opcionales en X ML debe mantenerse al mínimo, preferentemente a cero .............................. Objetivo 6: Los document os X M L deben ser legibles para las personas y razonablemente c l a r o s ................................................ Objetiv o 7: El diseño de X ML debe ser pre para do rápidamente .................... Objetivo 8: El diseño de XML debe ser formal y c o n c i s o ................................. Objetivo 9: Los documentos X ML deben ser fáciles de c r e a r .......................... Objetivo 10: La concisión del mar cado X M L es de mínima importancia .... Brev e lección de H T M L ...................................................................................................... XML - H T M L con etiquetas definidas por el usuario ............................................ Definiciones de tipo de d o c u m e n t o .................................................................................. Esquemas X M L ..................................................................................................................... Espacios de nombre X M L ..................................................................................................
819 821 82 1 821
822 822 823 823 823 823 827 829 831 834
A p é n d i c e B. C o n t e n i d o del C D - R O M ...................................................................
839
I n di ce a l f a b é t i c o .................................................................................................................
841
26
822 822
Introducción
La iniciativa N E T F ramework de Microsoft supone el cambi o mas i mpo rt an te en la metodología del desarrollo de s oftware par a un sistema operativo de Microsoft desde la introducción de Windows. Este entorno está construido u s a n do una arquit ect ura que permite a los lenguajes de software t r ab aj ar juntos, c o m partiendo recursos y código, para p r o p o r c i on ar a los programadores las avanzadas herrami ent as necesarias par a construir la siguiente generación de aplicaciones de escritorio y de Internet. Visual Studio N E T de Microsoft incluye nuevas ver siones de sus productos de compilador Visual Basic y C++ dirigidas al desarrollo de NET. al igual que un lenguaje compl et ament e nuevo llamado CU. Este libro le mo st rar á como escribir código us ando este novísimo lenguaje. Todos los términos de lenguaje tales como declaraciones, variables, bucles de control y clases, son tratados con detalle. Además, el libro le enseñara a usar CU par a p ro gr a ma r tareas con las que los pro gr amado re s suelen enfrentarse en el mundo real. La ultima parte del libro explica cómo usar CU p ar a desarrollar paginas Web, acceder a bases de datos, t rabaj ar con objetos C'OM y C O M + heredados, desarrollar aplicaciones de escritorio para Windows, t raba jar con v a n o s conceptos de N E T F ramework y mucho más. El principal objetivo de este libro es el desarrollo N E T usando CU como el lenguaje de implementación y el comp il ado r de línea de comand os CU de N E T Framework como la principal her ramienta de desarrollo. El desarrollo de CU
29
empleando la her ramienta Visual Studio N E T no se trata en este libro, aunque es algo que se puede domi nar fácilmente una vez que se compre ndan bien los f u n damentos del desarrollo N E T usando CU.
Quién debería leer este libro Este libro fue escrito teniendo en mente a los pr ogr amad or es novatos v los expertos. Si no conoce a bs olutamente nada sobre las bases del desarrollo de s of t ware. este libro le iniciará en sus fundamentos, mostrándole como funcionan las variables, los bucles de control y las clases. El libro también está dirigido a los progr amadore s de cualquier nivel, mostrándoles las herramientas N E T di sponi bles para el desarrollo en CU y proporcionándoles trucos par a hacer que sus propias aplicaciones en CU funcionen perfectamente dentro de las directrices de desarrollo de N E T Framework. Si ya esta introducido en el mundo de la creación de aplicaciones N ET . e n contrará en este libro un recurso muy útil porque cubre casi todos los aspectos del desarrollo N E T exhaustivamente. Las primeras tres partes del libro sirven de punto de referencia ilustrativo p ar a usar las características del lenguaje CU. En cambio, las dos últimas partes están dedicadas a mos trar C# como pl at aforma de desarrollo de aplicaciones, ilustrando el papel de CU en aplicaciones de es cri torio. Web. bases de datos y basa das en componentes. En este libro se asume que es la pri mera vez que utiliza C# y pretende propor ci onar u na comprensión del lenguaje sin exigir un conocimiento previo. Sin e mbargo, el libro también supone que el lector está familiarizado con los entornos de aplicaciones usados en c on junción con sus aplicaciones C#. Las últimas partes del libro abor dan el uso de CU con aplicaciones de e scri to rio. Web. bases de datos y b as ad as en componentes, pero no explica esas p l a t a formas con detalle. En su lugar, el libro supone que el lector tiene un conocimiento práctico de esas plataformas.
Cómo está organizado este libro Este libro está organizado en seis partes:
Parte I: Fundamentos del lenguaje C# Esta primera parte del libro p roporci ona una breve visión general de la familia de lenguajes de programaci ón C y pas a a trat ar los aspectos sintácticos básicos de CU. Variables, declaraciones, bucles de control de flujo y llamadas de método, todas son tratadas. Los p rogr amadore s principiantes también encontrarán m at e rial explicativo sobre el uso de estos elementos sintácticos y a prenderán a e l abo rar código con estos conceptos.
30
Parte II: Programación orientada a objetos con C# Los capítulos de esta segunda parte t ratan de la noción de clase en C#. La clase es la unidad fundament al de código en una aplicación C'# y comprender las clases es clave p ar a construir una aplicación C# que funcione. Ademas esta parte se ocupa de temas como el diseño de clases, clases básicas, clases derivadas y s obrecarga de operadores.
Parte III: C# avanzado La tercera parte del libro se concentra en rasgos de lenguaje específicos e m pleados por aplicaciones C# más avanzadas. Se a bordan temas como el control de excepciones, la implcmentación de interfaces. los espacios de nombre, los a t ri b u tos y el código no seguro, todos son tratados. El ultimo capítulo de esta parte esta dedicado a present ar algunos problemas de progr amaci ón complicados y solucio nes aplicadas usando C#.
Parte IV: Desarrollando soluciones .NET usando C# La parte IV muestra cómo usar C# en aplicaciones que utilizan x a n a s partes de N E T Framexvork. Esta parte del libro se separa de las otras secciones, que están dedicadas a la presentación de las características del lenguaje C#. La parte IV usa C# para construir aplicaciones usando x a n a s plataformas de la aplicación NET. desde formularios de Windoxvs hasta Web Forms y aplicaciones A S P . N E T y acceso a bases de datos. Tambi én echaremos un vistazo al t rabajo con algunas tecnologías . NET axa nzada s usando C'#. incluyendo s ubprocesami ent os. e n s a m blados y reflexión.
Parte V: C# y .NET Framework La ultima parte del libro describe como se puede usar C# par a t raba jar con el propio N E T Framexvork. Se explican conceptos de Framexvork tales como e n samblados. reflexión, subprocesamiento e mteroperabilidad de componentes COM C O M + . Ca da capítulo explica el concepto de Framexxork a pr opia do v también enseña a a pr ove cha r la tecnología usando C# como lenguaje de i mplementación.
Parte VI: Apéndices La última sección del libro consta de dos apéndices. El pri mero ofrece una introducción al Lenguaje de m ar cad o extensible ( X ML ) y de qué maner a los progr amadores pueden apr ovecha r este leguaje p ar a describir datos de una m a nera estandarizada. Muchos proyectos N E T usan X M L de una forma u otra y varios archixos de configuración N E T están basados en la infraestructura XML. El segundo apéndice incluve una descripción del contenido del C D - R O M que a c ompa ña al libro.
31
Cómo usar este libro Los lectores que sean compl et ament e novatos en el desarrollo de software (quizás los administradores Web) aprov echarán mejor este libro leyendo primero las partes I v II para conseguir una mejor comprensión de cómo funcionan los mecani smos de una aplicación de software. Puede ser i mportante que los nuev os pro gr amado re s comprendan las bases del desarrollo de software y como encajan todas las piezas para construir una aplicación C# completa. A los lectores que se acerquen a C# con un conocimiento previo de C'++. el nuevo lenguaje les resultara muy familiar. C# fue construido pensando en C y C h y la sintaxis se parece a la de estos lenguajes más antiguos. Estos lectores quizas deseen exami nar las partes I y II para ac os tu mbra rs e a las v a na n te s de sintaxis v luego quizas deseen lanzarse de lleno a la parte III par a c omprender las avanzadas características del lenguaje. Muchos de los aspectos de la parte III profundizan en los conceptos que distinguen CU de sus predecesores. Los progr amadore s que ya esten familiarizados con CU encontraran bastante material útil. Las partes IV y V muestran el uso de CU en varias aplicaciones para la p l at a f o r m a . N E T y presentan v arios ejemplos que explican el código CU que puede usarse para realizar tareas variadas. Estas dos ultimas partes trasladan el libro del nivel teorico al nivel practico y son ideales para los pr og ramad or es de cualqui er nivel que deseen co mprender cómo puede usarse CU para i mplementar varias aplicaciones.
Normas usadas en este libro A lo largo del libro encontrara unos rectángulos s ombreados que resaltan la información especial o importante, estos son los siguientes:
ADVERTENCIA: Indica un procedimiento que, en teoría, podría causar dificultades o incluso la pérdida de datos; preste especial atención a los iconos de advertencia para evitar los errores de programación más comunes y los que no lo son tanto.
NOTA: Resalta la información interesante o adicional y suele contener pequeños trozos extra de información técnica sobre un tema.
TRUCO: Llaman la atención sobre hábiles sugerencias, pistas recomen dables y consejos útiles.
32
Además en este libro se usan las siguientes convenciones tipográficas: Los códigos de ejemplo aparecen en un tipo de letra Courier. Las opciones de menús se indican en orden jerárquico, con cada i ns truc ción de menú s eparada por el signo "mayor que" y en un tipo de letra Anal. Por ejemplo. Arch¡vo>Abrir quiere decir hacer clic en el c om and o A rchi vo en la b a r ra de menú y luego seleccionar Abrir. Las combi naciones de teclas se indican de esta forma. C o n t r o l - C . Al final de cada capítulo encontrará un resumen de lo que debería haber a p r e n dido al t erminar de leer el capítulo.
33
Parte I
Fundamentos del lenguaje C#
35
Introducción aC#
Durante los últimos 20 años. C y C++ han sido los lenguajes elegidos para desarrol lar aplicaciones comerciales y de negocios. Estos lenguajes p r op o rc io nan un altísimo grado de control al pr ogr a ma dor permitiéndole el uso de punteros y muchas funciones de bajo nivel. Sin embargo, cuando se co mpar an lenguajes, como Microsoft Visual Basic con C/ C++. uno se da cuenta de que aunque C y C ++ son lenguajes mucho más potentes, se necesita mucho más tiempo para desarrollar una aplicación con ellos. Muchos progr amadores de C / C + + han t emi do la idea de c ambi ar a lenguajes como Visual Basic porque podrian perder gran parte del control de bajo nivel al que estaban acostumbrados. Lo que la comuni dad de p ro gr amad or es necesitaba era un lenguaje que es tu viera entre los dos. Un lenguaje que ayu da ra a desarrollar aplicaciones rápidas pero que también permitiese un gran control y un lenguaje que se integrase bien con el desarrollo de aplicaciones Web. X M L y muchas de las t ecnologías em er gentes. Facilitar la transición para los progr amadores de C / C++ existentes y p r o p o r cionar a la vez un lenguaje sencillo de a pr ender par a los pr og ramador es i nexper tos son sólo dos de las ventajas del nuevo lenguaje del barrio. C#. Microsoft presentó C# al público en la Prof essi onal Devel oper 's Conf erencc en Orlando. Florida, en el verano del 2000. C# combi na las mejores ideas de lenguajes como C. C++ y Java con las mejoras de productividad de N E T Framework de Microsoft
37
y brinda una experiencia de codificación muy producti va tanto par a los nuevos p rogr amadore s como par a los veteranos. Este capítulo profundiza en los cuatro componentes que constituyen la pl at af orma N E T ademas de analizar la c om pat i bilidad para las tecnologías Web emergentes. A continuación, se analizan muchas de las funciones del lenguaje C# y se c ompar an con otros lenguajes populares.
.NET Framework Microsoft diseñó C# desde su base par a ap ro vech ar el nuevo entorno N E T Framework. C omo C# forma parte de este nuevo mundo N ET . deberá c o mp re n der perfectamente lo que proporciona N E T Framework y de que manera aumenta su productiv idad. N E T Framework se compone de cuatro partes, como se muestra en la figura 1 .1: el entorno común de ejecución, un conjunto de bibliotecas de clases, un grupo de lenguajes de progr amaci ón y el entorno A S P . N ET . N E T Framework fue dise ñado con tres objetivos en mente. Primero, debía lograr aplicaciones Wi nd ows mucho mas estables, aunque también debía p ropor ci onar una aplicación con un mayor grado de seguridad. En segundo lugar, debía simplificar el desarrollo de aplicaciones y servicios Web que no sólo funcionen en pl at af ormas tradicionales, sino también en dispositivos móviles. Por ultimo, el entorno fue diseñado para propor ci onar un solo g rup o de bibliotecas que pudieran t raba jar con v arios len guajes. Las siguientes secciones analizan cada uno de los componentes de N E T Framework. C o m m o n L a n g u a g e R untim e B ib lio te c a s de C lase L e n g u a je s de P ro g ra m a ció n (C #, V C + + , BV.NET, J S c rip t.N E T ) A S P .N E T
Figura 1.1. Los cuatro com ponentes de NET Framework
Desarrollo Web N E T F ramework fue diseñado con una idea en mente: potenciar el desarrollo de Internet. Este nuev o incentivo par a el desarrollo de Internet se llama s e r v a i o s Web. Puede pensar en los servicios Web como en una pági na We b que interactua con progr amas en lugar de con gente. En lugar de enviar paginas Web. un servicio Web recibe una solicitud en formato XML. realiza una función en concreto \ luego devuelve una respuesta al solicitante en forma de mensaje XML.
38
NOTA: XML o el Lenguaje de marcado extensible es un lenguaje autodescriptivo muy parecido a HTML. Por otra parte, XML no consta de etiquetas predefinidas, lo que le concede una gran flexibilidad para repre sentar una amplia variedad de objetos. Una típica aplicación par a un servicio We b podría ser corno c apa situada en lo alto de un sistema de f acturación de una empresa. Cu and o un usuario que navega por la red compra los productos de una p ágina Web. la información de la compra es enviada al servicio Web. que calcula el precio de todos los productos, añade una línea a la base de datos de existencias y devuelve una respuest a con una confirmación de pedido. Este servicio We b no sólo puede int eract uar con páginas Web, puede i nt eract uar con otros servicios Web. como un sistema de cuentas de pago de una empresa. Para que el modelo de servicio We b s obreviva a la evolución natural de los lenguajes de programación, debe incluir muchas más cosas que un simple interfaz para la Web. El modelo de servicio Web también incluye protocolos que permiten que las aplicaciones encuentren serv icios Web disponibles en una red interna o en Internet. Este protocolo también permite a la aplicación expl orar el servicio Web y decidir cómo comuni carse con él y cómo i nt ercambiar información. Para per mi tir el descubri mi ento de serv icios We b se estableció la Descripción, d es c ub ri miento e integración universal (UDDI). Esta permite que los servicios We b sean registrados y consultados, b as ándos e en datos clav e como el nombre de la c o m pañía. el tipo de servicio y su localización geográfica.
Desarrollo de aplicaciones Aparte del desarrollo Web. con N E T Framevvork también puede construir las t r adi ci ona le s a pl i ca c io ne s W i nd ow s. Es tas a pl i ca c io ne s c r e a d a s con N E T Framevvork se basan en Windows Forms. Wi ndows Forms es una especie de cruce entre los formularios de Visual Basic 6 y los formularios de Visual C++. Aunque los formularios parecen iguales a sus predecesores, están completamente ori ent a dos a objetos y basados en clases, de forma muy parecida a los formularios obje to de Microsoft Foundation Class. Estos nuevos Wi ndows Forms ahora admiten muchos de los controles clásicos que aparecían en Visual Studio. como Button. Te:-:tBox y Labe 1. junto a los controles ActiveX. Aparte de los controles tradicionales, también admite nuevos c o m p o n e n t e s co mo P r i n t P r e v i e w . LinkLabel. C o l o r D i a l o g y
OpenFileDialog. La creación de aplicaciones con N E T también brinda muchas mejoras no disponibles en otros lenguajes, como la seguridad. Estas medidas de seguridad pueden determinar si una aplicación puede escribir o leer un archivo de disco. Tambi én permiten insertar firmas digitales en la aplicación par a asegurarse de
39
que la aplicación fue escrita por una fuente de confianza. N E T Framework tambic'n permite incluir información de componentes, y de versión, dentro del código real. E s t o h a c e p o s i b l e qu e el s o f t w a r e se i n s t a l e c u a n d o se lo p i d a n , aut omát icame nte o sin la intervención del usuario. Juntas, todas estas funciones reducen los costes asistencia par a la empresa.
Entorno común de ejecución Los lenguajes de p ro gr amaci ón suelen componer se de un compi lador y un entorno de ejecución. El comp il ado r convierte el código que escribe en codigo ejecutable que puede ser ejecutado por los usuarios. El entorno de ejecución p r o porciona al código ejecutable un conjunto de servicios de sistema operativo. Estos servicios están integrados en una ca pa de ejecución de modo que el código no necesite p re ocu par se de los detalles de bajo nivel de f uncionamiento con el siste ma operativo. Operaci ones como la gestión de memori a y la entrada y salida de archivos son buenos ejemplos de servicios realizados por un entorno de ej ec u ción. Antes de que se desarrollara NET. cada lenguaje c onstaba de su propio ent or no de ej ecuci ón. Vi sual Basi c c o n s t a de un t i e m po de ej ec uci ón l l am ad o M S V B V M 6 0 DLL Visual C ++ utiliza una DLL llamada M S V C R T . D L L . Cada uno de estos módulos de e ntorno de ejecución p ro po rci onaba un conjunto de s er vicios de bajo nivel par a codificar lo que los progr amadore s escribían. Los p r o g ra ma do res escribían codigo y luego lo compi laban con el apr opi ado tiempo de ejecución en mente. El código ejecutable incluiría su propio tiempo de ejecución, que puede ser instalado en el equipo del usuario si aún no est aba presente. El principal probl ema que presentan estos entornos de ejecución es que e st a ban diseñados para usar un solo lenguaje. El tiempo de ejecución de Visual Basic propor ci onaba algunas funciones estupendas par a operaciones como t rab aj ar con memori a e iniciar objetos C O M . pero estas funciones estaban disponibles solo para los usuarios de Visual Basic. Los programadores que usaban Visual C ++ no podían u sar las funciones del tiempo de ejecución de Visual Basic. Los usuarios de Visual C ++ tenían su propio tiempo de ejecución, con su propia larga lista de funciones, pero esas funciones no estaban disponibles p ar a los usuarios de Visual Basic. Este enfoque de "tiempos de ejecución separados" impedía que los l engua jes pudiesen funcionar conjuntamente sin problemas. No es posible, por ejemplo, t omar algo de memoria en un fr agmento de código en Visual Basic y luego p a s á r selo a una parte de código en Visual C++. lo que liberaría la memoria. Los diferentes tiempos de ejecución implementan su propio conjunto de funciones a su manera. Los conjuntos de funciones de los diferentes tiempos de ejecución son inconsistentes. Incluso las funciones que se encuent ran en más de un tiempo de ejecución se implementan de diferentes formas, haciendo imposible que dos f r a g mentos de código escritos en diferentes lenguajes trabajen juntos.
40
Uno de los objetivos de diseño de N E T Framework era unificar los motores de ejecución par a que todos los pro gr amado re s pudieran t raba jar con un solo conjunto de servicios de ejecución. La solución de N E T Framework se llama Entorno común de ejecución (CLR). El C L R propor ci ona funciones como la g es tión de memoria, la seguridad y un sólido sistema de control de errores, a c ua l quier lenguaje que se integre en N E T Framework. Graci as al CLR. todos los lenguajes N E T pueden u s ar varios servicios de ejecución sin que los pro gr amadores tengan que p re ocupar se de si su lenguaje p ar ti cular admite una función de ejecución. El C LR también permite a los lenguajes intcractuar entre sí. La memoria puede asignarse mediante código escrito en un lenguaje (Visual Basic NET. por ejemplo) y puede ser liberada con código escrito en otro (por ejemplo. CU). Del mismo modo, los errores pueden ser detectados en un lenguaje y procesados en otro.
Bibliotecas de clase .NET A los p rogr amadore s les gusta t rab aj ar con codigo que ya ha sido probado y ha demost rado que funciona, como el API Wi n32 y la biblioteca de clase MFC. La reutilización del código lleva mucho tiempo siendo el objetivo de la comunidad de des a bo ll a do re s de software. Sin embargo, la posibilidad de reutilizar el codigo no ha estado a la altura de las expectativas. Muchos lenguajes han tenido acceso a cuerpos de codigo previamente c o m probados y listos p ar a ser ejecutado. Visual C++ se ha beneficiado de las biblio tecas de clase, como las Clases de fundación Microsoft (MFC), que permitió a los p rogr amadore s de C + + crear aplicaciones Wi ndows rápidamente, y la Biblio teca activa de plantillas (ATL). que proporci ona ayuda para crear objetos COM. No obstante, la nat uraleza específica del lenguaje de estas bibliotecas las ha hecho inservibles par a ser usadas en otros lenguajes Los p rogr amadore s de Vi sual Basic tienen vetado el uso de A T L p a r a crear sus objetos COM. N E T Framework p roporciona muchas clases que ayudan a los programadores a reutilizar el codigo. Las bibliotecas de clase N E T contienen código para p r o g r a ma r subprocesos. entrada y salida de archiv os, compatibilidad para bases de datos, análisis X M L y est ructuras de datos, como pilas y colas. Y lo mejor de todo, toda esta biblioteca de clase está disponible para cualqui er lenguaje de progr amaci ón compatible con N E T Framework. Graci as al CLR. cualquier len guaje N E T puede us ar cualquier clase de la biblioteca NET. Como ahora todos los lenguajes admiten los mismos tiempos de ejecución, pueden reutilizar cu a l quier clase que funcione con N E T Framework. Esto significa que cualquier funcionalidad disponible par a un lenguaje también estará disponible par a cu a l quier otro lenguaje NET. El c u a d r o de re ut il i zac ión de b i b l io t ec a s de cl as es d i b u j a d o p or N E T F ramework se vuelve aún mejor cuando se da cuenta de que la reutilización se extiende a su código, no sólo al código que Microsoft lanza con NET. El código
41
de Microsoft solo es código que fue escrito usando un lenguaje que N E T admitía y se compi laba usando una herramienta de desarrollo NET. Esto significa que Microsoft está usando las mismas herramientas que us ar a par a escribir su códi go. Puede escribir código ca paz de ser usado en otros lenguajes NET. e x a c t a mente lo mismo que Microsoft con su biblioteca de clase. N E T Framework permite escribir código en C#. por ejemplo, y enviárselo a progr amadores en Visual Basic NET. que pueden usar ese código que compilo en sus aplicaciones. La figura 1 2 ilustra una visión muy general de las bibliotecas de clase NET. System .W eb
System
System .Data
Services
Runtime
ADO
Description
[
InteropServices
Discovery
[
Remoting Serialization
Protocols
Collections
Ul [
H tmIControls
(
W ebC ontrols ~ |
Design
SQL
SQL Types
C onfiguration
)
System.Xm l Diagnostics XSLT Reflection
Caching
XPath_______ I (______ Security Serialization
C onfiguration
Security
(
Text Threading
(~
~~j
ServiceProcess
System .Drawing Drawing2D
SessionState Globalization Imaging
System . W indow s. Forms
IO
I_______ Design_____
Net
[~ C om ponentM odes
R esources
Printing
Text
Figura 1.2. Las bibliotecas de clase NET Framework
Lenguajes de programación .NET N E T F ramework p ropor ci ona un conjunto de herramientas que le ayudan a el aborar código que funciona con N E T Framework. Microsoft propor ci ona un conjunto de lenguajes que ya son "compatibles con NET" . C# es uno de estos lenguajes. Tambi én se han creado nuevas versiones de Visual Basic y Visual C++ para apr ovechar las ventajas de N E T Framework y hay una versión de J scr ipt . NET en camino.
42
El desarrollo de lenguajes compatibles N E T no se limita a Microsoft. El g r u po N E T de Microsoft ha publicado documentación que muest ra cómo los prov ee dores pueden hacer que sus lenguajes funcionen con NET. y estos proveedores están haciendo lenguajes como C O B O L y Perl compatibles con N E T Framevvork Actualmente, una tercera parte de los prov eedores e instituciones están p r e p a r a n do más de 20 lenguajes capaces de integrarse en . NET Framevvork.
Entorno ASP.NET Internet fue concebida en un principio para enviar contenido estático a los clientes Web Estas páginas Web nunca cambi aban y eran las mismas para todos los usuarios que navegaban hasta esa localización. Microsoft lanzo servidores activ os para permitir la creación de páginas dinámicas bas adas en la aportación e interacción del usuario con una página Web. Esto se consiguió mediante el uso de secuencias de comandos que funcionaban por detras de la pagina Web. g en er al mente escritas en VB Script. C uando los usuarios visitaban una pagina Web. se les podía pedir que verificasen la i nformación (manual mente o con una cookie). y luego la secuencia de comandos podía generar una página Web que le era devuel ta al usuario. A S P . N E T mejora al original AS P propor ci onando "codigo detrás". En ASP. H T M L y las secuencias de comando se mezclaban en un documento. C on A SP .NET y su "código detrás'', se puede s eparar el codigo y HT M L. Ahora, cuando la lógica de una pagina We b necesite cambiar, no hace falta bu sca r por cientos o miles de líneas de H T M L par a localizar la secuencia de comandos que necesita modificarse. De forma parecida a Windows Forms. ASP N E T admite Web Forms Los Web Forms permiten ar r as t ra r y colocar controles en sus formularios y codificarlos como haría en cualquier típica aplicación Windows. Como A S P . N E T usa N E T Framevvork. también usa el compilador Justo a tiempo (JIT) Las paginas ASP tradicionales se ejecutaban muy lentamente p o r que el codigo era interpretado. A S P . N E T compila el codigo cuando es instalado en el servidor o la pri mera v ez que es necesario, lo que aument a enormement e la velocidad.
Historia de C, C++ y C# El lenguaje de pro gr amaci ón C# fue creado con el mismo espíritu que los lenguajes C y C++. Esto explica sus poderosas prestaciones y su fácil curva de aprendizaje. No se puede decir lo mismo de C y C++. pero como C# fue creado desde cero. Microsoft se tomó la libertad de eliminar algunas de las prestaciones mas pesadas (cómo los punteros). Esta sección echa un vistazo a los lenguajes C v C++. siguiendo su ev olución hasta C#.
43
El lenguaje de p rogr amaci ón C fue diseñado en un principio p ar a ser usado en el sistema operativo UNIX. C se usó par a crear muchas aplicaciones UNIX, incluyendo un compi lador de C. y a la l arga se usó par a escribir el mismo UNIX. Su ampl ia aceptación en el mundo académi co se amplió al mundo comercial v los proveedores de software como Microsoft y Borland publ icaron compi ladores C p ar a los ordenadores personales. El API original par a Wi ndo ws fue diseñado par a t raba jar con código Wi ndows escrito en C y el último conjunto de API básicos del sistema operativo Wi ndows sigue siendo compatible con C hoy en día. Desde el punto de vista del diseño. C carecía de un detalle que ya ofrecían otros lenguajes como Smalltalk: el concepto de objeto. Piense en un objeto como en una colección de datos y un conjunto de operaciones que pueden ser realizadas sobre esos datos La codificación con objetos se puede lograr usando C. pero la noción de objeto no era obligatoria p ar a el lenguaje. Si quería es tru ct ur ar su codigo para que simulara un objeto, perfecto. Si no. perfecto también. En realidad a C no le importaba. Los objetos no eran una parte fundamental del lenguaje, por lo que mucha gente no prestó mucha atención a este es tándar de programaci ón. Una vez que el concepto de orientación a objetos empezó a g ana r aceptación, se hizo ev idente que C necesitaba ser d epur ado p ar a ado pt ar este nuevo modo de considerar al codigo C ++ fue creado p ar a encarnar esta depuración. Fue di seña do par a ser compatible con el anterior C (de maner a que todos los prog ramas escritos en C pudieran ser también progr amas C ++ y pudieran ser compilados con un compi lador de C++). La principal aportación a C ++ fue la compatibilidad par a el nuevo concepto de objeto. C ++ i ncorporó compatibilidad par a clases (que son "plantillas" de objetos) y permitió que toda una generación de p rogr amadores de C pensaran en términos de objetos y su comportamiento. El lenguaje C + + es una mejora de C. pero aún así presenta algunas des vé nta las C y C + + pueden ser difíciles de manejar. A diferencia de lenguajes fáciles de us ar como Visual Basic. C y C ++ son lenguajes de muy "bajo nivel" y exigen que mucho código par a f uncionar correctamente. Tiene que escribir su propio codigo par a manej ar aspectos como la gestión de memori a y el control de errores. C y C ++ pueden dar como resultado aplicaciones muy potentes, pero debe asegurarse de que el código funciona bien. Un error en la escritura del p ro g ra ma puede hacer que toda la aplicación falle o se c omport e de f or ma inesperada. Como el objetiv o al diseñar C++ era retener la compatibilidad con el anterior C. C++ fue incapaz de e sc ap ar de la nat ural eza de bajo nivel de C. Microsoft diseño CU de modo que retuviera casi toda la sintaxis de C y C++. Los progr amadores que esten familiarizados con esos lenguajes pueden escoger el código CU v empezar a p ro gr a ma r de f orma relativamente rápida. Sin embargo, la gran v entaja de CU consiste en que sus diseñadores decidieron no hacerlo c o m p a tible con los anteriores C y C++. Aunque esto puede parecer un mal asunto, en realidad es una buena noticia. CU elimina las cosas que hacían que fuese difícil t rab aj ar con C y C++. Como todo el código C es también código C++. C ++ tenía que mant ener todas las rarezas y deficiencias de C. CU parte de cero y sin ningún
44
requisito de compatibilidad, así que puede mant ener los puntos fuertes de sus predecesores y des cart ar las debilidades que compl icaban las cosas a los p r o g r a madores de C y C++.
Introducción a C# C#. el nuevo lenguaje presentado en N E T Framework. procede de C++. Sin embargo. C# es un lenguaje orientado a objetos (desde el principio), moderno y seguro.
Características del lenguaje Las siguientes secciones hacen un rápido repaso a algunas de las caracterí st i cas de C#. Si no está familiarizado con al guno de estos conceptos, no se p r e o cu pe. Todos serán t rat ados con detalle en capítulos posteriores.
Clases Tod o el código y los datos en C# deben ser incluidos en una clase. No puede definir una variable fuera de una clase y no puede escribir ningún código que no esté en una clase. Las clases pueden tener constructores, que se ejecutan c u a n do se crea un objeto de la clase, y un destructor, que se ejecuta cuando un objeto de la clase es destruido. Las clases admiten herencias simples y todas las clases derivan al final de una clase base llamada objeto. C# admite técnicas de versio nes par a a yu da r a que sus clases evolucionen con el tiempo mientras mantienen la compatibilidad con código que use versiones anteriores de sus clases. Por ejemplo, observe la clase llamada Family. Esta clase contiene los dos ca mpos estáticos que incluyen el nombre y el apellido de un mi embro de la fa mi lia junto con un método que devuelve el no mbr e compl et o del mi embro de la familia. class
Cíassi
{ public public public
string FirstName; string LastÑame; string F u l l N a m e ()
{ return
FirstName
+ LastName;
NOTA: La herencia simple significa que una clase de C# sólo se puede derivar de una clase base.
45
C# le permite a gr u pa r sus clases en una colección de clases llamada espacio de nombre. Los espacios de nombre tienen nombres y pueden servir de ayuda para or ganizar colecciones de clases en agr upaci ones lógicas. A medida que empieza a apr ender C#. des cubri rá que todos los espacios de nombre relevantes par a N E T Framevvork empi ezan con el término S y s t e m . Microsoft también ha decidido incluir algunas clases que a yudan a la c omp at i bi lidad con versiones anteriores y al acceso a los API. Estas clases se incluven en el espacio de nombre Microsoft.
Tipos de datos C# permite t raba jar con dos tipos de datos: de valor y de referencia. Los de valor contienen valores reales. Los de referencia contienen referencias a valores al macenados en algún lugar de la memoria. Los tipos primitivos como char. int y float. junt o con los valores y est ructuras comentados, son tipos de valor. Los tipos de referencia tienen variables que tratan con objetos y matrices. C# viene con tipos de referencia predefinidos (object y string). junto con tipos de valor predefinidos (sbyte. short. int. long. byte. ushort. uint. ulong. float. double. bool. char y decimal). Tamb ién puede definir en el código sus propios tipos de v alor y referencia. Todos los tipos de v alor v de referencia derivan en ultima instancia de un tipo base llamado object. C# le permite convertir un valor de un tipo en un valor de otro tipo. Puede t rab aj ar con conversiones implícitas y explícitas. Las conversiones implícitas siempre funcionan y nunca pierden información (por ejemplo, puede convertir un int en un long sin perder ningún dato porque un long es mayor que un int). Las conversiones explícitas pueden producir perdidas de datos (por ejemplo, c on vertir un long en un int puede producir perdida de datos porque un long puede contener valores mayores que un int). Debe escribir un operador cast en el código par a que se produz ca una conversión explicita. En C# puede t raba jar con matrices unidimensionales y multidimensionales. Las matrices multidimensionales pueden ser rectangulares, en las que cada una de las matrices tiene las mismas dimensiones, o escalonadas, en las que cada una de las matrices puede tener diferentes dimensiones. Las clases y las est ructuras pueden tener miembros de datos llamados p r o p i e dades y c a m p o s . Los campos son variables que están asociadas a la clase o est ructura a la que pertenecen. Por ejemplo, puede definir una estructura llamada Empleado que tenga un ca mp o llamado Nombre. Si define una variable de tipo Empleado llamada Emplead o Actual, puede r ecuper ar el nombre del e m pleado escribiendo EmpleadoActual .Nombre. Las propiedades son como los campos, pero permiten escribir codigo que especifique lo que debería ocurrir c uando el codigo acceda al valor. Si el nombre del empleado debe leerse de una base de datos, por ejemplo, puede escribir código que diga "cuando alguien p r e gunte el valor de la propiedad Nombre, lee el nombre de la base de datos v devuelve el nombre como una cadena".
46
Funciones Lina función es un fragment o de código que puede ser i nvocado y que puede o no devolver un valor al código que lo invocó en un principio. Un ejemplo de una función podría ser la función Ful 1Name mos trada anteriormente en este c a pí t u lo. en la clase Family. Una función suele asociarse a fragmentes de código que devuelven información, mientras que un método no suele devolver información. Sin embargo, par a nuestros propósitos, generalizamos y nos referimos a las dos como funciones. Las funciones pueden tener cuatr o tipos de parámetros. •
Parámet ros de entrada, tienen valores que son enviados a la función, pero la función no puede ca mbi ar esos v alores.
•
Parámet ros de salida: no tienen valor cuando son enviados a la función, pero la función puede darles un valor y enviar el valor de v uelta al invocador. Parámet ros de referencia: introducen una referencia en otro valor. Tienen un valor de ent rada p a r a la función y ese valor puede ser c ambi ado dentro de la función. Parámet ros Params: definen un numero variable de argument os en una lista.
CU y el C L R t rabajan junt os par a brindar gestión de memoria automática. No necesita escribir código que diga "asigna suficiente memori a p ar a un numero entero" o "libera la memori a que está us ando este objeto". El C L R monitoriza el uso de memori a y recupera aut omát icame nte más cuando la necesita. Tambi én libera memoria automáticamente cuando detecta que ya no está siendo usada (esto también se conoce como recolección de objetos no utilizados). CU propor ci ona varios operadores que le permiten escribir expresiones m a t e m át icas y de bits. Mu ch os (pero no todos) de estos o per ad or es pueden ser redefinidos, permitiéndole c a mb i ar la forma en que trabajan estos operadores. CU admite una larga lista de expresiones que le permiten definir varias rutas de ejecución dentro del código. Las instrucciones de flujo de control que usan p a l a bras clave como if. switch. while. for. break y continué permiten al código bi furcarse por caminos diferentes, dependiendo de los v alores de sus v a riables. Las clases pueden contener código y datos. Cada mi embro de una clase tiene algo llamado ámbi to de accesibilidad, que define la visibilidad del miembro con respecto a otros objetos. CU admite los ámbitos de accesibilidad publ ic. protected. internal. protected internal y private.
Variables Las variables pueden ser definidas como constantes. Las constantes tienen valores que no pueden ca mb ia r durante la ejecución del código. Por ejemplo, el
47
valor de pi es una buena muest ra de una constante porque el valor no cambi a a medida que el codigo se ejecuta. Las declaraciones de tipo de e numer aci ón e s p e cifican un nombre de tipo par a un gru po de constantes relacionadas. Por ejemplo, puede definir una enumeración de planetas con valores de Mercurio. Venus. T i e rra. Marte. Júpiter. Saturno. Urano. Neptuno y Plutón. y usar estos nombres en el código. Usando los nombres de enumeraciones en el código hace que sea más fácil leerlo que si usara un número para representar a cada planeta. CU incorpora un mecani smo par a definir y procesar eventos. Si escribe una clase que realiza una operación muy larga, quizás quiera invocar un evento c u a n do la operacion se complete. Los clientes pueden suscribirse a ese evento e in cluirlo en el codigo. lo que permite que se les pueda av isar cuando haya ac ab ad o su operacion. El mecani smo de control de eventos en C# us a delegados, que son variables que se refieren a una función.
NOTA: Un controlador de eventos es un procedimiento en el código que determina las acciones que deben llevarse a cabo cuando ocurra un evento, como que el usuario pulse un botón. Si la clase contiene un conjunto de valores, los clientes quizás quieran acceder a los valores como si la clase fuera una matriz. Puede conseguirlo escribiendo un fr agment o de codigo conocido como indexador. Sup ong a que escribe una clase llamada Arcolris. por ejemplo, que contenga el conjunto de los colores del arco iris. Los visitantes querrán escribir Mi Arcolris [ 0 ] par a recuperar el primer color del arco iris. Puede escribir un indexador en la clase Arcolris p ar a definir lo que se debe devolver cuando el visitante acceda a esa clase, como si fuera una matriz de valores.
Interfaces C# admite interfaces. que son grupos de propiedades, métodos y eventos que especifican un conjunto de funcionalidad. Las clases C# pueden i mplementar interfaces. que informan a los usuarios de que la clase admite el conjunto de funcionalidades documentado por la interfaz. Puede desarrollar implementaciones de interfaces sin interferir con ningún código existente Una vez que la interfaz ha sido publicada, no se puede modificar, pero puede evolucionar mediante herencia Las clases CU pueden i mplementar muchas interfaces. aunque las clases solo pueden derivarse de una clase base. Veamos un ejemplo de la vida real que puede beneficiarse del uso de interfaces par a ilustrar su papel ext remadament e positivo en CU. Mucha s de las apl icaci o nes disponibles hoy en día admiten módulos compl ement ari os. S up on g am os que tiene un editor de código p ar a escribir aplicaciones. Este editor, al ejecutarse, puede c a r g ar módulos complementarios. Para ello, el módulo compl ement ari o debe seguir unas cuantas reglas. El módul o compl ement ari o de DLL debe e x p o r
48
tar una función llamada CEEntry v el nombre de la DLL debe empez ar por CEd. Cuand o ejecutamos nuestro editor de codigo. este b us ca en su directorio de trabaj o todas las DLL que empiecen por CEd. C uando encuentra una. la abre y a continuación utiliza GetProcAddress para localizar la función CEEntry dentro de la DLL. verificando asi que ha seguido todas las reglas exigidas para crear un módulo complementario. Este método de creación y a pert ura de módulos compl ement ar ios es muy pesado porque sobre carg a al editor de codigo con mas tareas de verificación de las necesarias. Si us ár amo s una interfaz en este caso, la DLL del módulo complementario podría haber i mplementado una interfaz. g a r a n tizando asi que todos los métodos necesarios, propiedades y eventos esten pres en tes en la propi a DLL y que funciona como especifica la documentación.
Atributos Los atributos aportan información adicional sobre su clase al C L R Antes, si quería que la clase fuera autodescriptiva. tenía que seguir un enfoque sin c o nexión. en el que la document aci ón fuera al ma c en ad a en archivos externos como un archivo IDL o incluso archivos H TM L. Los atributos solucionan este probl e ma permitiendo al p r og r am ad o r vincular información a las clases (cualquier tipo de información). Por ejemplo, puede us ar un atributo para insertar información de documentación en una clase, explicando cómo debe ac tua r al ser usada. Las posibilidades son infinitas y ésa es la razón por la que Microsoft i ncl i ne tantos atributos predefinidos en N E T Framework.
Cómo compilar C# Ejecutar el código C# con el compi lador de C# produce dos i mportantes co n j unt os de información: el código y los metadatos. Las siguientes secciones d escri ben estos dos elementos y luego terminan e xami nando el componente esencial del código NET: el ensamblado.
Lenguaje intermedio de Microsoft (MSIL) El código generado por el compi lador de C# está escrito en un lenguaje l lama do Lenguaje intermedio de Microsoft, o MSIL. MSIL se c ompone de un conjunto específico de instrucciones que especifican cómo debe ser ej ecut ado el codigo. Contiene instrucciones p ar a operaciones como la inicializacion de variables, los métodos de llamada a objetos y el control de errores, por citar solo unos pocos. C# no es el único lenguaje cuyo código fuente se t rans fo rma en MS IL durante el proceso de compilación. Todos los lenguajes compatibles con NET. incluido Vi sual Basic N E T y C ++ gestionado, producen MS IL cuando se compila su código fuente. Como todos los lenguajes N E T se compilan en el mi smo conjunto de instrucciones MSIL. \ como todos los lenguajes N E T usan el mismo tiempo de ejecución, los códigos de diferentes lenguajes y de diferentes compi ladores p u e den funcionar juntos fácilmente.
49
MS IL no es un conjunto de instrucciones específicas par a una CPU física. MSIL no sabe nada de la CPU de su equipo y su equipo no conoce nada de MSIL Entonces, ¿cómo se ejecuta el codigo NET. si su CPU no lo puede interpretar'.'1 La respuesta es que el codigo MSIL se convierte en código específico de CPU cuando se ejecuta por p ri mera vez. Este proceso se llama compilación "Justo a tiempo" o JIT. El t rabajo de un compi lador JIT es convertir el codigo genérico M SI L en codigo que su equipo pueda ejecutar en su CPU. Quizas se esté preguntando por lo que parece ser un paso innecesario en el proceso. <,Por que generar MS IL cuando un compi lador podría generar di rect a mente código específico p ar a la CPU Después de todo, los compi ladores siempre lo han hecho en el pasado. Hay un p ar de razones que lo explican. En primer lugar. MSIL permite que el codigo compilado se transfiera fácilmente a har dware diferente. Suponga que ha escrito algo de codigo C# y le g us tarí a ejecutarlo en su ordenador personal y en su dispositivo portátil. Es muy probable que estos dos equipos tengan diferentes CPU. Si sólo tiene un compi lador de C# p ar a una CPU específica, entonces nece sita dos compiladores de C#: uno par a la CPU de su ordenador personal v otro par a la CPU de su dispositivo portátil. Tendría que compilar el código dos veces, as egurándos e de poner el codigo adecuado par a cada equipo. Con MSIL. solo se compila una vez. Al instalar NET Framework en su ordenador personal se incluy e un compilador JIT que convierte el MS IL en código específico par a la CPU de su ordenador personal. Al instalar N E T Framework en su ordenador portátil se incluye un compi lador J IT que conv ierte el mismo MS IL en código específico p ar a la CPU de su dispositivo portátil. Ahora tiene un solo código base MSIL que puede eje cutarse en cualquier equipo que tenga un compi lador JIT NET. El compilador JIT en ese equipo se o cu pa de hacer que el código se ejecute. Ot ra razón par a que el compi lador use MS IL es que el conjunto de inst rucci o nes puede leerse fácilmente por un proceso de verificación. Parte del t rabajo del compi lador J IT es verificar el código par a a segurars e de que resulte lo mas claro posible. El proceso de verificación as egura que el código accede a la memori a correctamente y de que está usando los tipos de variable correctos al llamar a métodos que esperan un tipo específico. Estos procesos de verificación se a s e g u ran de que el codigo no ejecute ninguna instrucción que origine un fallo. El c on j unt o de instrucciones MS IL fue diseñado par a hacer este proceso de verificación relativamente sencillo. Los conjuntos de instrucciones específicos par a cada CPU están opt imi zados para la rápida ejecución del código, pero producen código que puede ser difícil de leer y. por tanto, de v erificar. Tener un compi lador de C# que produce directamente codigo específico par a C PU puede hacer difícil la v erifica ción del codigo. o incluso imposible. Al permitir al compi lador JIT de N E T Framework que verifique el código se asegura de que el código acceda a la m e m o ria en un modo libre de fallos y que los tipos de variable sean usados c or r ec ta mente.
50
Metadatos El proceso de compilación también produce metadatos. lo que es una parte importante de la historia de como se compart e el codigo .NET. Tan to si usa CU para construir una aplicación para un usuario final como si lo usa para construir una biblioteca de clases que sera usada por la aplicación de alguna otra persona, querrá usar codigo N E T ya compilado. Ese codigo puede ser propor ci onado por Microsoft como parte de N E T Framework. o por un usuario a través de Internet. La clave par a usar este codigo externo es hacer que el compi lador de CU sepa que clases y variables están en el otro codigo base par a que pueda c o m pa r ar el codigo fuente que ha escrito con el codigo que ap arece en el codigo base precompi lado con el que esta trabajando. Piense en los metadatos como en "tablas de contenidos" para el codigo c o mp i lado. El compi lador de CU coloca met adatos en el codigo compi lado j unt o al MS IL generado. Este metadato describe con exactitud todas las clases que escri ba y como están estructuradas. Todos los métodos y variables de las clases están completamente descritos en los metadatos. listos para ser leidos por otras apl i ca ciones. Visual Basic NET. por ejemplo, puede leer los met adatos para que una biblioteca N E T proporcione la función Intel 1iSense de listar todos los métodos disponibles para una clase en concreto. Si al guna vez ha t rabaj ado con C O M (Modelo de objetos componentes), q ui zas este familiarizado con las bibliotecas de t i p o s . Las bibliotecas de tipos tratan de propor ci onar una funcionalidad "de tabla de contenidos" similar par a objetos C O M Sin embargo, las bibliotecas de tipos presentaban algunas limitaciones, una de las cuales consistía en que no se incluían todos los datos importantes relacionados con el objeto. Los met adatos d e . N E T no presentan este inconxeniente. Tod a la información necesaria par a describir una clase en un codigo esta situada en el metadato. Los metadatos tienen todas las ventajas de las bibliotecas de tipos C OM . pero sin sus limitaciones.
Ensamblados A veces usara CU para construir una aplicación para un usuario final. Estas aplicaciones se presentan como archivos ejecutables con extensión EXE. Windows siempre ha t rabaj ado con archivos EXE como p rogr amas de aplicación y CU admite a la perfección la construcción de archivos EXE. Sin embargo, puede h aber ocasiones en las que no quiera construir una ap li ca ción completa. Quizas quiera construir en su lugar una biblioteca de codigo que pueda ser usada por otras personas. Quizas también quiera construir algunas clases de utilidad, por ejemplo, y luego transferir el codigo a un p ro g ra ma do r de Visual Basic NET. que usara sus clases en una aplicación de Visual Basic NET En casos como este, no construirá una aplicación, sino un ensamblado. Un ensambl ado es un paquete de codigo y metadatos. Cuand o utiliza un c on j unt o de clases en un ensamblado, esta usando las clases como una unidad y estas clases compar ten el mismo niv el de control de v ersión, información de s eg u n d a d
51
y requisitos de activación. Imagine un ensambl ado como una "DLL lógica". Si no está familiarizado con Microsoft Transact ion Server o C O M + , puede imaginar un ens ambl ado como el equivalente N E T de un paquete. Hay dos tipos de ensamblados: ensamblados privados y ensamblados globales Al construir el ensamblado, no necesita especificar si quiere construir un e n s a m blado privado o global. La diferencia es ostensible cuando se implcmenta el e n samblado. Con un ens ambl ado privado, hace que el código esté disponible par a una sola aplicación. El ensambl ado se empaquet a como una DLL y se instala en el mismo directorio que la aplicación que lo está usando. Con el uso de un e n s a m blado privado, la única aplicación que puede u sar el código es el ejecutable s i tu a do en el mi smo directorio que el ensamblado. Si quiere compartir el código con varias aplicaciones, quizás quiera considerar el uso del codigo como e ns ambl ado global. Los ensambl ados globales pueden ser usados por cualquier aplicación N E T en el sistema, sin i mport ar el directorio en el que este instalada. Microsoft incorpora ensambl ados en N E T F ramework y cada uno de los ensambl ados de Mi crosoft se instala como e ns ambl ado global. N E T Fr amewor k contiene una lista de ensambl ados globales en un servicio lla mado caché de ens ambl ado g l oba l y el S DK de N E T Microsoft Framework incluye utilidades p ar a instalar y eliminar ensambl ados de la caché de e n s a m b l a do global.
Resumen En este capítulo se han explicado las bases de N E T Framework. Tra s seguir la evolución desde C a C + + y hasta C#. se han exami nado los puntos fuertes de la lista de pr e st ac io ne s de C'#. T a m b i é n se ha i nves ti gado el p r od u ct o del compi lador de C#. el código MS IL y los metadatos. y se ha revisado el uso de ens ambl ados como los bloques de const rucci ón esenciales del código N E T c o m pilado.
52
Escribir su primer programa en C# wm
Este capítulo le guía a través del desarrollo de una sencilla aplicación de C#. Tambi én aprenderá cómo están es tructur adas las aplicaciones C# más sencillas y cómo i nvocar al c ompi lador de C# p a r a convertir el código fuente en código que puede ser ejecutado por N E T Framcwork. Finalmente, apr ender a a d o cu men tar el código empl eando coment ari os de código fuente y cómo convertir aut omát i came nt e sus comentarios en un document o XML.
Cómo escoger un editor A la hora de escribir código p ar a N E T F ramcwork en C# tiene varias opci o nes. La elección más lógica es us ar Visual Studio NET. Usando Visual Studio. dispone de todas las ventajas de la tecnología IntelliSense. el m ar cado de sintaxis y muchas otras herramientas que aument an la productividad. Muchos editores de terceros intentan a u na r en un paquete las herramientas de producción de Visual Studio. Algunas de estas herramientas pueden ser de sc a rg a das como Shareware y otras son de libre distribución. Los ejemplos de este ca p í tulo usan sencillamente el Bloc de notas de Windows. Al usar el Bloc de notas, no sólo demostramos que se puede u sar cualquier editor de texto para escribir ap li ca
55
ciones en C'#. sino que también servirá par a aprender las bases necesarias para compilar aplicaciones. Ademas, el uso del Bloc de notas servirá par a demostrarle que no necesita confiar en ningún asistente par a generar el código. Puede simplemente co nc e n trarse en el lenguaje mismo, sin tener que aprender los detalles de un IDE. Sin embargo, tenga en cuenta que p ar a las aplicaciones más grandes quizás prefiera usar un editor que muestre los números de línea, lo que puede ser muv útil cuando se está bus ca ndo codigo defectuoso.
La aplicación Helio World El codigo que se muestra en el listado 2.1 es una aplicación de C# completa. Se ejecuta desde una ventana de la consola y present a el mensaje Helio W o r l d 1en la pantalla. Las siguientes secciones siguen este código línea a línea. Listado 2.1. Cómo escribir en la consola class
H e l 1oWorld
{ public
static void M a i n ()
{ System.Console.WriteLine ("Helio
World! " ) ;
} }
Cómo construir una clase La pri mera línea del pro gr ama C# define una clase. Una clase es un recipiente par a todo el código de la aplicación. A diferencia de C y C++. todo el código debe estar contenido en una clase, con escasas excepciones. Estas excepciones a la regla son la instrucción using. las declaraciones de est ructur as y la declaración namespace. Cual qui er intento de escribir codigo que no este contenido en una clase da como resultado un error de compilación. La primera línea de la aplicación Helio World empieza con la p al abr a clave c l a s s y. a continuación, la pal abr a H e l l o W o r l d . H e l / o W o r l d es el nombre de la clase que el código está creando. Cada clase debe tener asignado un nombre único par a que luego pueda referirse a ellas. Inmediatamente después de la declaración de clase se debe abrir una llave. La llave de apert ura se usa para abrir el cuerpo de la clase del código. Todo el código que escriba en la clase debe incluirse después de esta llave de apertura. Ademas de la llave de apert ura, también debe haber una llave de cierre, como la que aparece en la ultima línea de la aplicación Helio World. Asegúrese de que toda su pr ogr amaci ón este entre estas dos llaves.
56
El método Main() Tod as las aplicaciones escritas en C# deben const ar de un método llamado Main ( ) . Un método es un conjunto de instrucciones que realizan una acción. Este método puede devolver i nformación a la sección de código que lo llamó pero en determinadas ci rcunstanci as no es necesario que lo haga.
NOTA: Los términos método y función suelen usarse de forma indistinta, pero hay una diferencia. Un método es una función contenida en una clase. Una función suele ser un grupo de instrucciones que no está contenido en una clase y que suele estar en un lenguaje, como C o C++. Como en C# no se puede añadir código fuera de una clase, nunca tendrá una función. La palabra clave p u b l i c en la declaraci ón del método M a i n ( ) también contiene la palabra p u b l i c . que informa al compilador de que el método M a i n () debe ser públ icament e accesible. El método M a i n ( ) no sólo es accesible por otros métodos desde dentro de la aplicación, sino también ext ernament e por otras aplicaciones. Al d eclarar el método M a i n ( ) como público, está creando un p u n to de ent rada p ar a que Wi ndows inicie la aplicación cuando un us uar io lo desee. C uando un usuario haga doble clic sobre el icono de la a p l i c a c i ó n HelIoWorki . Wi ndows expl orará el ejecutable en busca de un punto de entrada con ese nombre. Si no encuentra una entrada, la aplicación no podrá ejecutarse. La pal abra Static en la declaración del método significa que el compi lador sólo debe permitir que exista en memori a una c opia del método por vez. C omo el mét odo Main ( ) es el punto de entrada a la aplicación, sería catastrófico permitir que el punto de entrada se abriese más de una vez ya que permitiría más de una copia de la aplicación en memori a e, indudablemente, algunos errores graves. Justo antes de la pal abra Main, verá la palabra Void. Void es lo que la función principal devuelve cuando ha compl et ado su ejecución. Significa que la aplicación no devuelve ningún valor después de haberse completado. Esta apl ica ción de ejemplo no es muy avanzad a, así que no necesita devolver ningún valor. Sin embargo, en circunstancias normales, la función Main ( ) devolvería un valor entero reemplazando la pal ab ra void por i n t . Valores de devolución válidos son cualquier tipo de dato simple definido en . NET Framework. De for ma muy pareci da a una declaración de clase, cualquier método que defina debe también contener una llave de a pe rt ur a y otra de cierre entre las que se debe colocar todo el código del método. Puede ver las llaves de a pe r tu ra y de cierre p a r a el método Main ( ) en las líneas 4 y 6 en el listado 2.1.
Cómo escribir en la consola La línea 5 del listado 2.1 contiene una llamada al método WriteLine. Este método está contenido en N E T Framework v escribe una cadena en la consola. Si
57
se ejecuta desde una ventana de la consola, el texto debería aparecer en la p a n t a lla. Si ejecuta este c omando desde un entorno de Visual Studio. cualquier result a do que pr oduzca aparecerá en la ventana de salida. Ya hemos aprendido que todas las funciones de CU deben ser definidas d en tro de una clase. Las funciones de N E T F ramework no son una excepción. La función WriteLine ( ) se encuentra en una clase llamada Consolé. La p al a bra clave Consolé, usada justo antes de la llamada a la función WriteLine (). indica al compi lador que la función WriteLine ( ) se encuentra en una clase llamada Consolé. La clase Consolé es una de las muchas clases de N E T Framework y la función WriteLine ( ) es un mi embro de la clase Consolé. El nombre de la clase está s eparado del nombre de la función que se invoca por medio de un punto. El nombre System a p a r e c e i nm ed i at am en t e antes del n om br e de clase Consolé. Las clases de . NET Framework están organizadas en grupos l lama dos espacios de nombre. Los espacios de nombre se explican con mas detalle en un capítulo posterior. Por ahora, piense en los nombres de espacios como en una colección de clase. La clase Consolé se encuentra en un espacio de nombre de N E T F ramework llamado System y debe escribir el nombre de este espacio de n ombre en el codigo. El c om pi l ado r de CU necesita en con tr ar el código de WriteLine ( ) para que la aplicación se ejecute correctamente y debe dar al c ompi lador suficiente i nformación sobre los espacios de nombre y las clases a n tes de enc ont rar el código de WriteLine () . El texto que se i ncl ine dentro de los paréntesis de WriteLine ( ) es una cadena. Una cadena en CU es una colección de caracteres encerrados entre c omi llas y g u ar dado s juntos como unidad. Al colocar la cadena entre los paréntesis se indica al compi lador que queremos pas ar la cadena como par ámet ro de la función WriteLine ( ) . La función WriteLine ( ) escribe una cadena en la consola \ el par ámet ro de la función indica a WriteLine ( ) qué cadena debe escribirse. La línea 5 incluye una gran cantidad de información que puede interpretarse de la siguiente forma: " Compi lador C#. quiero llamar a WriteLine ( ) con el p ar ámet ro de cadena 'Helio W o r k l r La función WriteLine ( ) se incluye en una clase llamada Consolé y la clase Consolé se i nc li ne en un espaci o de nombre llamado System. La línea 5 termina con un punto y coma. Todas las instrucciones deben t erminar con un punto y coma. El punto y la coma separan una instrucción de otra en C#.
Compilación y ejecución del programa Ahora que ya ha revisado el codigo del listado 2.1, es hora de ejecutarlo. Escriba el código del listado 2.1 en su editor de texto favorito y guárdelo como un archivo llamado listado 2.1. es. La extensión es es la extensión de todos los archivos que contienen codigo CU.
58
NOTA: Antes de compilar el ejemplo en C#, debe asegurarse de que el compilador de C# esté en su Path. La aplicación csc.exe generalmente está en la carpeta C: \W in d o ws \ M ic ro so ft .N ET \F ra me wo rk \ v i . 0 . x x x x (reemplace Vl.O.Xxxx con su versión de .NET Framework), lo que puede comprobar buscándola en Windows. Para añadir entradas a su ruta, busque en la Ayuda de Windows la palabra Path. A continuación abr a un símbolo de comandos y diríjase a la carpet a en la que guar dó el archivo HelloWorld.es. Una vez allí, puede escribir el siguiente comando: ese
HelloWorld.es
El c omando ese invoca al compi lador C# de N E T Framework. Al ejecutar este c omando se genera un ejecutable llamado HelloWorId .eze. que puede ser ejecutado exactamente igual que cualquier aplicación de Windows. Si ejecuta mos este archivo, se escribirá texto en la ventana de la consola tal y como se muestra en la figura 2.1.
Figura 2.1. La ventana emergente de co m a n d o muestra la aplicación Helio W orld en acción.
jEnhorabuena! A cab a de escribir su pri mera aplicación de C#.
Las palabras clave y los identificadores La aplicación de C# del listado 2.1 contiene muchas palabras, separadas por espacios. En ella, se utilizan dos tipos de nombres: palabras clave e identificadores. Esta sección describe las diferencias entre estos dos tipos de nombres. Las pal abr as clave son p al abr as que tienen un significado especial en el len guaje C#. Estas pal abr as han sido reservadas en C# y nos referimos a ellas como
59
p a la b ra s r e s e r v a d a s . Las palabras class. static y void son las palabras reservadas del listado 2.1. Ca da pal ab ra clave posee en el lenguaje C# un signifi cado especial. La siguiente lista contiene todas las p al abr as clave definidas en C#. abs tract
en um
long
stackalloc
as
event
namespace
static
base
e xp li c it
new
string
bool
extern
null
s truc t
br ea k
false
ob j e c t
switch
b yte
fi nally
o p er a t or
this
ca se
f ix e d
out
th row
ca 1 0 h
f 10at
0 ve r ride
true
char
for
pa rams
try
0h e 0 ked
fore a 0 h
p r i vat e
t ypeo f
c lass
a01 0
p r 0 1 e c:t e d
u 1111
00 n s t
1f
publi c
ulong
continue
1m p l i ci t
r ea do n ly
unchecked
d ecimal
in
ref
un sa f e
de fault
int
return
u sh 0 rt
delegate
i nt erf ace
sby t e
us ing
do
in ter nal
s ea led
vi rt ual
doub 1 e
is
short
vo i d
else
lock
sizeof
whi 1 e
Los identifícadores son los nombres que se usan en las aplicaciones. C# 110 reserv a nombres de ídcntificadores. Los identificadores son palabras que designan objetos en el codigo CU. Su clase necesita un nombre y se ha usado el nombre Hel loWor ld para su clase. Esto convierte al nombre HeUoWorld en un identificado!'. Su método también necesita un nombre y se ha usado el nombre M a m para su función. Esto convierte al nombre M a m en un identificador. El compilador de C# no permite normalmente usar ninguna de las palabras clave reservadas como n o m bres de identifícador. Obtendrá un error si. por ejemplo, intenta aplicar el nombre static a una clase. Sin embargo, si realmente necesita usar el nombre de una palabra clave como identifícador. puede poner delante del identificador el símbolo a . Esto invalida el error del compilador y permite usar una palabra clav e como identifícador. El listado 2.2 muestra como hacerlo. Es una modificación del código del listado 2 1 y define la palabra virtual como el nombre de la clase.
60
Listad o 2.2. C óm o usar la palabra clave virtual como identificador de clase class @virtual
{ static
void M a m ()
{ System.Consolé.WriteLine("Helio
W o r l d ! ");
Sin el precedente símbolo a,, obtendría un error del compilador, como el que se muestra en la figura 2.2.
Figura 2.2. Si olvida el sím bolo @ el com pila dor generará errores.
Uso de espacios en blanco El texto de las aplicaciones C# puede incluir espacios, tabulaciones y c a ra c te res de retorno. Estos caracteres son llamados caracteres de espacio en blanco. Los caracteres de espacio en blanco, que pueden ser colocados en cualquier lugar excepto en medio de una p al ab ra clave o un identificador. ayudan a mej orar la legibilidad del código. El compi lador de C # p a s a por alto la colocación de espacios en blanco cuando compila un progr ama. Esto significa que puede colocar cualquier ca rácter de espacio en blanco en cualquier lugar donde el compi lador acepte un carácter de espacio en blanco. El compi lador p a sa p or alto el uso de caracteres de retorno, t abulaciones y espacios. Puede u sar cualquier combinación de espacios en blanco que desee en su código. Los listados de este libro mues tran estilos personales de colocación de e s p a cios en blanco: los retornos están colocados antes y después de las llaves de ape rt ura y cierre, y el código está s angrado a partir de las llaves. Sin embargo, no es obligatoria esta disposición en las aplicaciones C#. El listado 2.3 muest ra una
61
disposición alternativa del codigo usando caracteres de espacio en blanco dife rentes. No tema experi ment ar con el estilo que más le guste. Listado 2.3. Una disposición de espacios en blanco alternativa C 1a s s H e ! 1oWo r 1d
I stat ic voi c¡ M a in ()
I íJy.stem. Consol e .WriteLi ne í"Helio
W o r l d !" ) ;
} } Si compila y ejecuta el listado 2.3. verá que se c omport a exactamente igual que el codigo del listado 2.1: produce la cadena "Helio W o r ld 1" . La nueva d is po sición de espaci os en bl anco no tiene ningún efecto en el comport ami ent o del codigo que se ejecuta en el tiempo de ejecución.
Cómo iniciar programas con la función Main() La aplicación que se muestra en el listado 2.1 define una clase con una función llamada Main(). La función M a i n ( ) es una parte importante de las aplicaciones CU. va que es donde comienza a ejecutarse nuestro programa. Todas las ap li ca ciones escritas en CU deben tener una clase con una función llamada M a i n ( ) . La función Ma m ( ) es conocida como el punto de entrada de sus aplicaciones v la ejecución de sus aplicaciones CU empieza con el codigo en Mal n ( ) . Si el codigo contiene mas de una clase, solo una de ellas puede tener una función llamada Ma i n í ) . Si olvida definir una función Ma i n ( ) . recibirá varios m e n s a jes de error por parte del compilador, como se muest ra en la figura 2.3.
C:\>csc HelloWoi-ld.cs Compilador de Microsoft
Uisual C# .NET versión 7.00.9466 para Microsoft .NET Framework versión 1.0.3705 M icrosoft Microsoft Corporation 2001. Reservados todos los derechos. HelloUorId.es<3„5> : error CS1520: Una clase, una estructura o un método de interfaz debe tener un tipo de ualor devuelto
Figura 2.3. La ausencia de una función Main () produce errores de compilación
62
La función Main ( ) definida en el listado 2.1 no devuelve nada (de ahi la palabra clave void) y no toma argument os (de ahí los paréntesis vacíos). El compi lador C#. de hecho, acepta cualquiera de las c uatro posibles construcciones de la función Main ( ) :
•
•
pubiic
static
void
M a i n ()
public
static
void
M a i n (s t r i n g [] Arguments)
public
static
int M a i n ()
public
static
int M a m (string
[] Arguments)
La primera variante, public static void Main ( ) . es la forma usada en el listado 2.1. La segunda, public static void Main ( string [ ] Arguments). no devuelve un valor al que la llama. Sin embargo, toma una matriz de cadenas. Ca d a cadena de la matriz se corresponde a un ar gument o de la línea de comando suministrado al ejecutar el programa. Por ejemplo, suponga que modifica el codigo del listado 2.1 par a que el método Main ( ) acepte una matriz de cadenas como ar gumento. Además, s uponga que ejecuta ese código y sumi nist ra algunos ar gument os de línea de comandos: Listing2-l.exe
Paraml
Param2
Param3
En este caso, la matriz de cadenas que se pas a a la función Main ( ) tiene los siguientes contenidos: Arguments [0] : Paraml Arguments [1 ] : Param2 Arguments [2] : Param3
La tercera variante, public static int Main (). devuelve un valor entero. Que el valor que se devuelve sea entero se especifica mediante la pal ab ra clave int de la declaración. Los valores enteros dev ueltos por Main ( ) se usan como códigos de fin de programa. Por ejemplo, s uponga que desea diseñar sus aplicaciones par a que devuelvan un valor ( supongamos 0) si la operación resulta satisfactoria y otro valor ( supongamos 1) si la operación no se cumple. Si ejecuta la aplicación N E T desde un entorno que puede leer este código de terminación de programa, tiene suficiente información par a determinar si el pr og r am a se ejecuto satisfactoriamente. La última variante de la función Main ( ) ,public static int Main (string [] Arguments ). especifica una función que p r o po r ciona ar gument os de línea de comando en una matriz de cadenas y permite a la función devolver un código de terminación de programa. Debe tener presente algunas cosas cuando trabaje con la función Main ( ) : •
Las formas de devolución void de la función Main ( ) siempre tienen un código de terminación de p r o gr a ma de 0.
63
La palabra clave s t a t i c es necesaria en todas las variantes de la f un ción M a i n ( ) . Cuand o se ejecuta una aplicación de C#. el usuario siempre propor ci ona los argument os de la línea de comando. Sin embargo, si la aplicación de C# esta escrita con una de las variantes de la función M a i n ( ) que no toma argumentos, la aplicación sera incapaz de leerlos. Está permitido que el usuario especifique argument os en una aplicación de C# que no fue escrita para admitirlos (aunque no sera muy útil).
Cómo comentar el código C omen tar el codigo le permite añadir notas a sus archiv os fuente de C#. Estas notas pueden ayudarle a do cu ment ar el diseño y el funcionamiento de la a p l i c a ción. Puede colocar comentarios en cualquier parte del código fuente de C# donde sea posible usar espacios en blanco.
Cómo usar comentarios de una línea Los comentarios de una línea empiezan con dos bar ras inclinadas y afecta al resto de la línea: { // esto es una llave de apertura System.Console.WriteLine ("C#"j ; // cali } // esto es una llave de crerre
W r i t e L m e ()
Usar comentarios regulares Los comentarios regulares empiezan con una b ar r a inclinada seguida de un asterisco y su efecto per manece hasta que encuentra un asterisco seguido por una barra inclinada. Los comentarios regulares pueden extenderse por varias líneas: /* Esto es un comentario regular de C# . Contiene varias lineas de texto, Separadas por caracteres N e w L i n e .
V El compi lador de C# no permite incrustar un comentario regular en otro: /* comentario
externo
/* comentario
interno
*/ mas
*/
64
texto de
comentario
externo
No puede i ncrust ar un coment ari o regular en otro porque el compi lador e n cuentra los primeros caracteres */ y da por hecho que ha al ca nzado el final del coment ari o de varias líneas. A continuación, supone que el siguiente texto segui do por los caracteres es código fuente de C# c intenta interpretarlo como tal. Sin embargo, puede i ncrust ar un coment ari o de una sola línea en un c o me nt a rio regular: /* comentario externo // comentario interno mas texto de comentario externo
*/
Cómo generar documentación XML a partir de comentarios Una caracterí st ica interesante del compi lador de C# es que puede leer c om en tarios en un formato especial y generar document aci ón X M L a partir de los c o mentarios. Puede entonces colocar este X M L en la We b par a facilitar un nivel extra de documentación a los p rogr amadore s que necesiten comprender la es t ruc tura de sus aplicaciones. Par a usar esta función, debe hacer dos cosas: •
Usar tres barras inclinadas par a los comentarios. El compi lador de C# no genera ni nguna d ocument aci ón X M L p a r a ningún document o que no e m piece con tres barras. T a m po c o genera documentación X M L p a r a c om en tarios regulares de varias líneas.
•
Use la opción / d o c del compi lador de C# par a especificar el nombre del archivo que debería contener la d ocument aci ón X M L generada.
El listado 2.4 muest ra la aplicación H elio W o r ld ! con coment ari os de d o c u mentación XML. Listado 2.4. La aplicación Helio W orld! con comentarios XML /// La clase HelloWorld es la única clase de la /// clase "HelloWorld". La clase implementa la función /// Main () de la aplicación. La clase no contiene otras /// f u n c io ne s. class
HelloWorld
{ /// /// /// ///
Ésta es la función Main () para la clase del listado 2.4. No devuelve un valor y no toma ningún argumento. Escribe el texto "Helio World!" en la consola y luego sale.
65
static void M a m ()
{ System. Consolé. W r i t e L m e ("Helio
World! ") ;
} } Puede compi lar esta aplicación con la opción / d o c par a generar d o cu ment a ción X M L p ar a el código fuente: ese /d o c :He 11o Wor Id .xml HelloWorld.es
El compi lador produce un H e l l o W o r l d . e x e como era de esperar y t a m bién un archivo llamado HelloWorld.xml. Este archi vo contiene un document o X M L con sus comentarios de document aci ón X M L i ncrustados en él. El listado 2.5 mues tra el document o X M L que se genera cuando se compila con la opción / d o c el código del listado 2.4. Listado 2.5. Docum ento X ML generado para el código del listado 2.4 < a s s e mb 1 y > < ñ a m e > H e 11oWo r 1d < /ñame > < / a s s e mb 1 y > < membe r s > La clase HelloWorld es la única clase de la clase "HelloWorld". La clase implementa la función Main () de la aplicación. La clase no contiene otras funciones. ''membe r name= "M :He 11 oWor Id .Ma m "> Esta es la función Main () para la clase del listado 2.4. No devuelve un valor y no toma ningún argumento. Escribe el texto " HelloWorld!" en la consola y luego sale. < /me mb e r > < /doc >
Ahora puede escribir una hoja de estilo par a este document o en X ML y m o s trarlo en una pági na Web. propor ci onando a los demás usuarios documentación actual izada de su código. La principal porción del document o X M L está en el elemento < m e m b e r s > . Este elemento contiene una etiqueta < m e m b e r > p ar a cada objeto document ado en el codigo fuente. La etiqueta < m e m b e r > contiene un atributo, ñ a m e , que designa al mi embro documentado. El valor del at ri but o ñ a m e empieza con un prefijo de una letra que describe el tipo de i nformación en cuestión. La tabla 2.1 describe los posibles valores del at ri but o del nombre y su significado.
66
Tabla 2.1. Prefijos de atributo " ñ a m e -
P r e fijo
S ig n ific a d o
E
El elemento proporciona documentación de un evento.
F
El elemento proporciona documentación de un campo.
M
El elemento proporciona documentación de un método.
N
El elemento proporciona documentación de un nombre de espacio.
P
El elemento proporciona documentación de una propiedad.
T
El elemento proporciona documentación de un tipo defini do por el usuario. Éste puede ser una clase, una interfaz, una estructura, una enumeración o un delegado.
!
El compilador C# encontró un error y no pudo determinar el prefijo correcto de este miembro. Tras el prefijo se colocan dos puntos y el nombre del miembro. El atributo
name= indica el nombre de la clase p ar a los miembros de tipo. Para los m ie m bros de método, el atributo name = indica el n ombre de la clase que contiene el método, seguida p or un punt o y a continuación el nombre del método. Sus comentarios de do cument aci ón X M L pueden incluir cualqui er elemento X M L válido par a ayudarle en su tarea de documentación. La document aci ón de N E T F ramework recomienda un conjunto de elementos X ML que puede us ar en su documentación. El resto de esta sección exami na cada uno de estos elementos. Recuerde que debe emplear X M L válido en sus comentarios, lo que significa que cada elemento debe contener su elemento final cor respondiente en al guna par te de sus c o me nt a rios.
NOTA: El término etiqueta se refiere a cualquier elemento descriptivo con tenido en el XML. Las etiquetas siempre están entre los símbolos < y >.
Puede usar la etiqueta p a r a indicar que una pequeña parte del comentario debe ser t rat ada como código. Las hojas de estilo pueden u sar este elemento par a mos trar la porción de código del comentario con una fuente de t amaño fijo, como Courier: /// Ésta es la función Main () para la /// clase HelloWorld.
67
Puede us ar la etiqueta par a indicar que varias líneas de texto de sus comentarios deben ser t rat adas como código: /// /// /// /// /// /// /// ///
Llamar a esta aplicación con tres argumentos hara gue la matriz de cadenas suministrada a M a i n () contenga tres elementos: ^ code > A r g u m e n t [0]: command line argument 1 A r g u m e n t [1]: command line argument 2 A r g u m e n t [2]: command line argument 3
Puede us ar la etiqueta < example > par a indicar un ejemplo de cómo usar las clases que desarrolle a otros progr amadores. Los ejemplos suelen incluir una muest ra de código y quizás quiera us ar las etiquetas y juntas: /// Aqui tiene un ejemplo de un /// a este codigo: /// x code> /// ponga aguí su codigo de ejemplo /// ^ /code > /// s / example>
cliente
llamando
Puede usar la etiqueta par a document ar cualquier excepción que pueda surgir en el código del miembro. La etiqueta debe contener un atributo l lamado cref cuyo valor especifica el tipo de excepción que se documenta. El valor del at ri but o cref debe estar entre comillas. El texto del elemento describe las condiciones en las que ap arece la excepción: /// /// Aparece si el valor introducido es menor de 0. /// < /exception>
El compi lador de C# asegura que el valor del atributo cref sea un tipo de datos v álido. Si no lo es. el compi lador genera un mensaje de aviso. A c o nt i nu a ción se indica cómo d ocumen ta r una función Main ( ) : ///
eref=" junk ">probando
hace que el compi l ad or de C# genere un mensaje de aviso como el siguiente: aviso CS 1574: El comentario en XML 'Main () ' tiene un atributo cref 'junk' que no se encuentra
En este caso, el compi lador de C# todaví a cscribe la etiqueta en el archivo XM L. pero pone un signo de exclamaci ón antes del atributo cref:
68
Cmember na me = " M :M y C l a s s .Main"> probando
Puede usar la etiqueta p ar a describir una lista de elementos en la documentación. Puede describir una lista no numerada, una lista n umera da o una tabla. La etiqueta usa un atributo llamado type par a describir el tipo de la lista. La tabla 2.2 enumera los posibles valores para el atributo type y describe su significado. Tabla 2.2. Valores para el atributo "type" de la etiqueta
V a lo r
S ig n ifica d o
b u lle t
La lista es una lista no numerada.
numbe r
La lista es una lista numerada.
t a b le
La lista es una tabla.
Los estilos bullet y number deberían también incluir una o más etiquetas - dentro de la etiqueta
. Ca da etiqueta - se corresponde a un elemento de la lista. C ad a etiqueta
- debería contener una etiqueta . cuyo texto define el texto de la lista de elementos: /// /// /// /// /// /// /// ///
< it em> Éste es el elemento 1. < / item> < item> Éste es el elemento 2. < / it em>
El tipo de lista table también debe incluir una etiqueta . La etiqueta contiene una o más etiquetas que descri ben los encabezami ent os de las tablas: /// /// /// ///
Elemento de la tabla
/ / / - /// Éste es el elemento 1. ///
///
69
Use la etiqueta para document ar un par ámet ro para una función La etiqueta usa un atributo, ñame, c u n o valor identifica al par ámet ro que se está documentando. El texto de la etiqueta proporciona una descripción del parámetro: /// Aparara name-"Flag"> /// El valor debe ser 0 para desactivado ///
o 1 para
activado.
El compi lador de C# asegura que el v alor del atributo ñame realmente es pe ci fique el nombre de un parámetro. Si no es así. el compilador emite dos avisos. Por ejemplo, un codigo fuente como el siguiente: ///
Esto es j u n k .
publ i c static
void Main(string
[] strArguments)
{ } p rod uc e av isos como los siguientes: aviso CS 1572: El comentario XML en 'Main(string[] ) ' tiene una etiqueta de parametro para 'junk', pero no hay ningún parametro con ese nombre aviso CS1573: El parametro 'strArguments' no tiene una etiqueta de parametro coincidente en el comentario XML (pero otros paramet ros si)
El pri mer aviso dice que se encontró una etiqueta con un atributo ñame cuyo valor no concuerda con ninguno de los p ar ámet ros de la función. El segundo aviso dice que a uno de los par ámet ros le falta una etiqueta . La etiqueta se coloca en el archivo X M L de documentación, i ncl u so si el atributo ñame es incorrecto: Esto es junk.
Puede usar la etiqueta par a hacer referencia a un parámet ro desde una descripción. La etiqueta puede no tener ningún texto; sin embargo, lleva un at ri but o llamado ñame. El valor del atributo ñame debe indicar el nombre del p ar ám et ro al que se hace referencia: /// ///
70
La matriz contiene los parámetros especificados es la linea de comandos.
Use la etiqueta p a r a d ocument ar los permisos disponibles en una función o variable dadas. El acceso al código y los datos de una clase puede significar el ac ces o a todo el código o puede ser restringido a cierto subconjunto de código. Puede u s ar la etiqueta p a r a d oc um en t ar la disponibilidad del código y sus datos. La etiqueta hace uso de un atributo, cref. El valor del elemento cref debe designar la función o la variable cuyos permisos se están documentando: /// /// Todo el mundo puede acceder /// < /permission>
a M a i n ().
Use la etiqueta p ara añadir información. El elemento es estupendo p ar a mo st rar una vista general de un método o una variable y su uso. La etiqueta no tiene atributos y su texto contiene las observaciones: /// /// /// /// ///
La función M a i n () es el punto de entrada a la aplicación. El CLR llamará a M a i n () para iniciar la aplicación una vez que ésta se haya abierto.
Use la etiqueta p ar a describir un valor devuelto por una f un ción. La etiqueta no tiene atributos y su texto contiene la i nforma ción del valor devuelto: /// /// /// /// ///
La función M a i n () devolverá 0 si la aplicación procesó los datos correctamente y devolverá 1 en caso contrario. < / returns>
Use la etiqueta par a añadir una referencia a una función o variable que se encuentre en ot ra parte del archivo. El elemento usa un atributo l lama do cref cuyo valor especifica el nombre del método o variable al que se hace referencia. La etiqueta no debe contener texto: ///
cref="C 1 a s s 1.M a i n " />
El compi lador de C # a segura que el valor del at ri but o cref realmente e speci fique el nombre de un método o variable. Si no es así. el c ompi lador emite un aviso. Por tanto, un código fuente como el siguiente:
71
/// public
static
void Main(string
[]
strArguments)
{ } produce un aviso como el siguiente: aviso CS1574: El comentario XML en 'Classl .Main (string [¡ ) ' tiene un atributo cref 'junk' que no se encuentra
La etiqueta < s e e > está situada en el archivo X M L de documentación, i nclu so si el atributo cref es incorrecto: ^member name= "M :Cíassi.Main(System. String [] ) ">
C o m o . puede us ar la etiqueta par a añadir una referencia a una función o variable que este' en otra parte del archivo. Puede que necesite g enerar d ocument aci ón que cont enga una sección de referencias ademas de una sección de referencias See Also y el compi lador de C# le permite hacer esa distinción al admi tir las etiquetas y . La etiqueta usa un atributo llamado cref c u n o valor especifica el nombre del método o variable al que se hace referencia. La etiqueta no debe contener texto: ///
'.see al.so c r ef = "Cías s 1 .Main"
/>
El compi lador de C# as egura que el valor del atributo cref realmente es pe ci fique el nombre de un método o variable Si no es así. el compi lador emite un av iso. Por tanto, un código fuente como el siguiente: /// public
static
void Main(string
[] strArguments)
( ) produce un aviso como el siguiente: aviso CS1574: El comentario XML en 'Classl.Main(string [] ) ' tiene un atributo cref 'junk' que no se encuentra
La etiqueta está situada en el archivo X M L de documentación, incluso si el atributo cref es incorrecto: ■'membe r ñame = "M: Classl.Main (System.St ring [] ) "> •'seealso c r e f = " ! :j un k " / > <■/membe r >
72
Use la etiqueta < s u m m a r y > p ar a pr opor ci onar una descripción resumida de un fragment o de código. Esta etiqueta no admite ningún atributo. Su texto debe describir la información resumida: /// /// La función M a i n () es el punto de entrada de /// esta aplicación. ///
La etiqueta es como la etiqueta . Generalmente, debe u sar la etiqueta p a r a pr opo rci onar información sobre un m é todo o variable y la etiqueta < remarks> p a r a propor ci onar i nformación sobre el tipo del elemento.
Use la etiqueta p ar a describir una propiedad de la clase. La etique ta no tiene atributos. Su texto debe d ocument ar la propiedad: /// /// /// ///
La propiedad MyValue devuelve leidos de la base de datos.
public
el número de
registros
int MyValue
{ // ... el código de propiedad viene
aqui
...
}
Resumen Este capítulo mu es t ra cómo crear aplicaciones C# con un simple editor de texto, como el Bloc de notas. Tambi én examina varias alternativas a Visual Studio p a r a escribir código. Ha construido su pri mera aplicación de CU. Las aplicaciones CU. indepen dientemente de su t amaño, deben contener una clase con un a función l lamada Main ( ) . La función Main ( ) es el punto de par ti da de su aplicación de CU. Tamb ién aprendió a añadir comentarios al código fuente en CU. Puede añadir comentarios al código p ar a ay ud a r a otros pro gr amado re s a comprender cómo está es tructur ado el código fuente. Tambi én puede dar formato a las comentarios de tal modo que el comp il ado r pu ed a convertir los coment ari os en un document o XM L; y añadiendo pal ab ras clave especiales, puede hacer el document o X M L muy rico e informativo.
73
■el Trabajar con variables
El código de C# suele t rabaj ar con valores que no se conocen cuando se escribe el código. Puede que necesite t r a b aj a r con un valor leído de una base de datos en tiempo de ejecución o quizás necesite al macenar el resultado de un cálculo. C u a n do necesite al ma c en ar un valor en el tiempo de ejecución, use una variable. Las variables son los sustitutos de los valores con los que t rabaj a en su código.
Cómo dar nombre a sus variables Cada variable que use en su código C# debe tener un nombre. El compilador de C# interpreta los nombres de las variables como identificadores y. por tanto, deben seguir las convenciones de designación de los identificadores: El primer ca rácter de un identificador debe empezar con una letra m a y ú s cula o mi núscul a o con un carácter subrayado. Los caracteres que siguen al primero pueden ser cualquiera de los siguientes: Una letra m ay ús cul a o mi núscula •
Un número Un s ubravado
75
NOTA: C# admite código fuente escrito con caracteres Unicode. Si está escribiendo su código fuente usando un conjunto de caracteres Unicode, puede usar cualquier carácter de entre las clases de caracteres Unicode Lu, LI, Lt, Lm, Lo, Ni, Mn, Me, Nd, Pc y Cf en su identificados Consulte la sección 4.5 de las especificaciones de Unicode si desea obtener más infor mación sobre las clases de caracteres Unicode. Tamb ién puede usar una pal ab ra clave de C# como nombre de variable, pero sólo si va precedida del caracter a . No obstante, no se trata de una práctica recomendable, ya que puede hacer que su código resulte difícil de leer, pero es factible y el compi lador de CU lo permite.
Asignación de un tipo a una variable A las v ariables de C# se les asigna un tipo, que es una descripción del tipo de datos que va a contener la variable. Quizás quiera t rabaj ar con números enteros, números de coma flotante, caracteres, cadenas o incluso un tipo que puede definir en su codigo. C uand o define una v ariable en su código CU. debe a si gnar un tipo a la v ariable. La tabla 3.1 describe algunos de los tipos básicos de variables de CU. Tabla 3.1. Tipos de datos com unes de C#
D e sc r ip c ió n
76
sbyte
Las variables con tipo sbyte pueden contener números enteros de 8 bits con signo. La s’ de sbyte significa ‘con signo’, lo que quiere decir que el valor de la variable puede ser positivo o negativo. El menor valor posible para una variable sbyte es -128 y el mayor es 127.
byte
Las variables con tipo byte pueden contener números enteros de 8 bits sin signo. A diferencia de las variables sbyte, las variables by te no tienen signo y sólo pue den contener números positivos. El valor más pequeño posible para una variable by te es 0; el valor más alto es 255.
short
Las variables con tipo short puede contener números enteros de 16 bits con signo. El menor valor posible para una variable sh o r t es -32.768; el valor más alto es 32.767.
us hort
Las variables con tipo usho rt pueden contener núme ros enteros de 16 bits sin signo. La ‘u’ de u short signifi-
T ip o
D e sc r ip c ió n
ca sin signo. El menor valor posible para una variable u s ho r t es 0; el valor más alto es 65.535. int
Las variables con tipo int pueden contener números en teros de 32 bits con signo. El menor valor posible para una variable int es -2.147.483.648; el valor más alto es 2.147.483.647.
uint
Las variables con tipo uint pueden contener números enteros de 32 bits sin signo. La u’ en uint significa sin signo. El menor valor posible para una variable uint variable es 0; el valor más alto posible es 4.294.967.295.
long
Las variables con tipo long pueden contener números enteros de 64 bits con signo. El menor valor posible para una variable long es 9.223.372.036.854.775.808; el va lor más alto es 9.223.372.036.854.775.807.
ul ong
Las variables con tipo u l ong pueden contener números enteros de 64 bits sin signo. La ‘u’ en u l on g significa sin signo. El menor valor posible para una variable ulong es 0; el valor más alto es 18.446.744.073.709.551.615.
char
Las variables con tipo char pueden contener caracteres Unicode de 16 bits. El menor valor posible para una va riable char es el carácter Unicode cuyo valor es 0; el valor más alto posible es el carácter Unicode cuyo valor es 65.535.
float
Las variables con tipo float pueden contener un valor de coma flotante de 32 bits con signo. El menor valor posible para una variable float es aproximadamente 1,5 por 10 elevado a 45; el valor más alto es aproxima damente 3,4 por 10 elevado a 38.
doub1 e
Las variables con tipo d ou b l e pueden contener un valor de coma flotante de 64 bits con signo. El menor valor posible para una variable d ou b l e es aproximadamente 5 por 10 elevado a 324; el valor más alto es aproximada mente 1,7 por 10 elevado a 308.
de c i m al
Las variables con tipo d e ci ma l pueden contener un va lor de coma flotante de 128 bits con signo. Las variables de tipo d ec i m a l son buenas para cálculos financieros. El menor valor posible para una variable decimal es aproximadamente 1 por 10 elevado a 28; el valor más alto es aproximadamente 7,9 por 10 elevado a 28.
bool
Las variables con tipo bool pueden tener uno de los dos posibles valores: true o false. El uso del tipo bool es
77
T ip o
D e sc r ip c ió n
una de las partes en las que C# se aparta de su legado C y C++. En ellos, el valor entero 0 era sinónimo de false y cualquier valor que no fuese cero era sinónimo de t rué. Sin embargo, en C# los tipos no son sinónimos. No pue de convertir una variable entera en su valor equivalente bool. Si quiere trabajar con una variable que necesita tener una condición verdadera o falsa, use una variable bool y no una variable Int.
Cómo aplicar tamaño a sus variables Se estará preguntando por qué C# admite todos estos tipos de variables diferentes. Los valores más pequeños pueden colocarse en variables de mayores tipos; por lo tanto ¿por qué usar los tipos más pequeños? Si una variable s h o r t puede contener valores desde -32.768 hasta 32.767, y una grande puede contener valores desde -9.223.372.036.854.775.808 hasta 9.223.372.036.854.775.807, entonces es evidente que todos los posibles valores s h o r t pueden ser almacenados en una variable lo n g . Entonces, ¿para qué sirven los tipos s h o r t ? ¿Por qué no usar un variable lo n g para todo? Una respuesta es el consumo de memoria. Una variable lo n g puede conte ner valores más grandes, pero también necesita más memoria. Una variable s h o r t usa 16 bits (dos bytes), mientras que una grande usa 32 bits (cuatro bytes de memoria). Si va a trabajar con valores que no van más allá del límite de una variable s h o r t , use la variable s h o r t . Es una buena cos tumbre usar toda la memoria que necesite, pero no usar más de la necesaria.
Cómo declarar sus variables Antes de poder usar su v ariable, debe declararla en su código. Al declarar una variable informa al compi lador de C# del tipo y nombre de su variable. Una variable se declara escribiendo su tipo, seguido de algún espacio en blanco v. a continuación, del nombre de la v ariable. Termine la declaración con un punto v coma. Al gunos ejemplos de declaración de variables son:
byte MyByteVaríable ; in t V a 1 u e 12 3 ; ulong
78
AVeryLa rg eN um be r;
NOTA: Los espacios en blanco se definen como cualquier número de espacios necesario para mejorar la legibilidad del código. Debe declarar sus variables dentro de un a clase o de una función. El siguiente código es válido: class
MyClass
{ int
MylntegerVariable ;
static void M a i n ()
{ float
AnotherVariable;
System.Co ns ol e . W r i t e L i n e ( " H e l l o ! ");
} }
NOTA: Puede declarar una variable donde desee, pero tenga en cuenta que si la declara en una función, como se muestra en la variable AnotherVariable del ejemplo anterior, sólo el código incluido en esa función podrá trabajar con la variable. Si la declara dentro de la clase, como en la variable Mylnt egerVariable (también en el ejemplo ante rior), todo el código de esa clase podrá trabajar con esa variable. Si toma el código del ejemplo y le añade otra función a la clase, el código de esa nueva función podrá trabajar con la variable MylntegerVariable pero no podrá trabajar con la variable AnotherVariable. Si esta nueva fun ción intenta acceder a la variable AnotherVariable declarada en la función Main ( ) , obtendrá el siguiente mensaje de error del Compilador de C#: error CS0103: El nombre 'AnotherVariable' o espacio de nombre 'MyClass'’
no existe en la clase
Uso de valores por defecto en las variables En otros lenguajes de programaci ón, es posible t raba jar con una variable sin antes asignarle un valor. Este vacío es una fuente de errores, como demuest ra el siguiente código: class MyClass { static void M a i n ()
79
{ int
MyVariable;
// ¿Cual
es el valor de
"MyVariable"
aqui?
} } ¿Cuál es el valor de MyVariable cuando se ejecuta Main ( ) ? Su valor es desconocido porque el código no asi gna ningún valor a la variable. Los p rogr amadore s de C# eran conscientes de los errores que podian aparecer como resultado de usar variables a las que no se las ha asignado explícitamente un valor. El c ompi lador de C# bu sca condiciones como ésta y genera un mensaje de error. Si la función Main ( ) del código anterior hace referencia a la variable MyVariable sin que se le hay a asignado un valor, el compi lador de C# muest ra el siguiente mensaje de error: error CS0165:
Uso de
la variable
local no asignada
'MyVariable'
C# distingue entre variables asigna da s y no asignadas. Las variables as i gn a das han recibido un valor en algún punt o del código y las variables no asi gnadas no han recibido ningún valor en el código. En C# no se permite t ra ba j ar con variables no asi gnadas porque sus valores son desconocidos y empl ear estas v a riables puede generar errores en su código. En al gunos casos. C # otorga valores por defecto a variables. Uno de estos casos es una variable declarada en el nivel de clase. Las variables de clase reciben valores por defecto si no se les asi gna un val or en el código. Modifique el código anterior ca mbi ando la variable MyVariable de una variable declarada en el nivel de función a una variable d ec la rada en el nivel de clase: class MyClass
{ static
int MyVariable;
static
void Main()
{ // MyVariable recibe un valor por // defecto y puede ser usada aguí
} } Esta acción mueve la declaración de la variable dentro de la variable de clase y la variable ahor a es accesible p ar a todo el código incluido en la clase, no sólo en la función Main (). CU asigna valores por defecto a variables de nivel de clase y el compi lador de CU le permite t r ab a ja r con la variable MyVariable sin as i g narle un valor inicial. La tabla 3.2. enumera los valores que se asignan por defecto a las variables de clase.
80
Tabla 3.2. Valores por defecto de las variables
T ip o d e v a r ia b le
V a lo r p o r d e fe c to
sbyt e
0
b y te
0
s h o rt
0
u s h o rt
0
in t
0
u in t
0
lo n g
0
u lo n g
0
ch a r
carácter Unicode con valor 0
flo a t
0.0
doub 1 e
0.0
de cim a 1
0.0
bool
false
Asignación de valores a variables En algún punto de su código, quer rá darle a sus variables un valor. Asi gnar un valor a una variable es sencillo: se escribe el nombre de la variable, el símbolo i g u a l el valor y se termina la instrucción con un punt o y coma: MyVariable
= 123;
Puede asi gnar un valor a la variable cuando declara la variable: int MyVariable
= 123;
Apre nderá otros medios de as ignar valores a las variables en las secciones posteriores.
Uso de matrices de variables Las matrices simplemente son bytes de memori a contiguos que al macenan datos a los que se accede us ando un índice que está en la matriz. En esta sección se analizan las matrices unidimensionales, mul tidimensionales y escalonadas.
81
Declaración de matrices unidimensionales Sup on ga que está escribiendo una aplicación de C# par a que los profesores introduzcan las calificaciones de los exámenes p a r a cada uno de los estudiantes de su clase y quiere declarar variables que almacenen la punt uaci ón del examen de cada alumno. Como la calificación del examen esta entre 0 y 100 puede u sar tipos byte. Si su p rog rama admite 25 estudiantes en una clase, su primer pensamiento puede ser declarar 25 variables por separado: Byte
T estScoreForStudentl;
Byte
TestScoreForStudent2;
Byte TestScoreForStudentB; / / ... ma s ... b yt e
T e s t S c o r e F o r S t.u d e n 1 2 5 ;
Esto requerirá mucho tiempo y su código sera difícil de leer y mant ener con todas esas variables. Lo que necesita es un modo de decir. "Quiero tener una colección de 25 variables". Es decir, lo que queremos es una matriz. Una matriz es una coleccion de variables, cada una de las cuales tiene el mismo tipo de variable. Las matrices tienen un tamaño, que especifica cuantos elementos pueden contener. La declaración de una matriz es algo asi: byte
[]
TestScoresForStudents;
La declaración byte especifica que todos los elementos de la matriz son valores de tipo byte. Mediante los corchetes se indica al compi lador de C U que quiere c rear una variable de matriz, en vez de una sola variable, y el identificador TestScoresForStudents es el nombre de la matriz. El elemento que falta en esta declaración es el t amaño de la matriz. Cuant os elementos puede contener esta m a t r i z ‘> El t amaño de la matriz se especifica m e diante el oper ador de CU new. Este operador indica al compilador de CU que quiere reservar suficiente memoria par a una nueva variable; en este caso, una matriz de 25 variables byte: byte
[]
TestScoresForStudents;
TestScoresForStudents
=
new
byte[25];
La pal abra clave byte indica al compi lador que quiere crear una nueva m a triz de variables byte. y [25] indica que quiere reservar suficiente espacio para 25 variables byte. Cad a variable de la matriz se denomina elem ento de la matriz, y la matriz que ac a ba de crear contiene 25 elementos. Debe acordar se de especificar el tipo de matriz cuando use la pal abr a clave new. aunque ya haya especificado el tipo de la matriz cuando la declaró. Si olv ida hacerlo, obt endrá un mensaje de error del compilador. El código:
82
byte [] TestScoresForStudents ; TestScoresForStudents
= new
[25];
hace que el compi lador de C# emita el error: error CS1031: Se esperaba un tipo
Este error aparece porque el código no tiene un tipo de variable entre la nueva pal abr a clave y el t amaño de la matriz. Tambi én debe recordar u s ar el mismo tipo que usó cuando declaró la matriz. Si usa un tipo diferente, obtendrá otro mensaje de error, como demuest ra el si guiente código: byte
[]
TestScoresForStudents;
TestScoresForStudents
= new
long[25];
Este código hace que el compi lador de C# emita el error: error CS0029: No se puede 'l o n g [] ' a 'byte[] '
convertir
implícitamente
el
tipo
El error se produce porque el tipo de la instrucción (byte) no concuerda con el tipo usado en la nueva instrucción (long). Las matrices como ésta se llaman matrices u n id im e n sio n a le s . Las matrices unidimensionales tienen un factor que determina su tamaño. En este caso, el único factor que determina el t amaño de la matriz es el numero de estudiantes de la clase. El valor inicial de los elementos de la matriz queda det erminado por los v a l o res por defecto del tipo de matriz. C ad a elemento de la matriz se imcializa con un valor por defecto de acuerdo con la t abla 3.2. C omo esta matriz contiene elemen tos de tipo byte. ca da elemento de la matriz tiene un valor por defecto de 0.
Cómo trabajar con los valores de las matrices unidimensionales A cab a de crear una matriz con 25 elementos de tipo byte. C ad a elemento de la matriz tiene un número. El primer elemento de la matriz ocu pa el índice cero, y el último elemento de la mat ri z es uno menos que el número de elementos de la matriz (en este caso, el último elemento es el elemento 24). Las matrices de C# se llaman matrices de hase cero porque los números de sus elementos empiezan en el cero. T r a b aj a r con un elemento individual de la matriz es sencillo. Para obtener un valor de la matriz, acceda a él con el nombre de la variable y el numero de la v ariable entre corchetes, como se mues tra en el siguiente código. byte
FirstTestScore ;
FirstTestScore
=
TestScoresForStudents [0] ;
83
E s t e e o d i g o a c c e d e al p r i m e r e l e m e n t o de la m a t r i z T e s t s c o res ForStudents y asigna su valor a la primera variable FirstTestScore var i a b l e . Para poner un valor en la matriz, simplemente acceda al elemento usando la misma sintaxis, pero coloque el nombre de la matriz y el número del elemento a la izquierda del signo igual: TestScoresForStudents [5 j = 100;
Este codigo al mac en a el valor 1 0 0 en el de'cimo elemento de la mat ri z
TestScoresForStudents. C'# no permite acceder a un elemento que no se encuentre en la matriz. Como la matriz que ha definido contiene 25 elementos, los números de los elementos posibles van de 0 a 24. inclusive. Si usa un numero de elemento inferior a 0 o mayor que 24. obt endrá un mensaje de tiempo de ej ecu ción. como se mues tra en el siguiente código: TestScoresForStudents [1000]
-
12 3;
Este código se compila sin errores, pero al ejecutar la aplicación se produce un error porque no hay un elemento 1000 en su matriz de 25 elementos. Cua nd o se llega a esta instrucción, el Entorno de ejecución común (C'LR) detiene el p r o g r a ma e inicia un mensaje de excepción: Exoeption occur re d: System.IndexOutOfRangeException : An except ion of type S y s t e m .IndexOutOfRangeException was thrown.
IndezOutOf B.angeEzception indica que la aplicación intentó acceder a un elemento con un numero de elemento que no tiene sentido para la matriz.
Inicialización de valores de elementos de matriz S u pon gamo s que quiere crear una matriz de cinco números enteros v que el valor de cada elemento sea distinto del que nos ofrecen por defecto. Puede escribir instrucciones individuales para inicializar los valores de la matriz: mt
f]
MyAr r a y ;
MyArray -- new m t M y A r r a y [0] ^ Q; M y A r r a y [1] - 1 ; M y A r r a y [2] = 2; M y A r r a y [ 3] ^ 3; M y A r r a y [4] - 4;
[5] ;
Si al escribir el codigo ya conoce los valores con los que quiere inicializar la matriz, puede especificar los valores en una lista sep arad a por comas y encerrada entre llaves. La lista se coloca en la mi sma línea que la declaración de matriz. Puede poner todo el codigo anterior en una sola línea escribiendo lo siguiente: mt
84
[ ¡ M yA r ra y = ( 0 , 1, 2 , 3, 4} ;
Usando esta sintaxis, no especifica el nuevo operador o el t amaño de la matriz. El compilador de C# examina su lista de valores y calcula el tamaño de la matriz.
Declaración de matrices multidimensionales Puede pensar en una matriz unidimensional como en una línea. Se extiende en una dirección. Puede i maginarse una mat ri z multidimensional con dos di mens io nes como un trozo de papel de gráfica. Sus dimensiones no sólo se extienden hacia fuera, sino también hacia abajo. Esta sección estudia los tipos más comunes de matriz.
Uso de matrices rectangulares Co nt in ua mo s con el ejemplo de las calificaciones de los exámenes. La matriz unidimensional definida en la sección previa contenía un conjunto de cali ficaci o nes de 25 estudiantes. Ca da estudiante dispone de un elemento en la matriz para al macenar una calificación. Pero, ¿que ocurre si quiere al macenar varias califica ciones p ar a varios estudiantes? Ahora tiene una matriz con dos factores que a f ec tan a su tamaño: el número de estudiantes y el número de exámenes. S uponga que sus 25 estudiantes harán 10 exámenes a lo largo del curso. Eso significa que el profesor tiene que corregir 250 exámenes por curso. Puede declarar una matriz unidimensional p ar a que contenga las 250 calificaciones: byte
[]
TestScores ForStudents ;
TestScoresForStudents
= new b y t e [250];
Pero esto podría resultar confuso. ¿Cómo se usa esta m at r i z,} ¿Aparecen pri mero las puntuaciones de un solo estudiante o colocamos en pri mer lugar las calificaciones del primer es tud iant e9 Un modo mejor de declarar una matriz consiste en especificar cada dimensión por separado. Decl arar una matriz multidimensional es tan sencillo como colocar c omas entre los corchetes. Coloque una coma menos que el número de d imen si o nes necesarias par a su matriz multidimensional, como en la siguiente declaración: byte
[,]
TestScoresForStudents;
Esta declaración define una matriz multidimensional de dos dimensiones. Usando el o per ado r new p ar a crear una nueva matriz de este tipo es tan senci llo como especificar las dimensiones individualmente, sep arad as por comas, en los corchetes, como se m ues tra en el siguiente código: byte
[,]
TestScoresForStudents;
TestScoresForStudents
= new byte
[10,
25];
Este código indica al compi lador de C# que quiere crear una matriz con una dimensión de 10 y otra dimensión de 25. Puede i maginar una matriz de dos di
85
mensiones como una hoja de calculo de Microsoft Excel con 10 filas v 25 c ol u m nas. La tabla 3.3 recoge el aspecto que podria tener esta matriz si sus datos estuvieran en una tabla. Tabla 3.3. Representación en una tabla de una matriz de dos dimensiones
E xam en
E stu d ia n te 1
E stu d ia n te 2
E s t u d ia n t e 3 ...
E stu d ia n te 25
Examen 1
90
80
85
75
Examen 2
95
85
90
80
Examen 10
100
100
100
100
Para acceder a los elementos de una matriz de dos dimensiones, use las mismas reglas par a n umera r elementos que en las matrices unidimensionales. (Los n ú m e ros de elemento van de 0 a uno menos que la dimensión de la matriz.) Tambi én se usa la mi sma sintaxis de coma que uso con el oper ador new. Escribir código para al ma c en ar una calificación de 75 par a el primer examen del alumno 25 seria algo asi: Test Scor es Fo r Stud e n t s |0,
24]
- 75 ;
Leer la calificación del quinto examen del alumno 16 sería algo así: byt.e
Fit thScoreForStudent 1 ¿ ;
Fi f thScoreForStudenti ¿
=
Tes tScoresFo rS t u d e n t s [4,
15];
En otras palabras, cuando trabaje con una matriz de dos dimensiones v c ons i dere la matriz como una tabla, considere la primera dimension como el numero de fila de la tabla y el segundo numero como el numero de columna. Puede micializar los elementos de una matriz multidimensional cuando declare la variable de matriz. Para ello, coloque cada conjunto de v alores para una sola dimension en una lista delimitada por comas rodeada por llaves: mt
[, ] MyAr r ay - { {0 , 1,
2 } , {3 , 4 , 5 } } ;
Esta instrucción declara una matriz de dos dimensiones con dos filas v tres columnas. Los valores enteros 0. 1 y 2 están en la primera fila, v los valores 3. 4 y 5 están en la segunda fila. Las matrices de dos dimensiones con esta es tructur a se llaman matrices r ec tangulares. Estas matrices tienen la forma de una tabla: cada fila de la tabla tiene el mismo numero de columnas. C# permite definir matrices con mas de dos dimensiones. Para ello basta con utilizar mas comas en la declaración de la matriz.
86
Puede definir un a matriz de cuatr o dimensiones con tipo l o n g . por ejemplo, con la siguiente definición: long [,,,] ArrayWithFourDimensions;
Asegúrese de definir todas las dimensiones cuando use el o pe r ad or new: ArrayWithFourDimensions = new long [5, 10, 15, 2 0];
E1 acceso a los elementos de la mat ri z se realiza de la mi sma manera. No olvide especi fi car todos los elementos de la matriz: A r r a y Wi th Fo ur Di me ns io ns [0,
0,
0,
0]
= 32768436;
Definición de matrices escalonadas C # permite definir matrices escalonadas, en las que ca da fila puede tener un número diferente de columnas. Vol vamos al ejemplo de las calificaciones de los estudiantes par a explicarlo. S upo ng a también que el número máx imo de e x á m e nes es 10. pero al gunos estudiantes no tienen que hacer los últimos examenes si obtuvieron buena nota en los anteriores. Puede c rear una matriz rectangular para lo que necesita al macenar, pero puede a c a b a r con elementos que no se utilizan en su matriz rectangular. Si algunos estudiantes no hacen todos los exámenes, tendrá elementos en su mat ri z rect angul ar que no se usan. Estos elementos des a pr ov e chados equivalen a memori a desperdiciada, lo que pretendemos evitar. Una estrategia mejor consiste en definir una matriz en la que cada elemento de la matriz sea. en sí mismo, una matriz. La figura 3.1 ejemplifica este concepto. Mues tra al estudiante 1 con espacio p ar a tres calificaciones, al estudiante 2 con espacio p ar a cinco calificaciones, al estudiante 3 con espacio p ar a dos califica ciones y al estudiante 25 con espacio p ar a las diez calificaciones (los otros es tu diantes no aparecen en la figura).
C a lific a c ió n
C a lific a c ió n
1
2
C a lific a c ió n 3
C a lific a c ió n
C a lific a c ió n
C a lific a c ió n
C a lific a c ió n
C a lific a c ió n
1
2
3
4
5
C a lific a c ió n
C a lific a c ió n
1
2
E s t u d ia n t e
C a lific a c ió n
C a lific a c ió n
C a lific a c ió n
C a lific a c ió n
C a lific a c ió n
C a lific a c ió n
C a lific a c ió n
C a lific a c ió n
C a lific a c ió n
C a lific a c ió n
25
1
2
3
4
5
6
7
8
9
10
Figura 3.1. Las matrices escalonadas permiten definir una matriz que contiene otras matrices, cada una con un número diferente de elementos.
Estas matrices escal onadas tienen dos dimensiones, como las matrices rec tangulares, pero cada fila puede tener un número de elementos diferente (lo que da a las matrices su aspecto escalonado).
87
Las matrices escal onadas se definen utilizando dos grupos de corchetes i nme diatamente después del nombre del tipo de matriz. C uan do hace una llamada a new. se especifica un t amaño para la pri mera dimensión (la matriz student en nuestro ejemplo), pero no la segunda. Tras definir la pri mera matriz, haga una nueva llamada a new par a definir las otras matrices (las matrices score en nuestro ejemplo): byte
[] [] ArraysOfTestScores ;
ArraysOfTestScores = new byte [25][]; ArraysOfTestScores [0 ] = new b y t e [3]; Ar ra y s O f T e s t S c o r e s [1] = new b y t e [5]; ArraysOfTestScores [2] = new byte [2]; Ar ra y s O f T e s t S c o r e s [24] = new b y t e [10];
Una vez que ha construido la matriz escalonada, puede acceder a sus el emen tos de la mi sma forma que en una matriz rectangular.
Tipos de valor y de referencia Recuerde que debe usar la p al abr a clave new par a crear la matriz. Este requi sito es distinto de los tipos que hemos visto hast a ahora. Cu an do t rabaje con codigo que use variables int o long. por ejemplo, puede usar la variable sin usar new: int
IntegerVariab1e ;
IntegerVariable
=
12345;
¿Por qué son tan diferentes las m a t ri c e s9 ¿Por qué se necesita utilizar new al crear una m a t r i z 9 La respuesta está en la diferencia entre tipos de valor y tipos de referencia. Con un tipo de valor, la v ariable retiene el valor de la variable. Con un tipo de referencia, la v ariable retiene una referencia al valor al macenado en algún otro sitio de la memoria. Puede imaginar una referencia como una v ariable que apunt a hacia otra parte de memoria. La figura 3.2 muest ra la diferencia. int IntegerVariable
123 long LongVariable
456 double [] ArrayOfDoubles 0.0
0.0
0.0
0.0
0.0
Figura 3.2. Los tipos de valor contienen datos. Los tipos de referencia contienen referencias a datos situados en algún otro lugar de la memoria.
88
Ca d a uno de los tipos comentados has ta este punto es un tipo de valor. Las variables p ropor ci onan suficiente capaci dad de a lmacenami ent o p a ra los valores que pueden contener y no necesita new para crear un espacio par a sus var ia bles. Las matrices son tipos de valor y los objetos son tipos de referencia. Sus valores están en algún otro lugar de la memori a y no necesita usar la pal abr a clave new par a crear suficiente espacio par a sus datos. Aunque necesita us ar la pal ab ra clave new p ar a crear espacio de memoria p a r a un tipo de referencia, no necesita escribir ningún código que borre la m em o ria cuando haya ac ab ad o de usar la variable. El C L R contiene un mecanismo l lamado recolector de elem entos no utilizados que realiza la tarea de liberar la memori a que no se usa. El C L R ejecuta el recolector de elementos no utilizados mientras se ejecuta su aplicación de C#. El recolector de elementos no utilizados registra su p rog ram a buscando memori a no us ada por ni nguna de sus variables. Li berar la memori a que ya no se usa es tarea del recolector de elementos no utilizados.
Cómo convertir tipos de variable Puede t opar con una situación en la que tenga una variable de un tipo, pero necesite t raba jar con un fragmento de código que necesite otro tipo. Si. por ej em plo. está t rabaj ando con una variable de tipo int y necesita p as ar el valor a una función que requiere el uso de una variable de tipo long. entonces necesita realizar una conversión de la variable int en variable long. C# admite dos tipos de conversiones: conversiones implícitas y conversiones explícitas. Las siguientes secciones describen cada uno de estos tipos de co nv er siones.
Conversiones implícitas El compi lador de C# realiza a ut omát icame nte las conversiones implícitas. Exami ne el siguiente código: int In te g e r V a r i a b l e ; long LongVariable; IntegerVariable = 123; LongVariable = I n t e g e r V a n a b l e ;
En este código, a una variable de tipo entero se le asigna el valor 123 y a una variable long se le asi gna el valor de la variable de tipo entero. C uando se ejecute este código, el val or de LongVariable es 123. El compi lador de CU convierte el valor de la v ariable de tipo entero a un v alor long porque la conversión de un valor int a un valor long es una de las conversiones implícitas permitidas por C#. La tabl a 3.4 recoge las conversiones
89
implícitas permitidas por C#. La pri mera col umna enumera el tipo original de la variable y la fila superior enumer a los tipos de datos a los que puede convertirlo. Una X en la celdilla significa que puede convertir implícitamente el tipo de la izquierda al tipo en la parte superior. Tabla 3.4. C onversiones implícitas de tipo de valor
s byte
—
:
u in t
long
c h a r flo a t ulo ng
decim al dou ble
X
-
X
-
X
-
X
-
X
-
X
-
X
x
X
X
X
X
-
X
X
X
-
-
-
X
-
X
-
X
-
X
-
X
X
-
-
-
x
X
X
X
-
X
X
X
X
-
-
-
X
-
X
-
X
-
X
X
-
X
X
-
X
X
X
X
-
X
-
X
X
1
-
!
:
byte s h o rt u sh o rt in t
-
-
-
-
-
-
-
-
-
X
-
-
-
x
X
X
X
X
X
X
X
X
-
-
-
-
-
-
-
-
X
-
-
X
-
-
-
-
-
-
-
X
X
X
X
NOTA: No puede convertir ningún tipo a un tipo char (excepto mediante la variable char, lo que en realidad no es una conversión). Además, no puede hacer conversiones entre los tipos f loating-point y los tipos decimales.
Conversiones explícitas Si escribe código que intente conv ertir un valor que use tipos no admitidos por una conversión implícita, el compi lador de C# genera un error, como mues tra el siguiente código: char CharacterVariable; mt IntegerVanable; IntegerVariable = 5; CharacterVariable = IntegerVariable;
El compi lador de CU produce el siguiente error: error CS0029: No se puede 'in t ' a 'char'
90
convertir
implícitamente
el tipo
Este error se produce porque ni nguna conversión implícita admite la conve r sión de una variable i n t a una variable c h a r variable. Si realmente necesita hacer esta conversión, tiene que realizar una conversión explícita. Las conversiones explícitas se escriben en su código fuente y le dicen al compi lador "haz que se p ro d uz c a esta conversión, aunque no puede ser realizada implícitamente". Escribir una conversión explícita en el codigo CU requiere colo car el tipo al que está convirtiendo entre paréntesis. Los paréntesis se colocan j u st o antes de la variable que está usando c omo fuente de la conversión. A conti nuación se incluye el código anterior pero usando una conv ersión explícita: char int
CharacterVanable; IntegerVariable;
IntegerVariable = 9; CharacterVariable = (char)IntegerVariable;
Esta técnica es conocida como conversión de la variable de tipo entero a una variable de tipo carácter. Algunos tipos no pueden ser convertidos, ni siquiera mediante una operación de conversión explícita. La tabla 3.5 enumera las conver siones explícitas que admite CU. La p ri mera col umna e numer a el tipo original de la variable y la fila superior enumera los tipos de datos a los que puede convert ir lo. Una X en la celdilla significa que puede convertir explícitamente el tipo de la i zquierda al tipo de la parte superior usando la operación de conversión explícita. Tabla 3.5. Conversio nes explícitas de tipo de valor
—
s b y te
t . •
: : i ' :
I I..
.
, !
b y te s h o rt u s h o r t i n t u in t lo n g
c h a r f l o a t u lo n g d e c im a l d o u b le
X
X
-
X
X
-
X
X
-
-
-
-
X
X
-
-
-
-
-
X
-
-
-
-
X
X
X
X
-
X
X
X
-
-
-
-
X
X
X
X
-
-
-
X
-
-
-
-
X
X
X
X
X
X
-
X
-
X
-
-
X
X
X
X
X
X
-
X
-
-
-
-
X
X
X
X
X
X
X
X
-
X
-
-
X
X
X
-
-
-
-
X
-
-
-
-
X
X
X
X
X
X
X
X
X
X
X
-
X
X
X
X
X
X
X
X
-
X
-
-
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
T amb ién puede realizar conversiones explícitas sobre tipos de valor conv ir tiendo explícitamente el valor al tipo apropiado, como se mu es tra en el siguiente
91
ejemplo. C# permite u sar un oper ador de conversión explícita incluso con co n versiones implícitas, si lo desea: int integerVariable; long o n g V a r i a b l e ; IntegerVariable - 12 3; L o n g V a n a b l e = (long) IntegerVariable;
Esta sintaxis no es necesaria, porque C# permite conversiones implícitas de variables i n t a variables l o n g . pero puede escribirlo si quiere.
Cómo trabajar con cadenas C# admite un tipo de referencia llamado s t r i n g . El tipo de dato s t r i n g representa una cadena de caracteres Unicode.
N O T A : Unicode es un estándar mundial de codificación de caracteres. Los caracteres Unicode tienen 16 bits, con lo que admiten 65.536 caracteres posibles. Los caracteres ANSII tienen 8 bits, y aceptan hasta 256 caracte res posibles.
Use el siguiente código para cr ear e inicializar una cadena en C#: string
MyStnng;
MyStrmg
= "Helio
f rom C# !" ;
Al igual que con el resto de las variables, puede inicializar una cadena en la mi sma linea que su declaración: string MyString
= "Helio
from C#!";
Uso de caracteres especiales en cadenas C# permite usar una sintaxis especial para insertar caracteres especiales en su cadena. Estos caracteres especiales aparecen en la tabla 3.6. Tabla 3.6. C a r a c t e r e s e s p e c i a l e s d e C #
92
C a ra cteres
F u n c ió n
\t
Los caracteres especiales \t incrustan una tabulación en la cadena. Una cadena definida como h e i i o \ t t h e r e
C a ra cteres
F u n c ió n
se almacena en memoria con un carácter de tabulación entre las palabras helio y there. \r
Los caracteres especiales \r incrustan un retorno de carro en la cadena. Una cadena definida como hello\ rthere se almacena en memoria con un retorno de carro entre las palabras helio y there. El carácter retorno de carro devuel ve el cursor al principio de la línea, pero no mueve el cursor una línea por debajo.
\v
Los caracteres especiales \v insertan una tabulación ver tical en la cadena. Una cadena definida como helio\ v t h e r e se almacena en memoria con un carácter de tabulación vertical entre las palabras helio y there.
\f
Los caracteres especiales \ f insertan un carácter de im presión de página en la cadena. Una cadena definida como h e i i o \ f t h e r e se almacena en memoria con un carácter de impresión de página entre las palabras helio y there. Las impresoras suelen interpretar un carácter de impresión de página como una señal para pasar a una nueva página.
\n
Los caracteres especiales \n insertan una nueva línea en la cadena. Una cadena definida como h e l l o \ n t h e r e se almacena en memoria con un carácter de nueva línea entre las palabras h e lio y th e re . La comunidad de desabolladores de software ha debatido durante mucho tiempo la interpretación del carácter de nueva línea. Siem pre ha significado, "mueve la posición de la siguiente sali da una línea más abajo". La duda está en si la operación también incluye mover la siguiente posición al primer ca rácter de la línea anterior. .NET Framework interpreta el carácter de nueva línea como bajarlo una línea y devolver la posición del siguiente carácter al principio de la siguiente línea. Si no está seguro, siempre puede escribir los carac teres especiales \n y \ r juntos. Los caracteres especiales \x permiten especificar un ca rácter ASCII usando dos dígitos hexadecimales. Los dos dígitos hexadecimales deben seguir inmediatamente a los caracteres \x y deben corresponder con el valor hexadecimal del carácter ASCII que quiere producir. Por ejemplo, el carácter espacio en ASCII tiene el código de decimal 32 . El valor decimal 32 es equivalente al valor hexadecimal 20. Por tanto, una cadena definida como h e l l o \ x 2 0 there se almacena en memoria con un ca rácter de espacio entre las palabras helio y there.
93
C a ra cteres
F u n c ió n
\u
Los caracteres especiales \u permiten especificar un ca rácter Unicode usando exactamente cuatro dígitos hexadecimales. Los cuatro dígitos hexadecimales deben colocarse inmediatamente después de los caracteres \u y deben corresponder al valor hexadecimal del carácter Unicode que quiere producir. Por ejemplo, el carácter espacio en Unicode tiene un código de decimal 32. El valor decimal 32 es equivalente al valor hexadecimal 20. Portanto, una cadena definida c o m o h e l l o \ u 0 o 2 0 there se alma cena en memoria con un carácter de espacio entre las pa labras helio y there. Asegúrese de usar exactamente cuatro dígitos después de los caracteres \u. Si el valor es menor de cuatro dígitos, use ceros para rellenar su valor hasta que llegue a los cuatro dígitos.
\\
Los caracteres especiales \\ permiten especificar un ca rácter de barra invertida en la posición actual. Una cade na definida como h e l l o W t h e r e se almacena en memoria con un carácter barra invertida entre las palabras helio y there. La razón por la que debe haber dos barras invertidas es simple: el uso de una sola barra invertida podría hacer que el compilador de C# la confundiera con el principio de otro carácter especial. Por ejemplo, suponga que olvida la segunda barra invertida y escribe h e i i o \ t h e r e en su código. El compilador de C# verá la barra invertida y la ‘t’ en la palabra there y los confundirá con un carácter de tabulación. Esta cadena se almacenaría en memoria con un carácter de tabulación entre las palabras helio y there. (Recuerde que la T en there se interpretaría como un ca rácter de tabulación y no sería parte de la palabra real.)
Desactivación de los caracteres especiales en cadenas Puede o rdenar al compi lador de C# que ignore los caracteres especiales en una cadena ant eponi endo el signo @a la cadena: string MyString = @ "he 11 o\there " ;
Este código asigna a la variable MyString el valor del texto hello\there. Como la cadena tiene delante el signo se desactiva el modo habitual de inter pretar los caracteres \t como m ar cad or de tabulación. Esta sintaxis también permite escribir nombres de directorio en cadenas de nombre de archivo de C# sin
94
u sar la doble b ar ra invertida. Por defecto, siempre necesitará usar las dobles bar ras invertidas: string MyFilename = "C :W F o l d e r 1 W F o l d e r 2 W F o l d e r 3\\f i le .txt " ;
Sin embargo, con el prefijo @, puede conseguirlo con una sola ba r ra invertida. string MyFilename = 0"C :\Folder1\Folder2 \Folder3\ f i le .tx t ";
Cómo acceder a caracteres individuales en la cadena Puede acceder a caracteres en la cadena como si la cadena fuese una matriz. Concept ualment e, puede imaginarse una cadena como una matriz de caracteres. Puede us ar la sintaxis de elemento de matriz entre corchetes p ar a acceder a c ua l quier carácter de la cadena: char MyCharacter; string MyString = "Helio MyCharacter
from C#!";
= MyString[9];
Este código coloca el valor ' m ' en la variable MyCharacter. El carácter m ' está en el elemento 9 de la cadena, si imagina la cadena como una matriz de caracteres. Además, t enga en cuenta que esta matriz de caracteres es de base cero. El primer ca rác te r de la cadena se encuent ra en realidad en el elemento 0. El décimo carácter de esta cadena, como ha aprendido, está localizado en el elemen to 9.
Declaración de enumeraciones A diferencia de las variables trat adas hast a el momento, una enumeración no es un tipo en sí misma, sino una for ma especial de tipo de valor. Una enumeración se deriva de S y s t e m .Enum y proporci ona nombres par a valores. El tipo s u b y a cente que representa una enumeraci ón debe ser byte. short. int o long. C ad a campo de u na enumeración es estático y representa una constante. Para declarar una enumeración, debe usar la pal abr a clave enum seguida del nombre de la enumeración. A continuación debe escribir una llave de ape rt ura seguida por una lista de las cadenas de la enumeración y finalizar con una llave de cierre, como se m ues t ra en el siguiente ejemplo: public
enum Pizza
{ Supreme, MeatLovers, CheeseLovers,
95
Vegetable,
} Este código crea una enumeración llamada Pizza. La enumeraci ón pizza contiene cuatro pares diferentes nombre/ valor que describen diferentes tipos de pizza, pero no se definen valores. C uand o declara una enumeración, el primer nombre que declara t oma el valor 1 y así sucesivamente. Puede invalidar esta funcionalidad asi gnando un valor a cada nombre, como se muest ra a c ont i nua ción: public
enum Pizza
{ S up r eme = 2, MeatLovers-
3,
CheeseLovers = 4 , Vegetable - 5,
) El valor de cada campo de enumeración ha sido i ncrementado en 1. Aunque no todo este codigo es necesario. As ignando a Supreme un valor de 2. los si guien tes ca mpos siguen la secuencia. Por tanto, puede eliminar las asignaciones a MeatLovers. CheeseLovers. y Vegetable. Se puede hacer referencia a los enumeradores de una de estas dos formas. Puede p ro g ra ma r sobre sus nombres de campo o puede p ro g ra ma r sobre sus v alores. Por ejemplo, puede asi gnar el nombre de ca mp o a una v ariable de c ade na con el siguiente código: string MyString - Pizza. Supreme;
Quizas quiera hacer referencia al valor de un campo. Puede conseguirlo m e diante la conversión de tipos. Por ejemplo, puede recuper ar el valor del campo Supreme con el siguiente código: Int. Mylnteger
=
(int )Pi z z a .Supreme ;
Resumen Este capitulo examina las v ariables y sus tipos. Hay muchas clases de tipos de valor y cada uno tiene sus propias características y requisitos de memoria. Al g u nos tipos pueden ser convertidos implícitamente a otros tipos, pero otros deben ser convertidos explícitamente usando la sintaxis apropiada. Las matrices contienen colecciones de variables del mismo tipo. Son útiles cuando se necesita mant ener un conjunto de variables del mismo tipo. C# admite matrices unidimensionales y multidimensionalcs. Las matrices de C# son de base cero: es decir, el número del primer elemento de una matriz es el 0. Las cadenas le ayudan a t rabaj ar con partes de texto en su código. Son conjuntos de caracteres
96
Unicode. C# permite i ncrust ar caracteres especiales en sus cadenas, pero p r o porciona el prefijo @ p a r a especificar los casos en los que no necesite que se procesen los caracteres especiales. Se puede acceder a los caracteres de una cadena como si fueran matrices de caracteres.
97
Expresiones
Las expresiones son el elemento bás ico y fundament al de cualqui er lenguaje de p rogr amaci ón. Medi ant e el uso de operadores, las expresiones permiten que una operación realice compar aci one s simples, asignaciones e incluso op er a ci o nes muy complejas que necesitarían millones de años p a r a completarse. Este capítulo t rata del uso de los operadores p ar a realizar funciones m a t e m á ticas. asi gnar valores a variables y realizar comparaciones. Una vez que h a y a mos comprendi do estos elementos básicos, est udi aremos al gunas expresiones a va nzad as que usan operadores muy específicos del lenguaje C # y que le brindan u na ventaja sobre la mayo rí a de los demás lenguajes de programaci ón. Para ce rrar este capítulo, revisaremos las expresiones que usan operadores p ar a m an i p ul ar la parte más pequeñas de un byte. el bit.
Cómo usar los operadores Las expr es iones puede n escri bir se u s a n do variabl es: val ores codificados específicamente, llamados valores literales (explicados en la sección "Cómo usar literales", posteriormente en este mi smo capítulo): y símbolos llamados opera d o res. C# admite varios operadores, cada uno de los cuales realiza una acción diferente. Las variables o los valores literales que aparecen en una expresión se
99
llaman opéremelos. Los operadores se aplican a los operandos y el resultado de la operación es otro valor. C# consta tres tipos de operadores: O p erad o res unarios. trabajan con un solo operando. Una expresión con un oper ando y un oper ador produce un solo valor. •
O perad ores binarios, t rabaj an con dos operandos. Una expresión con dos operandos y un o per ador produce un solo valor. O p erad o res ternarios, trabajan con tres operandos. C # admite sólo un o per ando ternario.
Uso de expresiones primarias Las expresiones pri mari as son la base del código C#. C# define varios tipos diferentes de expresiones primarias: Literales Identificadores Expresiones con paréntesis •
Acceso a mi embros Expresiones de invocación Acceso a elementos La pal abr a clave this Acceso a bases Op erado re s de incremento y decremento postfijo El oper ador new El operador typeof
•
Los operadores checked y unchecked
Las expresiones pri mari as permiten definir el orden de las operaciones dentro de una expresión, definir nuevos literales (por ejemplo, valores codificados específicamente) y declarar nuevas variables p ar a la aplicación. En las siguientes secciones se e xa mi na rán estas expresiones y cómo usarlas.
Cómo usar los literales Los literales son valores codificados es pecíficamente que se pueden escribir di rectamente en el código fuente C#. Ha y muchos tipos de literales diferentes.
100
Para mos trar lo que es un literal, examine la siguiente línea de código en C# que us a el valor literal Brian. if (FirstName == "Brian")
Aquí se ha codificado el valor de Brian p ar a usarlo en una comparación. En lugar de un valor definido por el usuario, es preferible al mac enar las cadenas en variables de modo que. si hiciera falta ca mbi ar los valores, sólo habría que c a m biarlos en un sitio sin necesidad de bu s ca r cada una de sus ocurrencias en todas las líneas de código. El siguiente código mues tr a el mejor método p a r a al mac enar y u s a r una c ad e na con vistas a compararla: string MyFirstName = "Brian; if (FirstName == MyFirstName)
Como puede ver, éste es un enfoque mucho más claro p a r a u sar un valor literal.
Literales booleanos C# define dos valores literales booleanos, las pal ab ras clave True y False: bool MyTrueVariable = true; bool MyFalseVariable = false;
Ambos valores tienen un tipo de valor bool. La pal abra clave True es el equivalente entero de uno negativo (-1). mientras que el equivalente de False es el cero.
Cómo usar los literales enteros en notaciones decimales y hexadecimales Se pueden escribir literales enteros us ando una notación decimal o una n o t a ción hexadecimal. De for ma par eci da a los literales vistos anteriormente, estos literales permiten ordenar el código. Los valores literales pueden ser colocados en la parte superior del listado del código. Si result ara necesario modificar estos en al guna ocasión, resultaría muy sencillo c a mb i ar la ocurrencia del valor. Los literales decimales integrales se escriben como series de uno o más n ú m e ros usando los caracteres 0, 1. 2. 3. 4. 5, 6. 7, 8 y 9: int MyVariable
= 125;
Los literales decimales también pueden contener un sufijo de un ca rác te r que especifique el tipo del literal. Si el literal tiene como sufijo una U may ús cul a o minúscula, el literal decimal se considera de tipo sin signo: uint MyVariable
= 125U;
101
El t ermi no sin signo significa que no se especifica si el número es positivo o negativo. Por tanto, si convierte un valor de 100 negativo (-100) a un valor sin signo, su resultado sería simplemente cien (100). Si el valor es lo suficientemente pequeño como p a r a poder ser almac en ado en un tipo uint. el compi lador de C# consi derará el literal como de tipo uint. Si el valor del literal integral es demasiado grande p ar a un tipo uint, el compi lador de C # consi der ará el literal como de tipo ulong. Los diferentes tipos representan el t am añ o de la información que está al macenando. El tipo uint puede contener un número comprendido entre 0 y 4 . 294 .9 67. 29 5; mientras que el valor ulong puede contener un valor entre 0 y 8. 446 .74 4. 073 .70 9. 55 1.615. Si el literal tiene como sufijo una L m ay ús cul a o minúscula, el literal decimal se consi dera de tipo long: long MyVariable = 125L;
Si el valor está dentro del rango de tipo long. el compi lador de C# con si der a rá el literal como de tipo long. Si el valor no está dentro del rango de tipo long. el c ompi lador de C # cons ider ará el literal como de tipo ulong.
JNOTAf
el compilador de C# acepta tanto la 1 minúscula como la
L mayúscula como sufijos, probablemente preñera usar la L mayúscula. La 1 minúscula se parece demasiado al número 1 y si otros programadores leen el código, podrían confundir la 1 con el 1. Si el literal tiene como sufijos L y U. el literal decimal se considera de tipo
long sin signo: ulong MyVariable = 125LU;
El compi lador de C# acept a tanto un sufijo en el que la L aparece delante de la U como un sufijo en el que la U aparece delante de la L. Además, el compi lador de C# acepta la combinación de letras en mayúscul as y en minúsculas. Los sufijos LU. Lu. 1U. lu, UL. Ul. uL y ul denominan al sufijo ulong. Escribir literales integrales en formato hexadecimal permite escribir un literal usando las letras de la A a la F junto a los números del 0 al 9. Los literales h exadeci mal es deben tener el prefijo 0X o Ox: int MyVariable
= 0x7D;
// 7D hex = 125 decimal
Se pueden us ar letras mayús cul as y minúsculas par a la notación hexadecimal. Tambi én se pueden us ar como sufijos los mismos caracteres que est aban di sponi bles p ar a los literales decimales: long MyVariable
= 0x7DL;
La decisión de u sar un valor hexadecimal queda compl et ament e a discreción del p rogr amador. Usar hexadecimales en lugar de otro tipo de literales no supone
102
ni nguna diferencia respecto a u s ar cualquier otro tipo de número. No obstante, es aconsejable u s ar valores hexadeci mal es cuando se está const ruyendo una a p l i ca ción que utilice especificaciones en formato hexadecimal. Por ejemplo, una interfaz p ar a el mó dem de su ordenador. La referencia del p r o g r a ma do r p a r a su módem podría especificar los valores de algunas operaciones en formato hexadecimal. En lugar de leer toda la referencia del p r o g r a ma do r y convertir todos los números al s i s t e m a d e c i m a l , n o r m a l m e n t e só lo t e n d r í a qu e c o d i f i c a r e s t o s n ú m e r o s hexadecimales directamente en la aplicación, evitando así cualqui er error de c on versión.
Cómo usar los literales reales para valores de coma flotante Los literales reales permiten escribir valores de coma flotante en el código C#. Los literales reales pueden incluir tanto una coma decimal como un exponente. Las comas decimales pueden apa r ec e r en literales reales y los números p u e den ap ar ec er antes y después de la co ma decimal. Tamb ién es posible que un literal real empiece con una coma decimal, lo que es útil cua ndo se quiere crear un valor m ayo r de cero, pero menor de uno. Los valores como 2,5 y ,75 son ejemplos de literales reales. C# no impone ningún límite al número de cifras que pueden apa rec er antes o después de la coma decimal, mientras el valor del literal quede dentro del rango del tipo deseado. T am bi én puede especi fi car un e xp on e n te en sus literales reales. Los exponentes se escriben con una E m ay ús cul a o mi nu scu la i nmediatamente después de la porción decimal del número. Tr as la E se incluye uno o más números decimales p a r a indicar el valor del exponente. Esto quiere decir que se puede escribir el valor 750 como un literal real de 7 .5e2. Tambi én puede apa r ec e r un signo más o un signo menos entre la E y el valor del exponente. Un signo más significa un exponente con valor positivo: un signo menos significa un exponente con valor negativo. El literal real 7 .5e + 2 define un valor de 750 v el literal real 7 .5e-2 define un valor de .075. Si no se usa ninguno de los dos signos, el compi lador de C# consi dera que el valor del e x p o nente es positivo. Al igual que los literales decimales, los literales reales también pueden llevar detrás un sufijo p ar a especificar el tipo del literal. Si no se usa un sufijo en el literal real, el compi lador de C# considera que el literal es de tipo double. Si el literal real tiene como sufijo una F mayú scu la o minúscula, se considera que el literal decimal es del tipo f loat: float MyVariable = 7.5F;
Si el literal real tiene como sufijo una D m ayú sc ul a o minúscula, se considera que el literal decimal es de tipo double: double MyVariable
= 7.5D;
Si el literal real tiene c omo sufijo una M m ayú sc ul a o minúscula, se considera que el literal decimal es de tipo decimal: decimal MyVariable = 7 .5M;
103
Cómo usar los literales de carácter para asignar valores de carácter Los literales de carácter permiten escribir valores de carácter en el código C#. Normalmente, los literales de ca rácter ap arecen entre comillas simples: char MyVariable = 'a' ;
T ambi én se pueden usar las secuencias de escape vistas en un capítulo a nt e rior par a escribir literales de carácter en código C#. Estos literales de carácter deben encerrarse entre comillas simples: char MyVariable = '\t'; // caracter tabulador
NOTA: Si quiere utilizar una comilla simple como literal de carácter, de berá anteponerle una barra invertida. Escribir confunde al compilador de C#. Escriba en su lugar *V\ Puede definir valores hexadccimales como literales de carácter us an do la se cuencia de escape \x seguida de uno. dos o tres caracteres hexadccimales: char MyVariable
=
'\x5C';
Cómo usar los literales de cadena para incrustar cadenas Los literales de cadena permiten incrustar cadenas en el código CU. Los litera les de cadena se escriben como se indica en un capítulo anterior, poniendo la cadena entre dobles comillas: stnng
MyVariable
= "Helio from C#!";
El compi lador de C# reutiliza muchos literales de cadena con los mismos c o n tenidos. con lo que conserva espacio en el ejecutable final, como mu es tra el si guiente código: stnng stnng
Stringl = "Helio" ; S t r in g 2 = "Helio";
C ua nd o se compila este código, el ejecutable contiene u na copia del literal de la cadena Helio. Las dos variables de cadena leen el valor de la única copia al mac enada en el ejecutable. Esta optimización permite al compi lador de C# c on servar el uso de memori a del código, ya que a lmac en ar sólo una copia del literal requiere menos memori a que al ma c en ar dos copias del mismo literal.
Cómo usar los literales nuil El literal nuil es una p al abr a clave de C # que permite poner un objeto en un estado nulo o sin uso: object MyOb]ect = nuil;
104
Uso de identificadores Los identificadores C# son ejemplos de expresiones simples. Los identificadores tienen un tipo y el tipo se especifica c uando se declara el identificador: int MyVariable
= 123;
El identificador MyVariable se considera una expresión y tiene un tipo int. Los identificadores pueden ser definidos en c ualqui er bloque de código que se encuentre entre llaves, pero su tipo no puede cambiar: public
static void M a i n ()
{ int MyVariable MyVariable MyVariable
= 123;
= 1; = 2;
// "MyVariable" // "MyVariable"
todavía es un todavía es un
"int” "int"
} Si intenta redefinir el tipo de un identificador dentro del mi smo bloque de código, el c ompi lador de C# gene rar á un mensaje de error. public
static void M a m ()
{ int MyVariable = 123; float MyVariable = 1.25;
} El compilador de C# genera un mensaje de error en la linea que intenta redefinir
MyVariable como un val or float'. error CS0128: Ya se ha definido una variable 'MyVariable' en este ámbito
local
denominada
Sin embargo, se puede reutilizar el identificador si aparece en un bloque de código separado: public
static void M a i n ()
{ int MyVariable
= 12 3;
} public
void A n o t h e r F u n c t i o n ()
{ float MyVariable
= 1.25;
}
Expresiones entre paréntesis Como su nombre indica, las expresiones entre paréntesis son expresiones e n cerradas entre paréntesis. El compilador de C# evalúa la expresión incluida en los
105
paréntesis y el valor de la expresión entre paréntesis es el resultado de la e v a l ua ción. Por ejemplo, el valor de la expresión entre paréntesis (3+2) es 5.
Cómo llamar a métodos con expresiones de acceso a miembros Cu an do se necesita l lamar a un método de un objeto, se escribe el nombre del objeto seguido por un punto y p or el nombre del método. C uando el C L R llama al m ét o do Ma i n ( ) p ar a empezar a ejecutar la aplicación, crea un objeto a partir de su clase y llama a la función M a i n ( ) de ese objeto. Si se escribiese este código en C#„ sería algo parecido a lo siguiente: MyClass
MyObject;
MyObject = new MyClass () ; MyObj e c t .M a i n () ;
Los objetos se estudian con más detalle en capítulos posteriores. Lo más i m portante a ho ra es darse cuenta de que la instrucción que llama a M a i n ( ) cont ie ne una expresión de acceso a miembros, que contiene un objeto, un punt o y una l lamada de función. En capítulos posteriores, verá qué objetos pueden tener datos además de codigo. Puede acceder a los datos usando la mi sma sintaxis de expresión de acceso a miembro.
Cómo llamar a métodos con expresiones de invocación Las expresiones de invocación se usan par a hacer una llamada a un método en un objeto. El código us ado en el caso del acceso a mi embro también mues tra una expresión de invocación. El código llama a un método (en este caso M a i n ( ) ). que hace que el codigo invoque al método M a i n ( ) del objeto. Si se llama a un método desde otro método en el mi smo objeto, puede us ar el nombre del método en la llamada. No necesita es pecificar un objeto o un nombre de clase v no es necesaria la sintaxis de acceso a miembro, como muest ra el listado 4.1. Listado 4.1. Expresión de invocación class
MyClass
{ public
static void M a i n ()
{ MyClass
106
myclass
= new M y C l a s s ();
myc l a s s .D o W o r k ();
} void
D o W o r k ()
{ // haga
aqui
su trabajo
} } En este ejemplo, el método Main () llama a un método DoWork (). Sin embargo, antes hace falta crear una referencia a myClass y luego invocar al m ét o do DoWork (). El tipo de u na expresión de invocación es el tipo que devuelve la función a la que se llama. Si, por ejemplo, el código C# llama a u na función que devuelve un tipo int. la expresión de invocación que llama a ese método tiene un tipo int.
Cómo especificar elementos de matriz con expresiones de acceso a elementos Las expresiones de acceso a elementos permiten es pecificar elementos de m a triz. El número del elemento de mat ri z se escribe dentro de corchetes. int
[] MyArray;
MyArray = new int M y A r r a y [0] = 12 3;
[5];
En este ejemplo, al elemento cero de la mat ri z l lamada MyArray se le asigna el valor de 123. C# permite que cualqui er expresión que p ro du z ca un resultado de tipo int, uint, long o ulong se utilice como expresión de elemento. C# tambi én p er mi te el uso de cualquier expresión cuyo resultado sea de un tipo que pueda ser convertido implícitamente en tipo int. uint. long o ulong. En el código anterior, se us a un literal entero como expresión de elemento. Podría igualmente escribir un tipo de expresión diferente p ar a especificar el elemento, como muest ra el listado 4.2. Listado 4.2. Acceso a elementos class
MyClass
{ public
static void M a i n ()
{ int [] MyArray; MyClass myclass = new MyClass () ; MyArray = new int [5]; MyArray[ m y c l a s s .G e t A r r a y l n d e x ()] =
123;
107
int
GetArraylndex O
{ return
Ü;
} } Este eodigo funciona porque el método G e t A r r a y l n d e x ( ) devuelve un i n t y el resultado de la expresión de invocación es un i n t . Como cualquier expresión cuyo valor sea i n t puede ser usada como expresión de elemento de una matriz. C# permite que este código se ejecute. El resultado de la expresión de acceso al elemento es el tipo del elemento al que se accede, como se muest ra en el siguiente código: int
[] MyArray;
MyArray = new int MyArray[Ü] = 12 3;
[5];
La expresión de acceso al elemento M y A r r a y [ 0 ] es de tipo i n t porque el elemento al que se accede en la expresión es de tipo i n t .
Cómo acceder a objetos con la palabra clave this C# define una pal abra clave t h i s que puede usarse par a especificar un objeto p ar a un fragment o de código que necesite acceder a esc objeto. La pal ab ra clave t h i s se estudia con mas detalle en la sección que trata de las clases. El listado 4.3 usa la palabra clave t h i s . Listado 4.3. A cceso mediante palabra clave class
MyCiass
{ public
static
voicl Main ()
{ // llame D o W o r k () en este objeto MyCiass myclass = new M y C i a s s (); myclass .DoWork. (! ;
} void
DoWo r k ()
{ MyCiass myclass = new M y C i a s s (); t h i s .DoWo r k 2 () ; // haga aquí su trabajo
}
108
void
D o W o r k 2 ()
{ } } En este ejemplo, la e xpresi ón de acceso this tiene el tipo MyClass porque la clase MyClass contiene el código que incluye la expresi ón de acceso thís.
Cómo acceder a objetos con la palabra clave base C # también define la pal abr a clave base par a su uso con objetos. En un capítulo posterior apr ender á que puede usar clases como punto de partida par a const rui r nuevas clases. Las clases originales reciben el nombre de clases base y las clases construidas a partir de ellas se llaman clases derivadas. Para ordenar al código C# de la clase derivada que acceda a los datos de la clase base, se usa la pal abr a clave base. El tipo par a las expresiones que usan base es la clase base de la clase que contiene la p al ab r a clave base.
Cómo usar los operadores postfijo de incremento y de decremento C# permite incrementar o reducir valores numéricos usando símbolos es pe ci a les. El operador ++ incrementa el valor y el operador — reduce el valor. Se pueden apl icar estos operadores a expresiones de tipo sbyte. byte. short. ushort. int. uint. long y ulong. El listado 4.4 mues tra los operadores de incremen to y decrement o en acción. Listado 4.4. O peradores de incremento y de decrem ento class
MyClass
{ public
static void M a i n ()
{ int Mylnteger; Mylnteger = 125; Mylnteger++; // el valor Mylnteger--; // el valor
ahora es 126 ahora vuelve a ser
125
El tipo de u na expresión que u s a oper adores postfijos de incremento y de decrcmento concuerda con el tipo cuyo valor se está i ncrementando o reduciendo. En el listado 4.4. los oper adores de incremento y de decremento tienen tipo int.
109
Creación de nuevos tipos de referencia con el operador new El operador new se usa par a crear nuevas instancias de tipos de referencia. Hast a ahora, el operador new ha sido usado p ar a crear nuevas matrices y cuando estudie los objetos, aprenderá a usar el o per ador new par a crear nuevos objetos. El operador new es considerado una expresión y el tipo de la expresión c on cuerda con el tipo de la variable cr eada con la p al ab ra clave new.
Cómo devolver información sobre el tipo con el operador typeof El op er ado r typeof es una p al abr a clave C# que devuelve información sobre el tipo de una variable. Se usa como si fuera una función, empleando la p al abr a clave typeof seguida de una expresión: class
MyClass
{ public
static
void M a i n ()
{ System.Consolé.WriteLine (typeof (i n t ) ) ;
} } La p al ab ra clave typeof devuelve un objeto llamado System. Type que describe el tipo de la variable. El tipo de la expresión typeof es la clase
S y s t e m .Type.
Cómo usar operadores checked y unchecked Los operadores checked y unchecked permiten activar o desactivar la verifica ción en tiempo de ejecución de las operaciones matemát icas. Si se incluye una operación mat emát ica en un oper ado r checked. se i nformará de un error si la operación no tiene sentido. Si se incluye una operación mat emát i ca en un o p e r a dor unchecked. se informar á de un error incluso si la operaci ón no tiene sentido El listado 4 5 muest ra un probl ema de desbor damient o matemático. Se de c la ran dos variables enteras. Intl y Int2 y una tercera. IntlPlusInt2. c u y o valor a lmacena la suma de las otras dos. Los dos enteros se suman y el resultado se al macena en la tercera variable entera. Entonces el valor de la tercera variable se escribe en la consola. Listado 4.5. Desborde en operaciones matemáticas class
{
110
Listing4_5
publíc static void M a m ()
{ int int int
Intl; Int 2; IntiPlusInt2;
Intl = 2000000000; Int2 = 2000000000; IntlPlusInt2 - Intl + Int2; System.Cons ole.WriteLine(IntlPlusInt2) ;
} } A cada variable entera Intl y Int2 se le asi gna el valor de dos mil millones. Esta operación no supone ningún pro bl ema porque las variables enteras pueden al macenar valores por encima de dos mil cien millones. Sin embargo, s umar estos dos integrales y al macenar el resultado en otro integral va a s uponer un problema. La s uma sería cuatro mil millones, lo que s upera el valor límite de un entero, poco más de dos mil cien millones. Compile el código anterior con la línea de coman do habitual. ese
Listing4-l.cs
Cua nd o ejecute el archivo Listing4-1 exe. obt endrá un gran número negativo, como se ve en la figura 4.1. c \ C:\WINDOWS\System32\cmd.exe
^ Í S J 2L —
C : \ > L i s t. in y 4 - í» . e x e -2 9 4 9 6 7 2 9 6
—
C :\>
M
i —
—
Figura 4.1. Los d e sbordam ientos producen resultados impredecibles.
Se obtiene un result ado negativo debido al modo que tiene C# de pr oc e sa r los valores demasiado grandes p ar a encajar en las variables destinadas a ellos. C# no puedo representar todo el valor de un entero, así que toma el valor propuesto, c u a t r o mil m i l l o n e s y le r e s t a el v a l o r m á x i m o de un v a l o r de 32 bi ts (4.294.967. 296). extrayendo el resultado a la consola.
111
Obviamente, el código ha generado un resultado distinto del que queríamos. Si no se da cuenta de este tipo de errores matemáticos, su código puede c omport arse de for ma impredecible. Para insertar una medida de seguridad en códigos como éste, puede usar el operador checked, como aparece en el listado 4.6. Listado 4.6. Verificación de los desbordam ientos de las operaciones matemáticas class Listing4_6
{ public
static
void M a i n ()
{ int Intl; in t In 1 2 ; m t IntlPlusInt2; Intl
= 2000000000;
I n t 2 - 2000000000; IntlPlusInt2 = checked(Intl + Int2 ); System. Consolé. W r i t e L m e (IntlPlusInt2) ;
} } Si compila y ejecuta el listado 4.6 se escribirá un resultado diferente en la consola: Excepción no controlada: S ys t e m .OverflowException : La aritmética ha provocado un desbordamiento, at L is t i n g 4 _ l . M a i n ()
operacion
En lugar de escribir un valor mat emát ico sin sentido en la consola, un mensaje de desbor damient o permite saber que se intentó c om pr ob a r la validez del valor de la suma y que la p rue ba no fue superada. Se i nforma de una excepción y la aplicación concluye. La expresi ón unchecked ( ) es la que se u sa por defecto. En las expresiones que tienen unchecked ( ) no se comp ru e ba la validez de los valores y la ap li ca ción sigue ej ecutándose us ando los valores no verificados, aunque no tengan s en tido. El c omport ami ent o por defecto es el de no verificar las operaciones. Sin e m bargo. si quiere que se c omprue be si todos los valores de sus operaciones son válidos sin us ar el oper ador checked ( ) en el código, puede u s ar la opción / checkedi del compilador. Compil e el listado 4.1 con la siguiente línea de c o mando: ese
/checked+
L i s t m g 4 - 1 .es
C u an do se ejecuta el ejecutable de listing 4-1, se obtiene el mi smo mensaje de excepción que se obt uvo con listing 4-2, porque la opción /checked+ obliga a c om p ro ba r la validez de los valores de todas las operaciones matemáticas.
112
Las expresiones uñarías Las expresiones unarias funcionan sobre un solo operando. C # admite las siguientes expresiones unarias: Unario más o per ador Unario menos oper ador O p e r ad o r de negación lógica Op e ra do r de compl ement o bit a bit O p e r ad o r de direcci onami ent o indirecto O p e r ad o r de di recci onami ent o O p e r ad o r de incremento y decrcment o prefijado Expresiones de conversión La siguientes secciones t ratan estas expresiones unarias con detalle.
Cómo devolver valores de operando con el operador unario más El operador unario más (+) devuelve el v al or del operando. Puede pensar en él como el o pe r ad or m atem ático positivo. C# define el operador unario más para los oper andos de tipo int. uint, long. ulong. f loat. double y decimal.
Cómo devolver valores de operando con el operador unario menos El operador unario menos ( - ) devuelve el valor del operando. Puede pensar en él como el o pe r ad or m atem ático negativo. El valor de un oper ando con un oper a dor unitario menos es el equivalente negativo del operando. C # define el operador unario menos p ar a los operandos de tipo int, long, f loat. double y de
cimal.
Expresiones negativas booleanas con el operador de negación lógica El op er ad or de negación lógica niega el valor de una expresión booleana. El operador ca mbi a el valor True a False y el valor False a True. Se usa el signo de exclamaci ón p a r a escribir un oper ad or de negación lógica en el código
113
C#. El oper ador se coloca antes de la expresión bool eana que quiera negar, como se ilustra en el listado 4.7. Listado 4.7. Operador de negación lógica class MyClass
1 public
static
void Main ()
{ bool
MyBoolean;
MyBoolean MyBoolean
= true; = IMyBoolean;
//
"MyBoolean"
ahora
es
false
} )
El operador de complemento bit a bit C# permite aplicar una operación de complemento bit a bit a expresiones int. uint. long y ulong. Las operaciones de complemento bit a bit consideran al valor como si fueran un binario y dan la vuelta a todos los bits. Los bits que tenían un valor 1 se v u c h én 0 y los bits que tenían un valor 0 se vuelven 1. Los operadores de compl ement o bit a bit se especifican colocando el ca rácter virgulilla ( - ) antes de la expresión que debería ser compl ement ada bit a bit. como se ve en el listado 4.8. Listado 4.8. Operador de complem ento bit a bit class MyClass
{ p u b 1 ic s tat ic v o i el Ma in ()
{ in t In t 1; Int 1 = 123; Int l - --Int 1 ;
} }
Cómo prefijar operadores de incremento y decremento Los operadores postfijos ++ y — pueden ser usados en uno de los dos modos. Ya ha visto las versiones postfijas de los operadores, que aparece después de la expresión. Las versiones prefijas aparecen antes de la expresión, como se ve en el listado 4.9.
114
Listad o 4.9. Operadores de incremento y decrem ento prefijados
class MyClass
{ public
static void M a i n ()
{ int Mylnteger; Mylnteger = 125; ++Mylnteger; // el valor ahora es 126 --Mylnteger; // el valor ahora vuelve a ser
125
} } El tipo de un a expresión que us a los oper adores postfijos de incremento y decrement o c onc uer da con el tipo cuyo valor se increment a o reduce. T eng a en cuenta la sutil diferencia entre estos operadores prefijos y los operadores postfijos que se vieron con anterioridad: con los operadores prefijos, el valor se cambi a después de que se evalúe la expresión. El listado 4.10 ilustra esta diferencia. Listado 4.10. Diferencias entre operadores postfijos y prefijos class
Listing4_10
{ public
{ int
static
void M a i n ()
In 1 1;
I n t 1 = 12 3; S y s t e m . C o n s o l é . W r i t e L i n e (I n t 1 + + );
System.Consolé.WriteLine(++Int 1 );
} } Compile y ejecute el listado 4.3. El resultado de esta aplicación aparece en la figura 4.2. La pri mera instrucción del listado 4.10 usa el oper ador postfijo de incremento, lo que significa que el val or se increment a después de que se ejecute la instrucción. La aplicación escribe el valor actual. 123. en la consola y luego incrementa el valor a 124. La segunda instrucción usa el oper ad or de incremento postfijo, lo que significa que el val or se incrementa antes de que se ejecute la instrucción. La aplicación primero incrementa el valor actual a 125 y luego escri be el valor actual en la consola.
Los operadores aritméticos Los operadores aritméticos permiten realizar cálculos en el código CU. Las expresiones que us an operadores aritméticos son expresiones binarias porque se necesitan dos operandos p ar a realizar una operación matemática.
115
HBAVJi IJl.MUUiffl m *i'lWff!BW C :\>L ist ing4-10.exe 123 125
Id Figura 4.2. Uso de operadores prefijos y postfijos
Cómo asignar nuevos valores con el operador de asignación El oper ador de asignación asigna un nuevo v alor a una variable. El signo igual se usa como operador de asignación: Mylnteger
- 3;
Se establece el valor de Mylnteger en 3 y se pierde el valor anterior de
MyVariable. Los operadores de asignación compuest a permiten usar el operador de a s i g n a ción mas de una vez en una instrucción: Mylnteger
= MyOtherInteger
= 3;
El valor de la expresión a la derecha se usa como el nuevo valor par a las variables. En este ejemplo, se asigna 3 a Mylnteger y a MyOther Integer.
Uso del operador multiplicación El v alor de una expresión que usa el oper ador de multiplicación es el producto de los valores de los dos operadores. El carácter asterisco se usa como operador de multiplicación, como se v e en el listado 4.11. Listado 4.11. Operador multiplicación class
MyClass
{ public
116
static
void M a i n ()
{ int M y l n t e g e r ; Mylnteger
= 3 * c;
// Mylnteger
sera
18
} } Si se está mul tiplicando un valor por una v ariable v colocando el resultado en la misma variable, se puede escribir una instrucción abreviada par a realizar la multiplicación. Al introducir un asterisco seguido por un signo igual se multiplica un v alor por una variable y se actualiza el v alor de la variable con el resultado: MyIntege r *= 3 ;
Esta instrucción es la abrev iatura de la siguiente: Mylnteger = Mylnteger
* 3;
Uso del operador división El valor de una expresión que usa el o per ador de div isión es el producto de los valores de los operadores. La bar ra inclinada es el carácter que se usa como o per ado r de división, como se ve en el listado 4.12. Listado 4.12. Operador división (E jemplo 1) class
MyClass
( public
static
voicl Main ()
{ int M y l n t e g e r ; Mylnteger
= 6 / 3 ;
// Mylnteger
sera
2
} } Si la operación de división da como resultado un resto, el resultado de la operación será sólo el cociente (véase el listado 4.13). Listado 4.13. Operador división (Ejemplo 2) class
MyClass
{ public
static void M a i n ()
{ int Mylnteger; Mylnteger
= 7/3;
} }
117
Cuand o se ejecuta este código, la variable Mylnteger tiene un valor de 2. porque si se divide 7 entre 3 queda un cociente de 2 y un resto de 1. Si se divide un valor entre una variable y se coloca el resultado en la mi sma variable, se puede escribir una instrucción abrevi ada p ar a realizar la división. Escribiendo una b ar r a inclinada seguida por un signo igual se divide un valor entre una variable y se actual iza el valor de la variable con el resultado: Mylnteger /= 3 ;
La instrucción anterior es una abr evi at ura de la siguiente: Mylnteger = Mylnteger / 3;
Uso del operador resto El valor de una expresión que usa el oper ado r de resto es el resto de una operación de división. El ca rác te r tanto por ciento se usa como el oper ador de división (véase el listado 4.14). Listado 4.14. Operador resto class
MyClass
{ public
static
void M a i n ()
{ int Mylnteger; MyIntege r = 7
3;
} } Cuand o se ejecuta este código, la variable Mylnteger tiene el valor de 1. porque si se divide 7 entre 3 queda un cociente de 2 y un resto de 1. Si se está calculando un resto usando una variable y se coloca el resultado en la mi sma variable, se puede escribir una instrucción abrevi ada para realizar la operación de resto. Si se escribe un signo de t anto por ciento seguido del signo igual se calcul ará el resto de una v ariable y se actual izará el valor de la v ariable con el resultado: Mylnteger
= 3;
La instrucción anterior es la abr ev iat ura de la siguiente: Mylnteger
- Mylnteger
3;
Uso del operador suma El valor de una expresión que usa el oper ador de suma es la s uma de los valores de los dos operadores. El carácter s uma se usa como el oper ador de multiplicación (véase el listado 4.15).
118
Listad o 4.15. Operador suma class
MyClass
{ public
static void M a i n ()
{ int Mylnteger; Mylnteger = 3 + 6 ;
// Mylnteger
será
9
} } Si se está sumando un valor a una variable y se coloca el resultado en la misma variable, se puede escribir una instrucción abr evi ada que realice la suma. Al escribir un signo más seguido de un signo igual se añade un valor a una variable y se actual iza el val or de la variable con el resultado: Mylnteger
+=
3;
La instrucción anterior es la abr evi at ura de la siguiente: Mylnteger
= Mylnteger
+ 3;
El oper ado r de s uma tiene un significado especial cuando los dos operandos son cadenas. La s uma de dos cadenas une la pri mera cadena a la segunda: string C o m b i n e d S t r m g
= "Helio
from " + "C#";
El valor de CombinedString es Hello f rom C# cuando se ejecuta este código.
Uso del operador resta El valor de una expresión que usa el oper ador de resta es la diferencia de los valores de los dos operadores. El carácter guión se usa como el oper ador de resta (véase el listado 4.16). Listado 4.16. O perador resta class
MyClass
{ public
static
void M a i n ()
{ int Mylnteger; Mylnteger = 7 - 3 ;
// Mylnteger
será
4
} } Si se está restando un valor a u na variable y se coloca el resultado en la misma variable, se puede escribir una instrucción abr evi ada que realice la resta. Al es-
119
cribir un signo menos seguido de un signo igual se resta un valor de una variable y se actualiza el valor de la variable con el resultado: Mylnteger -= 3;
La instrucción anterior es la abrevi at ura de la siguiente: Mylnteger = Mylnteger - 3;
Los operadores de desplazamiento Los oper adores de despl azami ent o permiten mover bits de lugar en un valor de su codigo C#. Las expresiones que usan los operadores de despl azami ent o son expresiones binarias porque se necesitan dos operandos p ar a realizar una operación de desplazamiento.
Cómo mover bits con el operador de desplazamiento a la izquierda El valor de una expresión que usa el operador de desplazamiento a la izquierda se muev e a la izquierda la cantidad de bits especificados. Se usan dos caracteres menor que (<
MyClass
{ public
static
void M a i n ()
( mt
Mylnteger;
M y l n t e g e r = 6 << 3; ( } Cua nd o se ejecuta este código, la variable Mylnteger tiene un valor de 48. porque el valor original. 6. es considerado un número binario con un valor binario de ()()()()() l 10. Cad a bit en el valor original se desplaza tres lugares, que es el valor que aparece después del oper ad or de despl azami ent o a la izquierda y se colocan ceros en los bits de orden inferior. Al ca mbi ar cada bit tres lugares da como resultado un valor binario de 00 l 10000 o 48 en el sistema decimal. Se pueden aplicar desplazamientos a la izquierda a los valores de las expr esi o nes de tipo int. uint. long y ulong. Tambi én pueden desplazarse a la iz quierda otras expresiones que pueden ser convertidas a uno de estos tipos. Las
120
expresiones de tipo int y uint pueden despl azarse hast a 32 bits de una vez. Las expresiones de tipo long y ulong pueden ser despl azadas hasta 64 bits de una vez. Si se está calculando una operación de desplazamiento a la izquierda de un valor y una variable, y se coloca el resultado en la mi sma variable, se puede escribir una instrucción abreviada que realice esta operación. Al escribir dos signos menor que ( « ) seguidos por un signo igual se calcula la operación de desplazamiento a la izquierda y se actualiza el valor de la variable con el resultado. Mylnteger <<= 3;
La instrucción anterior es la abr evi at ura de la siguiente: Mylnteger = Mylnteger << 3;
Cómo mover bits con el operador de desplazamiento a la derecha El valor de una expresión que usa el oper ado r de despl azami ent o a la derecha se mueve a la derecha la cantidad de bits especificados. Se usan dos caracteres m ayo r que ( » ) como operadores de desplazamiento a la derecha (vt'ase el listado 4.18). Listado 4.18. O p erador de desplazam iento a la derecha class
MyClass
( public
static void M a i n ()
{ int Mylnteger; Mylnteger
= 48 >> 3;
} } Cuand o se ejecuta este código, la variable Mylnteger tiene un valor de 6. porque el valor original, 48. es considerado un número binario con un valor binario de 00110000. Cada bit en el valor original se desplaza tres lugares, que es el valor que aparece después del oper ad or de despl azami ent o a la derecha y se colocan ceros en los bits de orden superior. El ca mbi a r cada bit tres lugares da como resultado un valor binario de 0 0 00 01 1 0 o 6 decimal. Se pueden aplicar desplazamientos a la derecha a los valores de las exp res io nes de tipo int. uint. long y ulong. Tambi én pueden despl azarse a la der e cha otras expresiones que pueden ser convertidas a uno de estos tipos. Las expresiones de tipo int y uint pueden despl azarse hast a 32 bits de una vez. Las expresiones de tipo long y ulong pueden ser despl azadas hasta 64 bits de una vez.
121
Si está calcul ando una operación de des plazamiento a la derecha de un valor y una variable y colocando el resultado en la mi sma variable, puede escribir una instrucción abr evi ada que realice esta operación. Escribir dos signos may or que seguidos por un signo igual calcula la operación de desplazamiento a la derecha y actual iza el valor de la v ariable con el resultado: Mylnteger
> >-
3;
La instrucción anterior es la abr evi at ura de la siguiente: Mylnteger
= Mylnteger
>> 3;
Cómo comparar expresiones con operadores relaciónales Los operadores relaciónales permiten c o m pa r ar dos expresiones y obtener un v alor booleano que especifica la relación entre las dos expresiones. Las exp res io nes que usan operadores relaciónales son expresiones binarias porque se necesi tan dos operandos par a realizar una operación relacional.
Cómo comprobar la igualdad con el operador de igualdad El operador de igualdad se usa para c omp rob ar la igualdad entre los v alores de dos expresiones. Si las expresiones tienen el mismo valor, el operador de igualdad devuelve Truc. Si tienen valores diferentes, el oper ad or de igualdad devuelve F a l s e . Como operador de igualdad se usan dos signos igual: Mylnteger
--
12 3;
Si el valor de la variable Mylnteger es 123. el o per ador de igualdad devuel ve True. Si tiene otro valor, el oper ad or de igualdad devuelve False. El o perador de igualdad tiene un significado especial cuando los dos operandos son cadenas. Al co mp ar a r dos cadenas se comp ar an los contenidos de las c a d e nas. Dos cadenas se consideran iguales si tienen la mi sma longitud y los mismos caracteres en cada posición de la cadena.
Cómo comprobar la desigualdad con el operador de desigualdad El oper ador de desigualdad se usa par a comp ro b ar la desigualdad entre los valores de dos expresiones. Si las expresiones tienen diferentes valores, el o p e r a dor de desigualdad devuelve True. Si tienen el mismo valor, el oper ad or de
122
d es igual dad devuelve False. C omo o per ad or de desigualdad se us a un signo de exclamaci ón seguido por un signo igual: Mylnteger != 123;
Si el valor de la variable Mylnteger es 123. el op er ad or de desigualdad d evu el ve False. Si tiene otro valor, el oper ado r de desigualdad devuelve Trué. El o p er a do r de desi gual da d tiene un s ignificado especial c ua nd o los dos operandos son cadenas. Al c om p a r a r dos cadenas se c om pa r an los contenidos de las cadenas. Dos cadenas se consideran desiguales si tienen diferentes longitudes o diferen tes caracteres en, al menos, una posición de la cadena.
Cómo comprobar valores con el operador menor que El operador menor que se usa p ar a comp ro ba r los valores de dos expresiones y ver si un valor es menor que el otro. Si la pri mera expresión tiene un valor menor que el de la s egunda expresión, el oper ad or menor que devuelve True. Si la p ri mera expresión tiene un valor m ay o r o igual que el de la s egunda expresión, el o pe r ad or menor que devuelve False. El o per ado r menor que se representa m e diante un signo menor que (<): Mylnteger
< 123;
Si el valor de la variable Mylnteger es menor de 123. el op er ad or menor que devuelve True. Si tiene un valor mayor que o igual a 123, el operador menor que devuelve False.
Cómo comprobar valores con el operador mayor que El operador mayor que se us a p ar a comp ro ba r los valores de dos expresiones y ver si un valor es m ay or que el otro. Si la pri mera expresión tiene un valor mayor que el de la segunda expresión, el oper ado r m ayo r que devuelve True. Si la p ri mera expresión tiene un valor menor o igual que el de la s egunda expresión, el op er ad or m ay or que devuelve False. El op er ad or m ayo r que se representa m e diante un signo m ay or que (<). Mylnteger
> 123;
Si el valor de la variable Mylnteger es mayor de 123, el oper ad or mayor que devuelve True. Si tiene un valor menor que o igual a 123. el operador menor que devuelve False.
123
Cómo comprobar valores con el operador menor o igual que El o per ador menor o igual se usa par a c ompr ob ar los valores de dos expr es io nes y ver si un valor es menor o igual que el otro. Si la pri mera expresión tiene un valor menor o igual que el de la segunda expresión, el operador menor o igual que devuelve True. Si la primera expresión tiene un valor mayor que el de la segunda expresión, el op er ad or menor o igual que dev uelve False. Como op er ad or menor o igual que se usa un signo menor que seguido de un signo igual (< = ): Mylnteger
<=
12 3;
Si el valor de la variable Mylnteger es menor o igual a 123. el operador menor o igual que devuelve True. Si tiene un valor mayor de 123. el oper ador menor o igual que devuelve False.
Cómo comprobar valores con el operador mayor o igual que El oper ador mayor o igual que se usa par a co mp ro ba r los valores de dos expresiones y ver si un valor es m ayo r o igual que el otro. Si la primera expresión tiene un valor mayor o igual que el de la s egunda expresión, el operador mayor o igual que dev uelve True. Si la pri mera expresión tiene un valor menor que el de la segunda expresión, el op er ado r mayo r o igual que devuelve False. C omo oper ado r m ay or o igual que se usa un signo mayor que seguido de un signo igual (> = ): Mylnteger
>=
12 3;
Si el valor de la variable Mylnteger es mayor o igual a 123, el operador mayor o igual que devuelve True. Si tiene un valor menor de 123. el operador m ayo r o igual que devuelve False.
Operadores lógicos enteros Los oper adore s lógicos enteros permiten real izar oper aci ones arit mét icas bool eanas sobre dos valores numéricos. Las expresiones que usan operadores lógicos enteros son expresiones binarias porque se necesitan dos operandos par a realizar una operación lógica.
124
Cómo calcular valores booleanos con el operador AND El o per ador AND se usa p ar a calcular el valor AND bool eano de dos e xp res i o nes. C omo o per ado r AND se usa el símbolo de unión (&): Mylnteger = 6 & 3;
El val or de M y l n t e g e r es 2. Recuerde que un bit en una operaci ón AND es 1 sólo si los dos bits operandos de la mi sma posición son 1. El valor 6 en binario es 1 10 y el valor 3 en binario es 0 1 1 . Si se realiza un AND booleano de 1 10 y 01 1 se obtiene como resultado un valor booleano de 010 o 2 en el sistema decimal. Si se está calculando una operación AND sobre un valor y una variable y se coloca el resultado en la mi sma variable, se puede escribir una instrucción a b r e viada que realice la operación AND. Si se escribe un signo de unión (&) seguido de un signo igual se calcula la operación AND sobre una variable y un valor, y se actual iza el valor de la variable con el resultado: Mylnteger
&- 3;
La instrucción anterior es la ab rev iat ura de la siguiente: Mylnteger
= Mylnteger
& 3;
Cómo calcular valores booleanos con el operador exclusivo OR El oper ado r exclusivo OR se us a p ar a calcular el valor booleano exclusivo OR de dos expresiones. C omo o per ad or OR exclusivo se usa el signo de intercalación ( A): Mylnteger
= 6 A 3;
El val or de M y l n t e g e r es 5. Recuerde que un bit en una operación exclusiva
OR es 1 sólo si uno de los dos bits operando en la mi sma posición es 1. El valor de 6 en binario es 110 y el valor de 3 en binario es 0 1 1. Si realizamos un booleano OR exclusivo entre 110 y 011 obt enemos como resultado un valor booleano de 101 o 5 en el sistema decimal. Si se está calculando u na operación OR exclusiva sobre un valor y una v a r ia ble y se coloca el resultado en la mi sma variable, se puede escribir una i nst ruc ción abr evi ada que realice la operación OR exclusiva. Si se escribe un signo de intercalación ( A) seguido de un signo igual se calcula la operación OR exclusiva sobre una variable y un valor, y se actualiza el valor de la variable con el result a do: Mylnteger
3;
125
La instrucción anterior es la ab revi at ura de la siguiente: Mylnteger = Mylnteger A 3;
Cómo calcular valores booleanos con el operador OR El oper ado r OR se usa p ar a calcular el valor booleano OR de dos expresiones. Como oper ador OR se usa el carácter barra vertical (|): Mylnteger = 6
| 3;
El valor de Mylnteger es 7. Recuerde que un bit en una operación OR es 1 sólo si uno de los dos bits operandos de la mi sma posición es 1. El valor 6 en binario es 110 y el valor 3 en binario es 01 1. Si se realiza un booleano OR entre 1 10 y 01 1 se obtiene como resultado un valor de 1 1 1 o 7 en decimal. Si se está calculando una operacion OR sobre un valor y una variable, y se coloca el resultado en la mi sma variable, puede escribir una instrucción a b r ev i a da que realice la operacion OR. Si se escribe una b ar ra vertical (|) seguido de un signo igual se calcula la operación OR sobre una variable y un valor, y se a c t u a liza el valor de la variable con el resultado: Mylnteger
|= 3 ;
La instrucción anterior es la ab revi at ura de la siguiente. Mylnteger = Mylnteger
| 3;
Operadores condicionales lógicos Los o per adores condicionales lógicos son los equivalentes condicionales de los operadores lógicos enteros. Las expresiones que usan los operadores co nd i cionales lógicos son expresiones binarias porque se necesitan dos operandos para realizar una operación condicional lógica.
Comparación de valores booleanos con el operador AND condicional El operador AND condicional se usa para compar ar dos expresiones booleanas. El resultado de la operación es True si ambos operandos devuelven True y False si uno de los dos operandos devuelve False. C omo o per ador AND c o n dicional se usan dos símbolos de unión: MyBoolean
126
= true
& & false;
El valor de MyBoolean es False porque uno de los dos oper andos devuel ve False.
Comparación de valores booleanos con el operador OR condicional El oper ador OR condicional se usa p ar a c om pa r ar dos expresiones booleanas. El resultado de la operación es True si uno de los dos oper andos devuelve True y False si los dos operandos devuelven False. C omo o per ador OR condicional se usan dos bar ras verticales: MyBoolean = true
|| false;
El valor de MyBoolean es True porque uno de los dos oper andos devuelve
True.
Comparación de valores booleanos con el operador lógico condicional El operador lógico condicional evalúa una expresión booleana. El resultado de la expresión tiene un valor si la expresión de ent rada devuelve True y otro si la expresión de ent rada devuelve False. Las expresiones que usan operadores con dicionales son expresiones ternarias porque se necesitan tres operandos p ar a rea lizar una operación lógica condicional. El operador condicional es la única expresión ternaria admitida por el lenguaje C#. Escribir un oper ador condicional implica escribir la expresión de entrada se gu id a por un signo de interrogación. El valor True aparece después, seguido de dos puntos y a continuación seguido por el valor False: Mylnteger
=
(MyVanable
==
123)
? 3:
5;
Puede interpretar esta instrucción como " Co mp ar a el valor de MyVariable con 123. Si esa expresión devuelve True. haz que el valor de Mylnteger sea 3. Si esa expresión devuelve False, haz que el valor de Mylnteger sea 5".
El orden de las operaciones C# permite colocar varios operadores en un a sola instrucción: MyVariable = 3 * 2 + 1 ;
¿Cual es el valor de MyVariable a q u í ‘) Si C# aplica la multiplicación en pri mer lugar, leerá la instrucción como "multiplica 3 por dos y luego añade 1".
127
que da como resultado un valor de 7. Si C# aplica la s uma en primer lugar, leerá la instrucción como "suma 2 y 1 y luego multiplícalo por 3". que da como r es ult a do un valor de 9. C# combi na los operadores en grupos y aplica un orden de prioridad a cada grupo Este orden de prioridad especifica qué operadores se evalúan antes que otros. La lista con el orden de prioridad de C# es la siguiente, ordenados de mayor prioridad a menor: Expresiones primarias Operadores ú n a n o s + - ! - + + — •
Op eradores multiplicativos * / %
•
Op eradores aditivos + -
•
Op eradores de despl azami ent o << >> Operadores relaciónales < > < = > = Operadores de igualdad == != A N D logico O R lógico exclusivo OR lógico A ND condicional
•
OR condicional T er nari o condicional
•
Op eradores de asignación
Repase la siguiente instrucción: MyVariable
= 3 * 2 + 1
C# da a MyVariable un valor de 7 porque la prioridad del oper ador de multiplicación es superior a la del oper ador de suma. Esto significa que el o p e r a dor de multiplicación se evalúa primero y en segundo lugar el oper ador suma. Se puede invalidar el orden de prioridad con paréntesis. Las expresiones entre paréntesis se evalúan antes de que se apliquen las reglas de prioridad de o p er a d o res: MyVariable
- 3 *
(2+1)
En este caso. C# da a MyVariable un valor de 9. porque la expresión de suma está entre paréntesis, obligando a que se evalúe antes que la operación de multiplicación.
128
Resumen C # define muchos operadores p a r a ayudarle a evaluar expresiones y a calcular nuevos valores a partir de esas operaciones. Este lenguaje permite escribir e xp re siones que realizan funciones mat emát icas y booleanas. y com pa r a dos e xp res i o nes y obtiene un resultado booleano de esa comparación. En este capitulo, se presentan los operadores de C# y se aprende a us ar estos operadores en expresiones con literales y variables. Tambi én se han revisado las expresiones de oper ador y la prioridad al usar estos operadores en expresiones. Cuando examinemos las clases en un capítulo posterior, descubri rá que sus clases pueden redefinir algunos de estos operadores. A esto se le llama s obr ecar ga de operadores y le permite redefinir la for ma en que los operadores calculan los resultados.
129
Q Cómo controlar el flujo del código
El co mp or ta mi en to del código C# a menudo depende de las condiciones que se determinan en tiempo de ejecución. Qui zás quiera escribir una aplicación que salude a sus usuarios con un mensaje de "Buenos días" si la hora en ese momento es inferior a las 12:00 P.M.. por ejemplo; o "Buenas tardes" si la hora en ese momento está entre las 12:00 P.M. v las 6:00 P.M. C ompo rt ami ent os como éste necesitan que el código C# exami ne valores en tiempo de ejecución y realice una acción b as ad a en dichos valores. C# admite varias construcciones de código que le permiten ex ami nar variables y realizar una o varias acciones b as ad as en dichas variables. En este capítulo se examinan las instrucciones de flujo de control de C# que ac tua rán como el cerebro de las aplicaciones que escriba.
Instrucciones de C# Una instrucción es una expresión válida de C# que define una acción real iza da por el código. Las instrucciones pueden exa mi na r valores de variables, a si g nar nuevos valores a una variable, llamar a métodos, realizar una operación, crear objetos o realizar al guna otra acción.
131
La instrucción más corta posible en C# es la instrucción vacía. Ésta consiste en sólo el punt o y coma:
Se puede us ar la instrucción vacía par a decir. "No hagas nada aquí". Esto podría no par ecer muy útil, pero tiene su función.
NOTA: Todas las instrucciones de C# terminan en un punto y coma. Las instrucciones se agr upan en listas de instrucciones que se componen de una o más instrucciones escritas en secuencia: int
MyVariable;
MyVariable MyVariable
- 123; += 2 3 4 ;
Por lo general, las instrucciones se escriben en su propia línea. Sin embargo. C# no exige esta disposición. C# ignora cualquier espacio en blanco entre instruc ciones y acept a cualquier disposición siempre que cada instrucción este s eparada por un punto y coma: int
MyVariable;
MyVariable
= 123;
MyVariable
+= 234 ;
Las listas de instrucciones se encierran entre llaves. Una lista de instrucciones entre llaves recibe el nombre de bloque de in s tru c cio n es. Casi siempre usará bloques de instrucciones par a escribir el código de la función. To da la lista de instrucciones de la función se coloca en un bloque de instrucciones. Es per f ec ta mente posible us ar sólo una instrucción en un bloque de instrucciones: public
static
void M a i n ()
{ System.Consolé.WriteLine ("Helio! ") ;
} C# no impone ningún límite al número de instrucciones que se pueden colocar en un bloque de instrucciones.
Instrucciones para declarar variables locales Las instrucciones de declaración declaran variables locales en el código. Ya hemos visto varios ejemplos de este tipo de instrucciones. Las instrucciones de declaración especifican un tipo y un nombre par a un variable local: int MyVariable;
132
T am bi én se puede inicializar la variable cuando se declara us ando un signo igual y a si gnando un valor a la variable: int MyVariable
= 123;
C# permite enumer ar varias variables en la misma instrucción. Para separar los nombres de las variables se usan comas. int
MyFirstVariable , MySecondVariable ;
C ad a variable de la instrucción tiene el tipo especificado. En el ejemplo a n te rior, MyFirstVariable y MySecondVar iable son de tipo int. Las declaraciones de constantes definen una variable cuyo valor no puede ca mbi a r durante la ejecución del código. Las declaraciones de constantes usan la pal abra clave de C# const y deben asi gnar un valor a la variable cuando se declara dicha variable: const
int MyVariable
= 123;
Las declaraciones de constantes permiten una mejor legibilidad y ad mi n is tr a ción del código. Se pueden tener valores constantes en el código y al asignarles nombres se consigue que el código resulte más legible que si u sar a su valor. Además, si se usan valores por todo el código y luego se necesita cambiarlos, esta será una tarea muy pesada. Si se usa una constante, sólo har á falta cambi ar una línea de código. Por ejemplo, suponga que está escribiendo un código par a una aplicación que realiza medidas geométricas. Uno de los valores con los que querrá t rab aj ar es pi. la relación entre la circunferencia de un círculo y su diámetro. Sin una dec la ra ción de constante, tendría que escribir un código de la siguiente forma: Area = 3.14159
* Radius
* Radius;
Al us ar una constante se logra que el código sea un poco más sencillo de entender: const double
Pi = 3.14159;
Area = Pi * Radius
* Radius;
Esto es especialmente útil si us a m uchas veces en el código el valor de pi.
Cómo usar instrucciones de selección para seleccionar la ruta del código Las instrucciones de selección seleccionan una de las muchas rutas posibles p a r a que se ejecute el código. La ruta de código seleccionado se b a sa en el valor de u na expresión.
133
La instrucción if La instrucción i f t raba ja con una expresión que devuelve un valor booleano. Si la expresión booleana resulta ser true. la instrucción incrustada en la i nst ruc ción if se ejecuta. Si la expresión booleana resulta ser false. la instrucción i ncrustada en la instrucción i f no se ejecuta: if(MyVariable =- 123) S y s t e m . C o n s o l é . W r i t e L m e ("MyVariable's valué is 123.");
La instrucción booleana se escribe entre paréntesis. La instrucción incrustada sigue a los paréntesis. Se usa un punto y coma par a cerrar la instrucción i ncrus tada. pero no la expresión booleana.
NOTA: Cuando se usa la instrucción i f para comprobar una igualdad, siempre se deben usar dos signos igual. Dos signos igual hacen una com probación de igualdad, mientras que un signo igual realiza una asignación. Si se usa accidentalmente un signo igual dentro de una instrucción i f , ésta siempre devolverá un valor t r u e .
En el anterior ejemplo, el valor de M y V a r i a b l e se compar a con el valor literal 123. Si el \ a l o r es igual a 123. la expresión devuelve t r u e v se escribe el mensaje M y V a r i a b : e ' s v a l ú e i s 12 3 . en la consola. Si el \ a l o r no es igual a 123. la expresión devuelve f a l s e y no se escribe nada. La instrucción 1 f puede ir seguida de una clausula e l s e . La palabra clave e l s e va seguida de una instrucción incrustada que se ejecuta si la expresión booleana usada en la cláusula i f devuelve f a l s e : if(MyVariable == 123) System.Console.WriteLine("MyVariable's else Sys te m. Co ns ol e.Writ eL in e("MyVariable's
value
is
123.");
value
is
not
123.");
En el anterior ejemplo, el valor de M y V a r i a b l e se c ompar a con el valor literal 123. Si el valor es igual a 123. la expresión devuelve t r u e y se escribe el mensaje M y V a r i a b l e ' s v a l u e i s 1 2 3 . en la consola. Si el valor no es i g u a l a 123. la e x p r e s i ó n d e v u e l v e f a l s e y se e s c r i b e el m e n s a j e M y V a r i a b l e ' s v a l u e i s n o t 1 2 3 . en la consola. La cláusula e l s e puede ir seguida por su propia cláusula i f : if(MyVariable == 123) System.Console.WriteLine ("MyVa riable's value else if (M yV ariable == 124) System.Console.W r i t e L i n e ("MyVariable's value else System.Console.WriteLine("MyVariable's value
134
is
12 3.");
is
12 4.");
is
not
123.");
Las cláusulas if y else permiten asociar una instrucción a la cláusula. Por lo general. C# permite asociar sólo una instrucción a la cláusula, como se ve en el siguiente código: i f (MyVariable == 123) System.C o n s o l e .W r i t e L i n e ("MyVariable's value is System. Console . W r i t e L m e ("This always prints.");
123.");
La instrucción que escribe This always prints, en la consola siempre se ejecuta. No pertenece a la cláusula if y se ejecuta i ndependientemente de si el valor de MyVariable es 123. La única instrucción que depende de la c o m p a r a ción de MyVariable con 123 es la instrucción que escribe MyVariable' s value is 12 3 . en la consola. Si se quiere asociar varias instrucciones con una cláusula if. se debe usar un bloque de instrucciones: if (MyVariable
==
123)
{ S ys t e m .Cons ol e . W r i t e L m e ("MyVar íable's value is 12 3."); System.C o n s o l e .W r i t e L i n e ("This prints if MyVariable == 123 .") ;
} Tambi én se pueden usar bloques de instrucciones en las cl áusul as else: if (MyVariable
==
123)
{ System.Cons o l e .W r i t eLine ("MyVariable' s value is 123."); System.C o n s o l e . W r i t e L m e ("This prints if MyVariable == 12 3.") ;
} else
{ System. Console. W r i t e L m e ("MyVar i able's value is not System.C o n s o l e . W ri te Li ne ("This prints if MyVariable 123 .") ;
123."); !=
} C omo los bloques de instrucciones pueden contener una sola instrucción, el siguiente código también es valido: i f (MyVariable
= = 123)
( System. Console . W r i t e L m e ("MyVar iable ' s value
is
123.");
}
La instrucción switch La instrucción switch evalúa una expresión y c omp ar a el valor de esa e x presión con varios casos. Ca da caso se asocia con una lista de instrucciones, que recibe el n omb re de sección de switch. C# ejecuta la lista de instrucción a sociada con la sección de sw itch que concuerde con el val or de la expresión.
135
La expresión usada como controlador de la instrucción switch se encierra entre los paréntesis que siguen a la pal abra clave switch. La expresión va seguida por llaves y las secciones de sw itch están entre las llaves. switch ÍMyVariable)
{ // aquí
se colocan
las secciones
de switch
) La expresión usada en la instrucción switch debe evaluar uno de los siguien tes tipos:
•
sb y t e
•
b y te
•
sh o rt
•
ushort int
•
uint long
•
ulong
•
ch a r s t r in g
Tambi én se puede usar una expresión cuyo valor pueda ser convertido implíci tamente a uno de los tipos de la lista anterior. Las secciones de sw itch empiezan con la pal abr a clave de C# case, seguida de una expresión constante. A esa expresión constante le siguen dos puntos y a continuación escribimos la lista de instrucciones: switch (M yV a ri a b 1e )
{ case 123: System.Console.WriteLine("MyVariable break;
==
123");
) C# evalúa la expresión en la instrucción switch y luego bu sca un bloque switch cuya expresión constante concuerde con el valor de la expresión. Si C# puede encontr ar un valor similar en una de las secciones de switch. la lista de instrucciones de la sección ác sw itch se ejecuta. Una instrucción switch puede incluir muchas secciones de switch. cada una con un caso diferente: switch ÍMyVariable)
(
136
case 12 3: System.Cons ol é. Wr it eL ín e("MyVariable break; case 12 4: Sy st em .C on solé.WriteLíne("MyVariable break; case 12 5: Sy st em .C on solé.WriteLíne("MyVariable break;
==
123");
==
124");
==
125");
} C# permite ag r up ar varias etiquetas de caso juntas. Si se tiene más de un caso que necesite ejecutar la mi sma lista de instrucciones, se pueden combi nar las etiquetas de caso. switch(MyVariable)
{ case 123: case 12 4: System.Con so lé .W ri te Lí ne ("MyVariable break; case 12 5: S ys tem .C on solé.WriteLíne("MyVariable
==
123
or
==
125");
124");
break; } Una de las etiquetas de caso puede ser la p al abr a clave de C# d e f a u l t . La etiqueta d e f a u l t puede incluir su p ropi a lista de instrucciones: s wi t c h ( M y V a r i a b l e )
{ case
123:
S ys tem .C on solé.WriteLine("MyVariable
==
123");
!=
123");
break; de f a u l t : S ys te m.C on solé.WriteLíne("MyVariable
break; } La lista de instrucciones d e f a u l t se ejecuta cuando ninguna de las otras sec ciones de switch define alguna constante que concuerde con la expresión s w i t c h . La lista de instrucciones d e f a u l t es la parte que dice "Si no puedes encontrar algún bloque switch que concuerde, ejecuta este código por defecto". El uso de la palabra clave d e f a u l t es opcional en sus instrucciones de s w i t c h .
Cómo usar instrucciones de iteración para ejecutar instrucciones incrustadas Las instrucciones de iteración ejecutan instrucciones incrustadas varias ve ces. La expresión as oc ia da con la instrucción de iteración controla el número de veces que se ejecuta una instrucción incrustada.
137
La instrucción while La instrucción while ejecuta una lista de instrucciones incrustada siempre que la expresión while resulte ser true. La expresión booleana que controla la i nstrucción while se encierra entre los paréntesis que siguen a la pal abr a clave while. T ra s los paréntesis situamos las instrucciones que se ejecutarán si la expresión bool eana es true: int MyVariable
= 0;
while ( M y V a n a b l e
<
10)
{ Syste m. Console . W r i t e L m e (My Va r i abl e ) ; M y Va riable + +;
} El código escribe en la consola: 0 1
4 5
6 7 8
5
El código incrustado en la instrucción whi le cont inúa ejecutándose siempre que el valor de MyVariable sea menor que 10. Las instrucciones incrustadas escriben el valor de MyVariable en la consola y luego incrementan su valor. Cuando el valor de MyVariable alcanza 10, la expresión booleana MyVariable < 10 devuelve false y la lista de instrucciones i ncrust ada en la instrucción whi le deja de ejecutarse. La instrucción que sigue a la instrucción whi le se ejecuta en cuanto la e xpr e sión bool eana de la instrucción while devuelve false.
La instrucción do La instrucción whi le ejecuta sus instrucciones incrustadas cero o más veces. Si la expresión booleana u sad a en la expresión while devuelve false. ninguna de las instrucciones i ncrustadas se ejecuta: int MyVariable
= 100;
while(MyVariable
<
10)
{ System.Consolé . W n t e L m e (MyV a r i abl e ) ; MyVa ri a b 1e + + ;
)
138
Este código no escribe n ad a en la consola porque la expresión bool eana us ada en la instrucción while, M y V a r i a b l e < 1 0. devuelve f a l s e la pri mera vez que se ejecuta. Como la expresión bool eana devuelve f a l s e inmediatamente, las instrucciones i ncrust adas n un ca se ejecutan. Si quiere as egurars e de que las ins trucciones i ncrust adas se ejecuten al menos una vez. puede us ar la instrucción d o . La instrucción d o va seguida de instrucciones incrustadas, que a su vez van seguidas de la p al ab ra clave w h i l e . Tr as ella va la expresión bool eana que cont rol a el número de veces que se ejecuta el bucle. int MyVariable = 0; do
{ S ys te m. Co ns ol é. Wr i t e L i n e ( M y V a r i a b l e ) ; MyVariable++;
} while(MyVariable
<
10);
Este código escribe lo siguiente en la consola: 0 1 2
3
4 5
6
7 8
9
Las sentencias incrust adas siempre se ejecutan al menos una vez debido a que la expresión b ool eana se evalúa después de que se ejecuten las instrucciones in cr us tadas , como se puede ver en el siguiente código: int MyVariable
= 100;
do
{ System.Consolé.WriteLine (MyVariable) ; MyVariable++;
} while(MyVariable
<
10);
Este código escribe lo siguiente en la consola: 100
La instrucción for La instrucción f o r es la instrucción de iteración más potente. El código de control de una instrucción f o r se divide en tres partes.
139
Un in icia d o r. que fija las condiciones iniciales de la instrucción de bucle
fo r . Una condición, que especifica la expresión bool eana que mantiene ej ecu t ándose la instrucción for. Un iterador, que especifica las instrucciones que se ejecutan al final de cada paso por las instrucciones incrustadas. La instrucción for empieza con la palabra clave for. seguida por paréntesis, que contienen las instrucciones iniciadora, de condición y de iteración, todas sepa radas por puntos y coma. Las instrucciones incrustadas siguen a los paréntesis. Obser ve el siguiente bucle simple for: int MyVar ia bl e; íor(MyVariable
= 0; MyVariable
< 10; M yV ar i a b 1e++)
{ System. Consolé . W n t e L m e (MyVar iable ) ;
} El iniciador en este bucle for es la instrucción MyVariable = 0. El iniciador sólo se ejecuta una vez en un bucle for y se ejecuta antes de que las instrucciones i ncrustadas se ejecuten por primera vez. La condicion de este bucle for es la instrucción MyVariable < 10. La condición en un bucle for debe ser una expresión booleana. Las instrucciones incrustadas de un bucle for se ejecutan mientras esta expresión booleana devuel ve true. Cuand o la expresión devuelve false. las instrucciones incrustadas dejan de ejecutarse. El iterador de este bucle for es la instrucción MyVariable++. El iterador se ejecuta después de cada paso por las instrucciones incrustadas del bucle for. Si se pone toda esta información junta, se podrá interpretar la instrucción como: "Fija el valor de M y V a r i a b l e igual a cero. Mi ent ras el val or de MyVar iabl e sea menor de 10. escribe el valor en la consola y luego a ument a el valor de MyVar iable". Estas instrucciones escriben lo siguiente en la consola: 0 1 } 4
El iniciador, la condicion y el iterador de un bucle for son opcionales. Si prefiere no usar alguna de estas partes, simplemente escriba un punto y coma sin
140
especificar la instrucción. El siguiente código es. en buena lógica, equivalente al código anterior: int M y V a r i a b l e = 0; f o r (/ M y V a r i a b l e
<
10;
MyVariable++)
{ S y s t e m . C o n s o l é . W r i t e L i n e ( M y V a r i a b l e );
} Este código t ambi én es equivalente al código original: int
MyVariable;
for (MyVariable
=
0;
MyVariable
<
10;
)
{ S y s t e m . C o n s o l é . W r i t e L i n e ( M y V a r i a b l e ); M y V a ri able + + ;
} Hay que tener cui dado cuando se omita la parte de la condición en un bucle
for. El siguiente código es un ejemplo de los probl emas que pueden surgir si no se incluy en condiciones: int
MyVariable;
for(MyVariable
=
0;
; MyVariable++)
{ System.C o n s o l é . W r i t e L i n e (MyVaria b l e ) ;
} Este código se ejecuta hasta que MyVariable finalmente provoc a un error porque contiene un número demasi ado largo par a ser almacenado. Esto ocurre porque ninguna condición del bucle for llega a devolver false. lo que permite a la v ariable au men ta r hasta super ar su límite. Las condiciones que faltan devuel ven true en un bucle for. C om o la condición en el código ant er ior de ejemplo siempre es true. nunca devuelve false y la instrucción for nunca deja de ejecutar su instrucción incrustada. Las expresiones iniciadoras, de condición y de iteración pueden contener v a rias instrucciones, s eparadas por comas. El siguiente código es válido: int int
MyFirstVaríable ; MySecondVariable;
f o r ( M y F i r s t V a r i a b l e = 0, M y S e c o n d V a r i a b l e M y F i r s t V a r í a b l e < 10; M y F i r s t V a r i a b l e + + , M y S e c o n d V a r i a b l e + +)
=
0;
{ System.Consolé.WriteLine(MyFirstVariable); S y s t e m .C o n s o l e .W r i t e L i n e ( M y S e c o n d V a r i a b l e ) ;
}
141
La instrucción foreach Se puede us ar la instrucción foreach par a repetir varias veces los elemen tos de una colección. Las matrices de C # admiten la instrucción foreach v pueden usarse par a t rab aj ar fácilmente con cada elemento de la matriz. La instrucción foreach se usa escribiendo la pal abr a clave foreach se guida de paréntesis. Estos paréntesis deben contener la siguiente información: El tipo del elemento de la colección. Un nombre identificador p ar a un elemento de la colección. La pal abr a clave in. •
El identificador de la colección.
Tras los paréntesis se colocan las instrucciones incrustadas. El listado 5.1 muestra la instrucción foreach en acción. Crea una matriz entera de cinco elementos y luego usa la instrucción foreach para acudir a cada elemento de la matriz y escribir su valor en la consola. Listado 5.1. Usando la instrucción f o r e a c h c la s s
L is tm g 5 ^ 1
{ p u b lic
s ta tic
v o id
M a i n ()
{ in t
[]
M yA rra y;
M y A r r a y = new i n t M y A r r a y [0 ] = 0; M y A r r a y [ 1 ] = 1; M y A r r a y [ 2 ] = 2; M y A r r a y [ 3 ] = 3; M y A r r a y [ 4 ] = 4;
[5] ;
f o r e a c h ( i n t A rra y E le m e n t m M yA rray) S y s te m . C o n s o l é . W r i t e L m e ( A r r a y E l e m e n t ) ;
} } El i dent ifi cador A r r a y E l e m e n t es una v ar ia b le defini da en el bucle foreach. Contiene el valor de un elemento de la matriz. El bucle foreach recorre cada elemento de la matriz, lo que es muy útil cuando se necesita t rab aj ar con cada elemento de una matriz sin tener que conocer el t amaño de la misma.
Instrucciones de salto para moverse por el código Las instrucciones de salto saltan hacia una parte específica del código. S ie m pre se ejecutan y no están controladas por ninguna expresión booleana.
142
La instrucción break Ya vio la i n s t r uc ci ó n break en la sección dedicada a las instrucciones switch. C# también permite u s ar la instrucción break para salir del bloque de i ns trucciones en el que se encuentre. Normalmente, la instrucción break se usa p ar a salir de un bloque de instrucciones iterativas: int M y V a n a b l e = 0; while(MyVariable
<
10)
{ System.Consolé.WriteLine(MyVar i a b l e ) ; if(MyVariable == 5)
break; MyVariable++;
} System.Con so lé .W ri te Li ne ("Out
of
the
loop.");
El código anterior escribe lo siguiente en la consola: 0 1 2
3 4 Out of the
loop.
El código se interpreta: "Si el valor de MyVariable es 5. sal del bucle while". Cuand o se ejecuta la instrucción break. C# transfiere el control a la instrucción que sigue a las instrucciones incrustadas de la instrucción de itera ción. La instrucción break suele us ar se con bloques de instrucciones switch. while. do, for y foreach.
La instrucción continué La instrucción continué devuelve el control a la expresión bool eana que controla un a instrucción de iteración, como se puede ver en el siguiente código: int MyVariable; for (MyVariable
= 0; MyVariable
<
10; MyVariable + + )
{ if (MyVariable == 5) continué; System. C on solé.WriteLine(MyVariable ) ;
} El código anterior escribe lo siguiente en la consola: 0 1
143
3 4 6 7 8
5 Este código interpreta: "Si el valor de MyVariable es 5. continúa hasta la siguiente iteración del bucle for sin ejecutar ninguna otra instrucción i ncr us t a da". Por eso no aparece el 5 en pantalla. Cuand o el valor de MyVariable es 5. el control regresa a la parte superior del bucle for y la l lamada a WriteLine () nunca se produce en esa iteración del bucle for. Al igual que la instrucción break. la instrucción continué suele usarse en los bloques de instrucciones switch. while, do. for y foreach.
La instrucción goto La instrucción goto t ransfiere sin condiciones el control a una instrucción etiquetada. Puede etiquetarse cualquier instrucción de C#. Las etiquetas de ins trucciones son identificadores que preceden a una instrucción. Después de una etiqueta de instrucción se colocan dos puntos. Un identifícador de etiqueta sigue a la p al abr a clave goto y la instrucción goto transfiere el control a la instrucción desi gnada por el identifícador de etiqueta, como mues tra el siguiente código: int MyVariable
= 0;
w h 1 1e (MyVariable
<
10)
f System.Consolé . W r i t e L m e (MyVa r i ab 1 e ) ; i f( MyVariab1e == 5) goto Done; MyVa riable + +;
} Done:
System.Consolé.WriteLine ("Out
of
the
loop".);
El código anterior escribe lo siguiente en la consola: 0 1 2 4 Out
of the
loop.
Cuando el valor de MyVariable es 5. la instrucción goto se ejecuta v transfiere el control a la instrucción con la etiqueta Done. La instrucción goto siempre se ejecuta, independientemente de la instrucción de iteración que pueda estar ejecutándose. Tambi én se puede u sar la p al ab ra clave goto en conjunción con las etiquetas de caso en un a instrucción switch. en lugar de una instrucción break:
144
switch(MyVariable)
{ case 12 3 : System.Cons ol e. Wr it eL in e("MyVariable goto case 12 4; case 12 4 : System.Cons ol e. Wr it eL in e("MyVariable
==
123");
==
124");
break; }
NOTA: Usar la instrucción g o to en muchos sitios puede hacer el código confuso @ilegible. Lo mejor es evitar usar una instrucción g o to siempre que sea posible. Intente reestructurar el código para no tener que recurrir al uso de una instnícció» g o to .
Cómo usar instrucciones para realizar cálculos matemáticos con seguridad Y a ha visto cómo las pal abr as clave checked y unchecked permiten c on t rol ar el compo rt ami ent o de las condiciones de error en sus expresiones m a t e m á ticas. Tambi én se pueden us ar estas pa la br a s clave como instrucciones par a controlar la seguridad de sus operaciones matemáticas. Use las pal abr as clave antes de un bloque de instrucciones al que afecte la p a l ab r a reservada checked o unchecked. como en el siguiente código: checked
{ Intl = 2000000000; Int2 = 2000000000; IntlPlusInt2 = Intl + In 12; S y s t e m . C o n s o l é . W ri te Li ne (I nt lP lu s In t2 );
}
Resumen C# dispone de varios medios de cont rol ar la ejecución del código, dándole opciones p ar a ejecutar un bloque de código más de una vez o. a veces, ninguna vez. b as ándos e en el resultado de una expresión booleana. La instrucción i f ejecuta el código una vez. pero sólo si la expresión booleana que la a c o mp a ñ a devuelve true. La instrucción if puede incluir una cláusula else. que ejecuta un bloque de código si la expresión booleana devuelve false. La instrucción switch ejecuta uno de los muchos bloques de código pos i bles. C ad a bloque de código viene precedido de una lista de instrucciones de caso.
145
C# evalúa la expresión de la instrucción switch y a continuación busca una lista de instrucciones de caso cuyo valor coincida con la expresión e valuada en la instrucción switch. Las instrucciones while. do y for continúan ejecutando el código mientras la expresión booleana indicada sea t rue . C uan do la expresión booleana d evuel ve false. las instrucciones incrustadas dejan de ejecutarse. Las instrucciones while y for se pueden definir p ar a que sus expresiones booleanas devuelvan i nmediatamente false. lo que quiere decir que sus instrucciones incrustadas nunca llegan a ejecutarse realmente. La instrucción do, sin embargo, siempre ejecuta sus instrucciones incrustadas al menos una vez. La instrucción foreach pr opor ci ona un buen modo de recorrer repetida y rápidamente los elementos de una matriz. Puede ordenar a un bucle foreach que recorra repetidamente los elementos de una mat ri z sin conocer el t amaño de la matriz o los elementos que la forman. La instrucción foreach prepara un i dentificador especial formado por el valor del elemento de una matriz durante cada iteración del bucle foreach. Las instrucciones break. continué y goto afectan al flujo normal de una instrucción de iteración, como while o foreach. La instrucción break sale del bucle iterativo, incluso si la expresión bool eana que controla la ejecución del bucle sigue devolviendo true. La instrucción continué devuelve el control a la parte superior del bucle iterativo sin ejecutar ni nguna de las instrucciones incrustadas que la siguen. La instrucción goto siempre transfiere el control a la instrucción etiquetada. Puede a c o m pa ñ ar sus operaciones mat emát icas con instrucciones checked o unchecked para especificar cómo quiere trat ar los errores mat emát icos del código C#.
146
Cómo trabajar con métodos 151
Los métodos son bloques de instrucciones que devuelven algún tipo de valor cuando se ejecutan. Pueden ser llamados mediante el nombre y llamar a un mé to do hace que las instrucciones del método se ejecuten. Ya hemos visto un método: el método Main ( ) . Aunque C# permite poner todo el código en el método Main (). probablemente quiera diseñar sus clases p ar a definir más de un método. El uso de métodos mantiene el código legible porque las instrucciones se colocan en bloques más pequeños, en lugar de en un gran bloque de código. Los métodos también permiten t omar instrucciones que pueden ser ejecutadas varias veces y colocarlas en un bloque de código que puede ser llamado todas las veces que haga falta. En este capítulo, apr ender á a crear funciones que devuelven datos y que no los devuelven. Aprenderá a p as ar par ámet ro s a los métodos y la mejor maner a de es truc tur ar un método par a hacer sus aplicaciones modulares.
La estructura de un método Como mínimo, un método está compues to de las siguientes partes: Ti po dev uelto Nom br e del método
149
Lista de parámetros C u er po del método
NOTA: Todos los métodos se encierran en úna clase. Un método no pue de existir fuei# una clase. Los métodos tienen otras partes opcionales, como las listas de atributos v los modificadores de ámbito. Las siguientes secciones analizan los fundament os de un método.
Tipo devuelto Un método comi enz a definiendo el tipo de datos que devolverá cu an do se le llame. Por ejemplo, s uponga que quiere escribir un método que s uma dos números enteros y devuelve el resultado. En esc caso, escribirá el tipo devuelto como int. C# permite escribir un método que no devuelve nada. Por ejemplo, puede e s cribir un método que simplemente escri ba algún texto en la consola, pero que no calcule ningún dato que deba devolver al código que llamó al método. En ese caso, se puede usar la p al abr a clave void p ar a indicar al compilador de C# que el método no devuelve ningún dato. Si se quiere devolver un valor de un método se us a la p al ab r a clave return p a r a especificar el valor que debe devolverse. La p a l ab r a clave va seguida de una expresión que evalúa el tipo de valor que debe devolverse. Esta e xpresión puede ser un valor literal, una variable o una expresión más compleja.
Nombre del método Todos los métodos deben tener un nombre. Un n ombre de método es un i dentificador y los nombres de método deben seguir las reglas de nomencl atur a de cualquier identificador. Recuerde que los identificadores deben e mpez ar con una letra mayú scu la o minúscul a o con un carácter subrayado. Los caracteres que siguen al primer carácter pueden ser una letra m ayús cul a o minúscula, un número o un subrayado.
Lista de parámetros Se puede llamar a métodos con los parámetros que se usan par a pas ar los datos al método. En el ejemplo anterior, en el que un método s uma dos números enteros, se necesitaría enviar al método los valores de los dos números enteros que se van a sumar. La lista de variables recibe el nombre de lista de parám etro s del método. La lista de par ámet ros del método a parece entre paréntesis y sigue al nombre del
150
método. Cad a par ámet ro de la lista de par ámet ros está separado por u na coma e incluye el tipo del p a rá me tr o seguido p or su nombre. Tambi én se puede prefijar los p ar ámet ro s de la lista de p ar ámet ros con modi ficadores que especifican cómo se usan sus valores dentro del método. Veremos estos modificadores más adelante en este mi smo capítulo. Se pueden definir métodos que no reciben ningún parámetro. Si se quiere utili zar uno de estos métodos, ba st a con dejar vacíos los paréntesis. Y a hemos visto esto en los mét odos M a i n ( ) escritos. Tambi én se puede colocar la p al abr a clave v o i d entre los paréntesis p a r a especificar que el método no acept a parámetros.
Cuerpo del método El cuerpo del mét odo es el bl oque de i nstrucciones que compone el código del método. El cuerpo del método está entre llaves. La llave de a pe r tu ra se incluye tras la lista de par ámet ros del método y la llave de cierre se coloca detrás de la última instrucción del cuerpo del método.
Cómo llamar a un método P a ra llamar a un método, se escribe su nombre en el lugar donde debería ej ecut arse el código de ese método. Después del n ombre del método se escriben dos paréntesis, como se muest ra en el listado 6.1. Como en todas las instrucciones de C#, la instrucción de l lamada al método debe t ermi nar con un punt o y coma. Listad o 6.1. Llamar a un método simple class Listing6_l
{ public
static
void Main()
{ Listing6
1 MyObject;
MyObject = new L i s t i n g 6 _ l () ; M y O b j e c t .CallMethod () ;
} void
C a l l M e t h o d ()
{ System.Consolé.WriteLine("Helio
f rom
C a l l M e t h o d () !" ) ;
} }
NOTA: Necesita las instrucciones de Main () que crean uñ nuevo objeto L i s t i h g 6 _ l antes de poder llamar a ios métodos del objeto.
151
Si el método se define con una lista de par ámet ros, sus valores deben ser especificados en el momento de llamar al método. Debe especificar los parámetros en el mi smo orden en que son especificados en la lista de p ar ámet ro s del método, como m ues tra el listado 6.2. Listado 6.2. L l a m a d a a un m é t o d o c o n un p a r á m e t r o class
Listíng6 2
I public
static
void M a i n ()
{ int Mylnteger; Listingt 2 MyObject; MyOb^ect = new L i s t m g c 2 () ; M y O b j e c t .C a l l M e t h o d (2); Mylnteger = 3 ; MyOb ject.CallMethod(Mylnteger) ;
} void
C a l l M e t h o d (int
Integer)
{ System.Consolé.WriteLine (Integer) ;
} } C ua nd o compile y ejecute el listado 6.2 obtendrá un resultado igual al de la figura 6.1. ca C:\WirHDOWS\System32\cmd!eHe~^^^^^ C : \ > L j ; ; t iriyf)
X
2.exe
2
3 C:\>
Figura 6.1. U n a s i m p l e l l a m a d a a un m é t o d o d e v u e l v e e s t e r e s u l t a d o .
Esto es debido a que el método Main ( ) llama a CallMethod ( ) dos veces: una con el valor 2 y otra con el valor 3. El cuerpo del método CallMethod () escribe el valor sumi nist rado en la consola.
152
Cuand o sumi nist ramos un valor par a el par ámet ro de un método, podemos usar un valor literal, como el 2 del listado 6.2 o suministrar una variable y usar su valor, como en la variable Mylnteger del listado 6.2. Cuand o se llama a un método. C# toma el valor especificado y asigna esos v alores a los par ámet ros que se usan en el método. Durante la pri mera llamada a CallMethod ( ) en el listado 6.2. el literal 2 se usa como el par ámet ro del mét odo y el p ar á me tr o Integer del método recibe el valor 2. Durant e la s eg un da llamada a CallMethod ( ) en el listado 6.2. la variable Mylnteger se usa como el par ámet ro del método y el par ámet ro Integer del método recibe el valor de la variable Mylnteger: 3. Los parámet ros que se especifican cuando se llama a un método deben c on cor dar con los tipos especificados en la lista de parámetros. Si un par ámet ro de la lista de parámetros del método especifica un tipo int. por ejemplo, los parámetros que le pase deberán ser de tipo int o de un tipo que pueda ser convertido a int. Cual qui er otro tipo produce un error al compi lar el código. C# es un lenguaje de tipo seguro, lo que significa que se c om p ru e ba la legali dad de los tipos de variables al compilar el código C#. Por lo que respecta a los métodos, esto significa que deben especificarse los tipos correctos cuando se especifican parámetros. El listado 6.3 muestra los tipos correctos durante la especificación de parámetros: L istado 6.3. S eguridad de tipos en listas de parámetros de método class Listing6 3
{ public
static
void Main()
{ Listmgc
3 MyObject;
MyObject = new L i s t m g 6 _ 3 () ; MyObject.CallMethod("a s tr in g" );
} void
C a l l M e t h o d (int
Integer)
{ S y s t e m . C o n s o l é .W ri te Li ne (I nt eg er );
} } Este código no se compila, como puede verse en la figura 6.2. El compi lador de C# emite estos errores porque el método CallMethod ( ) se es tá e j e c u t a n d o con un p a r á m e t r o de c a d e n a y la lista de p a r á m e t r o s CallMethod ( ) especifica que se debe us ar un número entero como parámetro. Las cadenas no son números enteros ni pueden ser conv ertidas a números enteros y esta discordancia hace que el compi lador de C# genere errores. Si el método devuelve un valor, debe declararse una variable que contenga el valor devuelto. La variable u sada par a esta operación se coloca antes del nombre
153
del método y un signo igual s epa ra el identifieador de la variable y el nombre del método, como se ve en el listado 6.4. C:\WINDOWS\System32\cmd.exe
HY,
.=JnjxJ
C : \ k s c Ciassl.cs Compilador de Microsoft Uisual GB -NET versión V.00.9466 para Microsoft Microsoft Microsoft Corporation 2001. Reservados todos los derechos. C l a s s i .o s <8,3>: error CS1502: La mejor coincidencia de método sobrecargado para 'Listing6__3 .CallMetliodCintV tiene algunos argumentos no válidos Classi -e s <8„23>: error CS1503: Argumento '1': no se puede convertir de ’string' a ' int '
Figura 6.2. La llamada a un método con un tipo de datos no válido produce errores de compilación.
Listado 6.4. Devolución de un valor de un método c 1 ass Listingt 4 f public static void Main ()
( L i s t m g t 4 MyOb^ect; int ReturnValue; MyObject = new L i s t m g c _ 4 () ; ReturnValue -= My Ob ] e c t . A d d l n t e g e r s (3, 5); System. Con solé. W r i t e L m e (Re t ur nVa lúe ) ;
) int Addlntegers(int
Integerl,
int
Integer2)
{ int
Sum;
Sum = Integerl return Sum;
+ Integer2;
} } En este código suceden varias cosas: Se declara un método llamado Addlntegers ( ) . El método tiene dos parámetros en su lista de parámetros: un numero entero llamado Integerl y otro numero entero llamado Integer2.
154
•
El cuerpo del método Addlntegers () su ma los valores de los dos parámet ros y asigna el resultado a una variable local llamada Sum. Se devuelve el val or de Sum. El mét odo Main ( ) llama al método AddIntegers ( ) con los valores 3 y 5. El valor devuelto del método Addlntegers () se coloca en una variable local llamada ReturnValue.
•
El valor de ReturnValue se escribe en la consola.
La figura 6.3 contiene los resultados del progr ama que aparece en el listado 6.4. ca
C:\WINDOWS\System32\cmd.exe
LzJ Figura 6.3. Los datos se devuelven de un método y se muestran en la ventana de la consola
Tipos de parámetros C# permite cuatro tipos de par ámet ros en una lista de parámetros: Parámet ros de entrada Parámetros de salida Parámet ros de referencia Matrices de parámet ros
Parámetros de entrada Los p a r á m etro s de entrada son par ámet ros cuyo valor es enviado al método. Todos los par ámet ros que se han usado hasta ahor a han sido par ámet ros de entra-
155
da. Los valores de estos par ámet ros de entrada se envían a la función, pero el cuerpo del método no puede c a mb i ar per manent ement e sus valores. El listado 6.4 del anterior ejemplo define un método con dos p ar ámet ro s de entrada: Integerl y Integer2. Los valores de estos par ámet ros se i nt rodu cen en el método, que lee sus valores y hace su trabajo. Los parámetros de entrada se pasan a los métodos por valor. Básicamente, el método ve una copia del valor del parámetro, pero no se le permite ca mb ia r el valor propor ci onado por la parte que realiza la llamada. En el listado 6.5 se puede ver un ejemplo. Listad o 6.5. Cómo modificar copias de parámetros de entrada c 1a s s Listingó 5
{ public
static
void M a i n ()
{ int Mylnteger; L i s t m g c 5 MyObject; MyOb j ect = new L i s t m g o 5 () ; Mylnteger = 3 ; MyObject.CallMethod(Mylnteger); Sy st em .C on so lé . W r i t e L i n e ( M y l n t e g e r ) ;
} voi d C a l l M e t h o d (int
Integerl)
i Integerl
= 6;
System.Consolé.WriteLine (Integerl) ;
En el listado 6.5. el método Main ( ) establece una variable entera llamada Mylnteger y le asigna el valor de 3. A continuación llama a MyMethod () con Mylnteger como parámet ro. El método CallMethod ( ) establece el v a lor del par ámet ro en 6 y luego escribe el v alor en la consola. Cuand o el método Ca 1 1Method () se devuelve, el método Main () continua y escribe el valor de Mylnteger. Si ejecuta este código, el resultado debería parecerse al de la fi gu ra 6.4. Este resultado se produce porque el método CallMethod ( ) modifica su copia del par ámet ro de entrada, pero esa modificación no afecta al valor del método original propor ci onado por Main ( ) . El valor de Mylnteger sigue siendo 3 después de que regrese el método CallMethod () . debido a que CallMethod ( ) no puede ca mb ia r el valor del p ar ámet ro de entrada del ele mento que hace la llamada. Sólo puede ca mbi ar el valor de su copia del valor.
Parámetros de salida Los parám etros de sa/ida son parámetros cuyos v alores no se establecen cuando se llama al método. En su lugar, el método establece los v alores y los devuelv e al
156
elemento que hace la l lamada mediante el p ar á me tr o de salida. Suponga, por ejemplo, que quiere escribir un método que cuente el número de registros de una t abl a de una base de datos. S uponga que también quiere especificar si la o p er a ción se realizó satisfactoriamente. (La operación puede no realizarse si, por ejem plo, la t abl a de bases de datos no está disponible.) Por tanto, queremos que el método devuelva dos instancias de información: Un cont ador de registros. Un indicador de éxito de operación. X
Figura 6.4. Demostración de parámetros de entrada con la función C a l l M e t h o d ().
C# sólo permite a los métodos devolver un valor. ¿,Qué hacer si queremos que devuelva dos instancias de i n f or ma c ió n9 La respuest a está en el concepto de p ar ámet ro de salida. Puede hacer que su mét odo devuelva el indicador de éxito de la oper aci ón como un valor booleano y especificar el recuento de registros como un par ámet ro de salida. El método a l m a cena el recuento de registros en una variable de salida, cuyo valor es recogido por el elemento que hizo la llamada. Los par ámet ros de salida se especifican en listas de par ámet ros con la p al abr a clave o u t . La p al ab ra clave o u t debe preceder al tipo de par ámet ro en la lista de parámetros. C uand o se llama a un método con un p ar ám et ro de salida, se debe dec la rar una variable que contenga ese valor, como se ve en el listado 6.6. Listad o 6.6. Cómo trabajar con parámetros de salida c la s s
L is tin g 6 ^ 6
{ p u b lic
s ta tic
v o id
M a i n ()
{ in t
M y ln te g e r;
157
Listmg6
6 MyObject;
MyObject - new L i s t i n g 6 _ 6 (); MyOb j ect.CallMethod (out Mylnteger) ; Sy s t e m . C o n s o l e .WriteLine (Mylnteger);
} voici CallMethod (out
int
Integerl)
( Integerl
= 7;
} ) El listado 6.6 define un método llamado CallMethod () . que define un p ar ám et r o de salida entero llamado Integerl. El cuerpo de! método establece el valor del parámet ro de salida en 7. El método Main ( ) declara un entero llamado M y l n t e g e r y lo us a como p a r á m e t r o de sal ida p a r a el mét odo C a l l M e t h o d ( ) . C u a n d o C a l l M e t h o d ( ) se d e v u e l v e , el v a l o r de Mylnteger se escribe en la consola. La figura 6.5 contiene el resultado de estas aplicaciones de prue ba de parámetros. C:\WINDOWS\System32\cmdexe
X
C :\>List ing6 _ 6 .exe 7 C:\>_
y. Figura 6.5. Un parámetro de salida devuelve el valor apropiado.
Debe usarse la palabra clave out dos veces por cada parámetro: una vez cuando se declara el par ámet ro y otra vez cuando se especifica el p ar ámet ro de salida al llamar al método. Si se olvida la pal abra clave out al llamar a un método con un parámetro de salida, se obtienen los siguientes errores del compilador: Listing6-6.cs (9, 6): error CS 1502: La me j o r coincidencia de método sobrecargado para 'Li st i n g 6 _ 6 .C a l l M e t h o d (out int)' tiene algunos argumentos no validos Listing6-6.cs (9,2 6) : error CS1503: Argumento '1' : no se puede convertir de 'int' a 'out int'
158
Cualquier valor a signado a variables usadas como par ámet ros de salida antes de que se llame al método se pierde. Los valores originales se sobrescriben con los valores que les asignó el método.
Parámetros de referencia Los p a r á m etro s de referencia proporcionan valores por referencia. En otras pal abras, el método recibe una referencia a la variable especificada cuando se l lama al método. Piense en un p ar á me tr o de referencia como en una variable de entrada y de salida: El método puede leer el valor original de la variable y también modificar el valor original como si fuera un p ar ámet ro de salida. Los par ámet ros de referencia se especifican en listas de par ámet ros con la p al abr a clave ref . La p al abr a clave ref debe preceder al tipo del par ámet ro en la lista de parámetros. C uando se llama a un método con un par ámet ro de referen cia. se debe declarar una variable p ar a que contenga el valor del p ar ámet ro de referencia, como se ve en el listado 6.7. Listado 6.7. Cómo trabajar con parámetros de referencia class
Listmgc_7
{ public
static
void Main()
{ int Mylnteger; Listing6_7 MyObject; MyObject = new Listing6_7 () ; Mylnteger = 3; S y s t e m . C o n s o l e . Wr it eL in e( My ln te g er ); M y O b j e c t .CallMethod (ref Mylnteger) ; S y s t e m . C o n sol é. Wr it eL in e( My ln te g er );
} void
C a l l M e t h o d (ref
int
Integerl)
{ Integerl
= 4;
} } El método CallMethod ( ) del listado 6.7 us a un p ar ámet ro de referencia llamado Integerl. El cuerpo del método establece el valor del p ar ámet ro de re fer enci a en 4. El mét od o M a i n ( ) d e c l a r a una v ar ia b le en te r a l lamad a Mylnteger y le asigna un valor de 3. Escribe el valor de Mylnteger en la consol a y luego lo us a como el p a rá me tr o del método CallMethod (). Cuando CallMethod ( ) se devuelve, el val or de Mylnteger se escribe en la consola una segunda vez. Si ejecuta el código del listado 6.7 debería obtener los valores que aparecen en la figura 6.6.
159
0A C:\WINDOWS\System32\cmd.exe
— weMiu.mLiflfea /-. ■
JO ]
C : \ > L i a t inc/6 7 . ex e O 4 C:\>_
Figura 6.6. Un parámetro de referencia cambia su variable directamente.
La segunda línea lee 4 porque la sintaxis del p ar ámet ro de referencia permite al método c ambi ar el valor de la variable original. Este es un cambio respecto al ejemplo de p ar ámet ro de ent rada del listado 6.5. Debe usarse la palabra clave r e f dos veces par a cada parámetro: una vez cuando se declara el par amet ro en la lista de par ámet ros y otra vez cuando se especifica el p ar ámet ro de referencia al llamar al método.
Matrices de parámetros Los métodos suelen escribirse para recibir un número especifico de parámetros. Un método con una lista de tres par ámet ros siempre espera ser llamada con tres par ámet ros, ni más. ni menos. Sin embargo, a veces puede ocurrir que un método no conozca cuántos par ámet ros debe acept ar al ser diseñado. Puede escribir un método que acepte una lista de cadenas que especifiquen los nombres de los regis tros que deberán borr ars e del disco. ¿.Cuántas cadenas debe permitir el método .’ Para ser flexible, el método debe diseñarse de man er a que el i nvocador pueda especificar las cadenas que necesita. Esto hace que llamar al método sea un poco más flexible porque el elemento que realiza la llamada ah or a puede decidir c u a n tas cadenas deben p as ar se al método. Sin embar go ¿cómo escribiremos la lista de par ámet ros de la lista cuando el método no conoce cuántos p ar ámet ro s le serán pasados. ’ Las matrices de parámet ros resuelven este problema de diseño ya que permiten especificar que el método acepte un número variable de argumentos. La matriz de par ámet ros de la lista de par ámet ros se especifica usando la p al abr a clave de C# p a r a m s . seguida del tipo de variable que deberá p ropor ci onar el llamador La especificación de tipos va seguida de corchetes, que a su vez van seguidos del identificador de la matriz de parámetros, como se ve en el listado 6.8.
160
Listad o 6.8. C ómo trabajar con matrices de parámetros class Listing6_8
{ public
static void M a i n ()
{ Listing6_8
MyObject;
MyObject = new L i s t i n g 6 _ 8 (); M y O b j e c t .C a l IMethod (1) ; M y O b j e c t .C a l l M e t h o d (1, 2) ; M y O b j e c t .C a l I M e t h o d (1, 2, 3) ;
void
CallMethod(params
int [] ParamArray)
{ System. Consolé . W n t e L m e ("------ " ) ; System.Consolé.WriteLine ("CallMethod () ") ; S y s t e m . C o n s o l é . W r i t e L i n e ("------ "); foreach(int ParamArrayElement in ParamArray) S y s t e m . C o n s o l é . W r i t e L i n e (Pa r a m A r r a y E l e m e n t );
} } En el listado 6.8. el método CallMethod ( ) está escrito p ar a acept ar un número variable de enteros. El método recibe los p ar ámet ro s en for ma de matriz de números enteros. El cuerpo del método usa la instrucción foreach para iterar la matriz de par ámet ros y escribe cada elemento en la consola. El método Main ( ) llama al método CallMethod ( ) tres veces, cada vez c o n un n u m e r o de a r g u m e n t o s d i f e r e n t e . E s t o es p o s i b l e s ó l o p o r q u e CallMethod ( ) se declara con una matriz de parámetros. La figura 6.7 indica que todos los par ámet ros fueron pas ados intactos al método. c-A C:\WINDOWS\System32\cmd.exe C :\ >1,is t ing6 0 .e xe C a l IMethodO
CdllMethodC
Id
Figura 6.7. La palabra clave params permite cualquier número de parámetros.
161
Puede usar una matriz de par ámet ros en su lista de par ámet ros de método. Puede c ombi nar una matriz de par ámet ros con otros par ámet ros en la lista de par ámet ros de método. Sin embargo, si usa una matriz de p ar ámet ros en una lista de par ámet ros de método, debe especificarla como el último par ámet ro de la lista. No se puede usar las pal abr as clave o u t o r e f en una matriz de parámetros.
Sobrecarga de métodos C# permite definir varios métodos con el mi smo nombre en la mi sma clase, siempre que esos métodos tengan listas de par ámet ros diferentes. Esta operación se conoce como so brec a rg a r el nombre del método. Obser ve el ejemplo en el listado 6.9. Listado 6.9. Cómo trabajar con métodos sobrecargados class
Listing6_9
{ public
static void M a i n ()
{ Listmgc
9 MyObject;
MyObject = new L i s t i n g 6 _ 9 (); M y O b ] e c t . A d d (3 , 4) ; M y O b j e c t . A d d (3.5, 4.75);
} void Add ( m t
Integerl,
int
Integer2)
{ in t S u m ; S ys te m. Co ns ole.WriteLine("adding Sum = Integerl + Integer2; S y s t e m . C o n s o l e . W r i t e L i n e (Sum) ;
two
i nt egers");
} void Add(double
Doublel,
double
Double2)
( double
Sum;
Sy st em .C on sole.WriteLine("adding Sum = Doublel + Double2; S y s t e m . C o n s o l e . W r i t e L i n e (Sum) ;
two
doubl es ") ;
El listado 6.9 i mplementa dos métodos A d d ( ) . Uno de ellos t oma dos n ú m e ros enteros como par ámet ros de entrada y el otro toma dos dobles. Como las dos implementaciones tienen diferentes listas de par ámet ros . C# permite que los dos
162
mét odos Add () coexistan en la mi sma clase. El método Main ( ) llama al m ét o do Add ( ) dos veces: u na vez con dos par ámet ros integrales v otra con dos v al o res de punto flotante. Como puede ver en la figura 6.8, los dos métodos se ejecutan satisfactoriamente, proc es ando los datos correctos. cI> i s t :i n a d d i r x f t;w o
'
X
(> 9 . e x e inteqer'
? ■addinq l;no
8,25
doubles
C:\>
y. Figura 6.8. El método sobrecargado suma números enteros y dobles.
Cuand o el compi lador de C# encuent ra una llamada a un método que tiene más de una implementación, ex ami na los par ámet ros usados en la l lamada y lla ma al método con la lista de p ar ámet ro s que mejor concuerde con los par ámet ros usados en la llamada. En la primera llamada a Add ( ) se usan dos números e n t e r o s . E n t o n c e s , el c o m p i l a d o r de C # e m p a r e j a e s t a l l a m a d a con la i mplement aci ón de Add ( ) que toma los dos p ar ámet ros de ent rada enteros p o r que los par ámet ros de la l lamada c oncuerdan con la lista de par ámet ros que tiene los números enteros. En la segunda llamada a Add ( ) se usan dos dobles. El compi lador de C# entonces empar ej a esta l lamada con la i mplementación de Add ( ) que toma los dos par ámet ros de ent rada dobles porque los par ámet ros de la l lamada concuerdan con la lista de p ar ámet ro s que tiene los dobles. No todos los métodos sobre carg ado s necesitan u sar el mismo número de pa r ám et ros en su lista de parámetros, ni todos los p ar ámet ro s de la lista de p ar ámet ros tienen que ser del mismo tipo. El único requisito que C# exige es que las funciones tengan diferentes listas de parámet ros. Una versión de una función so bre carg ada puede tener un entero en su lista de par ámet ro s y la otra versión puede tener tipos de datos como string, long y character en su lista de parámetros.
Métodos virtuales P ar a proseguir con el t ema de los métodos virtuales, hay que co mprender el concepto de herencia. La herencia b a s a una clase en otra ya existente, añadiendo
163
o quitando f uncionalidad según se necesite. En las siguientes secciones e x a m i n a remos cómo se crean y se usan los métodos virtuales.
Métodos sobrecargados Para e mpez ar esta sección, const rui rá un ejemplo de clase llamado Books. Esta clase contiene dos métodos llamados Title y Rating. El mét odo Tit le devuelve el nombre de un libro y el mét odo Rating devuelve un a cadena indi cando el n úm er o de estrellas con que ha sido calificado el libro en cuestión. En el listado 6.10 se recoge el código completo de su aplicación. Escríbalo en su editor de texto preferido y compílelo como hizo antes. Listad o 6.10. Cómo mostrar la inform ación del título y la puntuación de un libro con las siguie ntes clases using System; namespace
BookOverride
{ class
Book
{ public
stnng
Title ()
{ return
"Programming
Book";
} public
stnng
Rating()
{ return
"5 Stars";
} } class
Classl
{ static
void M a i n (s t r i n g []
args)
{ Book be = new Book () ; C o n s o l é . W n t e L m e (be.Title () ) ; C o n s o l é . W r i t e L i n e ( b e . R a t i n g ());
Antes de ejecutar este prog rama, repáselo rápidamente. C omo puede o b s e r var. un a clase contiene el método Main ( ) . Este método es donde se inicializa una instancia de la clase BookOverride. que contiene los métodos Title y
Rating. Después de inicializar una instancia, se llama a los métodos Title y Rating y se escribe la salida en la consola. El resultado puede verse en la figura 6.9.
164
Figura 6.9. El título y la puntuación de su libro aparecen como se esperaba.
A continuación, s obre cargue el método Title creando una clase ba sa d a en la clase Book. Para crear una clase ba sa d a en otra clase (y que por t anto p er mi te s ob re car ga r métodos), simplemente declare una clase de la for ma habitual y p on ga a continuación del nombre de la clase dos puntos y el no mbre de la clase en la que quiere que se base. A ña da el código del listado 6.11 a la aplicación. L istad o 6.11. Cómo s o b recargar m étodos derivando la clase Book class Wiley : Book
{ new public
string Title ()
{ return
"C# Bible";
} } Este código crea una clase Wiley que hereda la clase Book. Ahor a puede p a r a cr ear un nuevo método público llamado Title. Como ya se ha asignado a este mét odo el mi smo n ombre que al definido en la clase Book, se s obrecarga el m ét o d o Title aunque sigue di sponiendo de acceso a los otros mi embros dentro de la clase Book.
Ahor a que ha s o b re c ar ga d o el mét odo Title, debe c a m b i a r el método Main ( ) p ar a us ar su nueva clase. Cambi e su método Main ( ) como se m u e s tra en el listado 6.12.
165
Listad o 6.12. Cómo modificar el método Main() para sobrecargar una clase statxc void M a i n (string [] args)
{ Wiley be = new Wiley() ; C o n s o l é . W r i t e L i n e ( b c . T i t l e ()); Consolé.WriteLine ( be .R at in g() ) ;
} En su método Main(). cambie la variable be p ar a crear la nueva clase Wi ley. Como h ab rá adivinado, al llamar al método Title. el titulo del libro cambi a de Programming Book a C# Bible. Fíjese en que t odaví a tiene acceso al mét od o Rating. que fue definido originalmente en la clase Book. La s obrecarga de métodos dentro de una clase base es una excelente manera de ca mb ia r la funcionalidad especifica sin grandes probl emas
Resumen C# permite escribir métodos en sus clases de C#. Los métodos pueden a yuda r a div idir el código en partes fáciles de entender y pueden brindar un lugar único en el que al ma c ena r codigo que puede ser llamado varias veces. Las funciones pueden recibir parámetros. Los par ámet ros de entrada contie nen valores que han sido p as ados a los métodos, pero sus valores no pueden cambiar. Los parámet ros de salida tienen valores que les son asignados por un método y el valor asi gnado es v isible par a el elemento que hace la llamada. Los par ámet ros de referencia contienen v alores que pueden ser proporcionados dentro de la función y ademas, su valor puede ser modificado por el método. Las m a t r i ces de par ámet ros permiten escribir métodos que toman un número v ariable de argumentos. C# también permite s obr e ca rga r métodos. Los métodos s obrecargados tienen el mismo nombre pero diferentes listas de parámetros. C# usa los par ámet ros propor ci onados en una llamada p a ra determinar qué método debe inv ocar cuando se ejecute el código.
166
Agrupación de datos usando estructuras C# permite a g r u p a r las variables en estructuras. Al definir una estructura p a r a los datos, todo el g rup o puede ser proces ado con un solo nombre de es tru c tura, sin i mport ar el número de variables que contenga la estructura. El uso de un a sola es tructur a facilita la mani pul aci ón de un conjunto de variables, en lugar de tener que seguir la pi st a de cada variable p or separado Una es tructur a puede contener campos, métodos, constantes, constructores, propiedades, indizadores, operadores y otras estructuras. Las e st ructuras de C# son tipos de valor, no tipos de referencia. Esto significa que las variables de e st ruc tur a contienen directamente los valores de las e st ru ctu ras, en l ugar de mantener u na referencia a un a es tructur a que se encuent ra en otra parte de la memoria. Al gunas de las variables declaradas en el código C,U pueden tener una relación lógica con otras variables ya declaradas. Suponga, por ejemplo, que quiere escri bir un código que t rabaje con un punt o de la pantalla. Puede declarar dos v ar ia bles p ar a describir el punto: int int
XC oo rdinateOfPoint; YCoordinateOfPoint;
El punt o tiene dos valores, la coor denada x y la coor denada y, que funcionan j u n ta s p ar a describir el punto.
169
Aunque puede escribir el código C# de esta manera, es bast ant e pesado. Los dos valores deben poder usarse en cualqui er código que quiera t ra ba j ar con el punto. Si quiere que un método trabaje con el punto, tendrá que p a s ar los valores uno a uno: void Wor k W i t h P o m t ( int /.Coordínate, int '/Coordínate) ; void S e t M e w P o m t (out m t ¡(Coordínate, out int YCoordinate) ;
La situación resulta incluso mas complicada cuando varias variables t rabajan juntas par a describir una sola entidad. Por ejemplo, un empleado en una base de datos de recursos humanos puede tener variables que representen un nombre de pila, un apellido, una dirección, un numero de teléfono y un salario actual. C ont ro lar todas estas variables por separado y asegurarse de que todas se usan como un g ru po puede volverse complicado.
Cómo declarar una estructura Los contenidos de una estructura se declaran usando la pal abra clave struct. Para dar nombre a una est ructura use un identificador después de la pal abr a clave struct. La lista de variables que forman la est ructura se encierra entre llaves a cont inuación del identificador de la estructura. Las declaraciones de mi embro de la est ructura suelen llevar antepuesta la pal abr a clave public para avisar al compi lador de que sus valores deben ser públicamente accesibles a todo el código de la clase. Ca da declaración de mi embro termina con un punto y coma. La declaración de una e st ructur a que defina un punto puede parecerse a lo siguiente: struct
Pomt
{ public public
mt mt
X; Y;
} En el ejemplo anterior, los mi embros de la est ructura. X e Y. tienen el mismo tipo. Sin embargo, esto no es obligatorio. Las est ructur as también pueden estar formadas por variables de distintos tipos. El ejemplo anterior del empleado puede present ar este aspecto: struct
Employee
{ public public public public public public public
170
s t n n g FirstName; s t n n g LastName; s t n n g Address; string City; s t n n g State; ushort ZIPCode; decimal Salary;
Como con todas las instrucciones de C#. sólo puede declararse una est ructura desde el interior de un a clase.
Los valores iniciales de los mi embros de la e st ruc tur a siguen las reglas de inicialización de valores descritas en un capítulo anterior. Los valores se inicializan con alguna representación del cero y las cadenas se vacían. C# no permite inicializar mi embros de es tru ct ur as en el moment o de declararse. Obs er ve el error en el siguiente código: struct
Point
{ public int X = 100; public int Y = 200;
} Est a declaración p roduc e estos errores del compilador: error campo error campo
CS0573: 'Point.X': no se permiten de instancia en las estructuras CS0573: 'Point.Y': no se permiten de instancia en las estructuras
inicializadores
de
inicializadores
de
Puede u s ar un método especial llamado co nstru cto r p ar a inicializar miembros de e st ructur as con valores distintos de cero. Má s adelante, en este mi smo c a pí t u lo, se e xa mi na rán los constructores.
Cómo usar estructuras en el código Después de habe r definido la estructura, puede u s ar su identifícador como un tipo de variable, igual que si fuera un tipo int o long. Indique el identifícador de la estructura, seguido de algún espacio en blanco y del identifícador de la variable de estructura: Point MyPoint;
Esta declaración declara una variable l lamada MyPoint cuyo tipo es el de la estructura Point. Se puede usar esta variable igual que cualquier otra variable, incluso dentro de e xpresiones y como p a r á m et ro de un método. El acceso a cada mi embro de la es tructur a resulta tan sencillo como escribir el nombre del identifícador de la variable de la estructura, un punto y a continuación el mi embro de la estructura. El listado 7.1 mues tra cómo se puede u sar una es t ru ct ur a en el código.
171
Listad o 7.1. A cce so a miembros de la estructura class
Listmg7
1
{ struct
Point
{ public public
int X; int Y;
} public
static
void M a i n ()
{ Point
MyPoint;
M y P o m t . X = 100; MyPoint.Y = 200; S y s t e m . C o n s o l é . W ri te Li ne (M yP oi nt . X) ; S y s t e m . C o n s o l é . W ri te Li ne (M yP oi nt . Y) ;
} } El resultado de este ejemplo debería ser el siguiente: 100 2 0 0
Se puede asignar una variable de est ructura a otra, siempre que las estructuras sean del mismo tipo. Cuando se asigna una variable de est ructura a otra. C# asigna el valor de la variable de est ructur a que aparece antes del signo igual a los valores correspondientes de la es tructur a que aparece después del signo igual, como se puede obs er var en el listado 7.2. L istado 7.2. Asignación de una variable de estructura a otra class
Listing7_2
{ struct
Point
{ public public
int X; int Y;
} public
static void M a i n ()
{ Point Point
M yF i r s t P o i n t ; MySecondPoint;
MyFirstPoint.X = M y F i r s t P o i n t .Y = M y S e c o n d P o i n t .X = M y S e c o n d P o i n t .Y =
172
100; 100; 2 00; 200;
S ystem.C o n s o l e . W r i t e L in e( My Fi rs tP oi nt .X ) ; Sy st em . C o n s o l e . W r i t e L i n e ( M y F i r s t P o i n t .Y ) ; MyFirstPoint
= MySecondPoint;
System.Cons ole.WriteLine(MyFirstPoint.X) ; Sy st em . C o n s o l e . W r i t e L i n e ( M y F i r s t P o i n t .Y) ;
} } El código anterior asi gna el valor 100 a los mi embros de MyFirstPoint y el v a l o r de 2 00 a los m i e m b r o s de M y S e c o n d P o i n t . Los v a l o r e s de MyFirstPoint se escriben en la consola y luego los valores de la variable MyFirstPoint se copian en los valores de la variable MySecondPoint. T ra s la asignación, los valores de MyFirstPoint se vuelven a escribir en la consola. Si compila y ejecuta este código, obt endrá el resultado ilustrado en la figura 7.1. c.A C:\WINDOW5\System32\cmd.exe C : \ > L i;', t i i K f V
1m
.....i ny
x
2 . *■ xe
?.m
2MU C: '
Figura 7.1. Asignación de una estructura a otra
Todos los valores de una es truc tu ra se sobrescriben en una asignación con los valores de la variable de es truc tu ra indicada después del signo igual.
Cómo definir métodos en estructuras Además de variables, en las est ructur as se pueden incluir métodos. Si necesita escribir un código que trabaje con los contenidos de u na estructura, podría consi der ar la opción de escribir el método dentro de la mi sma estructura.
173
Cómo usar métodos constructores Una es tructur a puede incluir un método especial llamado constructor. Un método co ns tructor se ejecuta cuando se ejecuta en t iempo de ejecución una declaración de variable que usa el tipo de la estructura. Las est ructur as pueden tener v a n o s constructores o ninguno. Las dec laraci o nes de constructores de es tructur a son muy parecidas a las declaraciones de métodos de clase, con las siguientes excepciones: Los constructores no devuelven ningún valor. No se pueden u sar pal abr as clave de tipo de devolución p ar a escribir un const ructor de estructura, ni siquiera void. Los identificadores de constructores tienen el mismo nombre que la es truc tura. Los constructores deben tener al menos un parámetro. C# no permite defi nir un constructor sin parámetros. C# siempre define una constructor por defecto sin par ámet ros por nosotros. Este es el const ructor que inicializa todos los miembros de la e st ruc tu ra a cero o su equivalente. Una es tructur a puede definir más de un constructor, siempre que los c o n st ru c tores tengan diferentes listas de parámetros. El listado 7.3 mues tra una estructura Point con dos constructores. Listad o 7.3. C onstructores de estructuras class
Listing7_3
{ struct
Point
{ public int X; publ i c int Y ; public
Point(int
InitialX)
{ X = ImtialX; Y = 10 0 0;
public Point (int
ImtialX,
int
ImtialY)
{ X = ImtialX; Y = Initial Y;
public
static
void M a i n ()
{ Point M y F i r s t P o m t
174
= new Point ();
Point M y S econdPoint = new Point (100) ; Point M y T h i r d P o m t = new Point (250, 475) System.Console .W r i t e L m e (My Fi r s t P o m t . X ) ; System.Console . W r i t e L m e (My Fi r s t P o m t . Y ) ; System.Console . W r i t e L m e ( M y S e c o n d P o m t .X) S y s t e m .Console . W r i t e L m e ( M y S e c o n d P o m t .Y ) System. Con sole . W r i t e L m e (MyThi rdPoi nt .X ) ; System.Console . W r i t e L m e (MyThi r d Po i nt .Y ) ;
La figura 7.2 ilustra el resultado de compilar y ejecutar el código del listado 7.
Figura 7.2. La estructura revela los valores predefinidos.
Ten ga en cuenta los siguientes conceptos del listado 7.3: •
La estructura Point declara dos constructores. Uno recibe como a r g u mento un solo número entero y el otro recibe dos números enteros. Ambos llevan ant epuest a la p al ab r a clave public por lo que su código es ac ces i ble al resto del código de la clase.
•
El const ructor con un p a rám et ro entero asigna al mi embro X de la es t ruc t ura el valor del ar gument o entero y asigna al miembro Y de la est ructura el valor 1.000. El const ructor con dos par ámet ros enteros asigna al mi embro X de la es t ru ct ura el valor del pri mer ar gu me nt o entero y asigna al mi embro Y de la es tructur a el val or del segundo ar gume nt o entero.
175
El código declara tres variables de tipo Point. C ad a una de ellas llama a uno de los constructores Point. La declaración de MyFirstPoint llama al constructor sin argumentos. Este es el constructor por defecto que C# define par a cada estructura. La declaración de MySecondPoint lla m a al c o n s t r u c t o r q u e t i e n e un a r g u m e n t o y la d e c l a r a c i ó n de MyThirdPoint llama al const ructor con dos argumentos. Preste mucha atención a la sintaxis del listado 7.3. que invoca a un constructor de estructura. Si se quiere invocar a un const ructor en una estructura, se debe emplear la pal abr a clave new seguida del nombre de la es truc tu ra y de los par ámet ros del const ructor entre paréntesis. El valor de esa expresión se asigna a la variable que se está declarando. Observe la siguiente declaración: Point MyThirdPoint
= new Point (250,
475);
Esta declaración indica: "Crea una nueva est ructur a Point usando el c on s tr uc tor que tiene dos enteros. Asigna su valor a la variable MyThirdPoint". D e b i do a las reglas de asignación de estructuras anteriormente descritas, los miembros de la variabl e MyThirdPoint reciben los valores de los mi embros de la nueva estructura. No es necesario hacer nada más con la nueva est ructura cr eada c u a n do se llamó a new. El entorno común de ejecución (C L R) detecta que la e s t r u c t u ra ya no se us a y se deshace de ella mediante el meca ni smo de recolección de elementos no utilizados. En el listado 7.3 también aparece la sintaxis del const ructor sin parámetros: Point MyFirstPoint
= new P o i n t ();
Asi se indica al compi lador de C# que se quiere inicializar la es tructur a de la for ma habitual. Se deben asi gnar valores a todos los mi embros de una est ructur a antes de usarla, bien invocando su co ns tructor sin par ámet ros o asi gnando e xpl í citamente todos los ca mpos de un valor. Observe el listado 7.4. L istad o 7.4. Si se usa una estructura antes de inicializarla se producen errores de compilación class
Listing7_4
{ struct
Point
{ public public
int X; int Y;
} public
static void M a i n ()
{ Point
MyPoint;
S y s t e m . C o n s o l é .W r i t e L i n e ( M y P o i n t .X );
176
System.Consolé.WriteLine(MyPoint.Y)
El código anterior es erróneo y al compilarlo el c ompi lador de C# produce los siguientes mensajes de error: error CS0170: Uso del campo 'X', posiblemente error CS0170: Uso del campo 'Y', posiblemente warning CS0649: El campo 'Listing7_4.P o i n t .X ' siempre tendrá el valor predeterminado 0 warning CS0649: El campo 'Listing7_4.P o i n t .Y' siempre tendrá el valor predeterminado 0
no asignado no asignado nunca se asigna
y
nunca
y
se asigna
Los mensajes de error avisan de que las llamadas a WriteLine() usan mi embros de datos en la estructura, pero que a esos miembros de datos todavía no se les ha asignado valor. La variable MyPoint no ha sido inicializada con una llamada al const ructor sin par ámet ros ni t amp oco se han asi gnado valores expl í citamente a sus miembros. C# no invoca al cons tructor sin par ámet ro s a menos que se escri ba la l lamada en el código. Este es otro ejemplo de cómo el compilador de C# protege el código par a evitar que se comport e de forma impredecible. Tod as las variables deben inicializarse antes de ser usadas.
Cómo llamar a métodos desde estructuras Ta mb i én se pueden escribir métodos en las estructuras. Estos métodos siguen las mi smas reglas que los métodos de clase: deben es pecificar un tipo de d evol u ción (o void) y tener un identificador y una lista de argumentos, que puede estar vacía. Para llamar a un método en una est ructur a se usa la mi s ma notación de puntos que p a r a acceder a un método de clase. Observe el listado 7.5. Listad o 7.5. Cóm o llamar a métodos de estructura class
Listing7__5
{ struct
Point
{ public int X; publ ic int Y ; public
Point(int
InitialX,
int
InitialY)
{ X = InitialX; Y = InitialY;
} public
bool
IsAtOrigin()
{
177
±f ( (X == 0) && (Y = = 0) ) return true; e 1s e return false;
} ( pub 1 1 c s tatic
vo ici M a m ()
I Point Point
M y F i r s t P o m t = new Point (100, Myí.;econdPoint - nexv Point () ;
2 00) ;
i f: (MyF'i rst Poi nt . IsAtOri.gin (j -- true) System.Consolé .Writ.eLine ("MyFirstPoint o n q m . " ); e 1s e :jy ste m .: o n solé. W i:it e I,in e í"M y Fi rst Poi n t o r iy in ." ¡ ; i f (MySecondPoint.IsAtOr iqiní) == true) System.Conso 1e .Wr itel.ine ("MySecondPo int o r icj i.n ." ) ; e 1s e System.Conso 1e .Wr iteLine ("MySecondPoi nt cj i i g in ." ) ;
is at
the
is n o t at
ís
at
is not
th e
the
at
the
} La estructura Poi nt del listado 7.5 declara un método llamado TsAtOr iqi n. El codigo de ese método c omp rue ba los valores de los métodos de la es tructur a y devue lve true si las coordenadas del punto son (0. 0) y false en cualquier otro caso. El método M ai n ( ) declara dos variables de tipo Point: a la variable M y F i r s t P o i n t se le asignan las coordenadas (100. 200) usando el constructor explícito v a la variable M y S e c o n d P o i n t se le asignan las coordenadas (0. 0) usando el cons tructor por defecto sin parámet ros. En ese momento el método M ai n () llama al método I sAtOr iqin con los dos puntos y escribe un mensaje bas ado en el valor devuelto por el método. Si se compi la y ejecuta el código del listado 7.5. se obtiene el siguiente resultado en la consola: MyFirstPoint ís not at the o n g i n . MySecondPoint ís at the o n g i n .
Hay que a segurars e de prefijar los métodos con la pal abr a clave pu bl ic si se quiere que sean accesibles por todo el resto del código de la clase.
Cómo definir propiedades en estructuras Las propiedades de una es tructur a permiten leer, escribir y calcul ar valores usando descriptores de acceso. A diferencia de los campos, las propiedades no se
178
consideran variables; p or tanto, no designan espacios de al macenamiento. Debi do a esto, no pueden ser p as adas como par ámet ros r e f o o u t . El listado 7.6 incluye una propi edad dentro de la est ruc tu ra Point. Listad o 7.6. Definición de una propiedad en el interior de una estructura class Listing7_6
{ struct
Point
{ private int x; public int X
{ ge t
{ return
x;
} set { x = valué;
}
public
static void M a i n ()
{ int RetValue; Point MyPoint
= new P o i n t ();
MyPoint.X = 10; RetValue = MyPoint.X; Sy st em .C on so le . W r i t e L i n e ( R e t V a l u e ) ;
Este código asigna un valor al mi embro X de la est ructur a Point y luego devuelve este valor a la variable RetValue. El resultado del listado 7.6 se ilustra en la figura 7.3. El uso de propiedades es u na for ma excelente de leer, escribir y c alcular datos dentro de una estructura. No hace falta incluir métodos voluminosos p ar a que realiccn los cál cu los y se p uede definir cómo y c u á n d o puede n a c t u a r los descriptores de acceso get y set.
Cómo definir indizadores en estructuras Los indizadores son objetos que permiten indizar una est ructur a de forma muy parecida a una matriz. Con un mdizador. se pueden declarar varias estructuras al mi smo tiempo y hacer referencia a cada es tr uc tu ra usando un número de índice.
179
El listado 7.7. que declara una est ructur a llamada MyStruct que contiene una cadena y un índice, lo demuestra.
Figura 7.3. Definición de una propiedad en el interior de una estructura
Listado 7.7. Cómo incluir un indizador en una estructura
class í
L i s t ing7 _7
s t r u c t My S tiuc t
( public public
string string
[]clata ; this [int
index]
{ get
{ return
d a ta [i nd ex ];
} set { data[index]
= value;
l
public
static void M a i n ()
{ in t x ; MyStruct ms.data m s [0 ] = m s [1 ] = m s [2] = m s [3] = m s [4] =
180
ms = new M y S t r u c t () ; = new string[5]; "Brian D Patterson"; "Aimee J Patterson"; "Breanna C Mounts"; "Haileigh E Mounts"; "Brian W Patterson";
for (x=0;x < 5 ;x++) Sy st em .C on so lé . W r i t e L i n e ( m s [ x ] );
} } C om o se puede ver. este ejemplo crea un nuevo objeto MyStruct y asigna a los mi embros data el valor 5. lo que indica que se usan cinco copias de esta estructura. Pa ra hacer referencia a cada copia de esta est ructura se usa un número indizador (de 0 a 5) y se al macenan los nombres dentro de la estructura. Para as egurars e de que todos lo datos per manecen intactos, se aplica un simple bucle a los posibles números de índice y se escribe el result ado en la consola. En la figura 7.4 se ilustra el resultado del listado 7.7. X
C:\WINDOWS\System32\cmd.exe C : \ > L i »t i n < ( 7 B i'i< u i ñ i
7 . o <>: D ¡ \ i I t. r- (Mi J Pat. t , m i
U rticin n a
C M ounts
H a i ( t; i «jh H M o t í n t; R r i - u t U P¿tt. t > ;rs o n
Figura 7.4. Para devolver fácilmente los datos se incluye un indizador dentro de la estructura
Un indizador en una es tructur a puede ser muy útil cuando se t rabaj a con g r a n des cantidades de datos del mismo tipo. Por ejemplo, si se va a leer la i nformación de una di rección desde una bas e de datos, éste es un excel ent e l ugar p ar a almacenarla. Todos los ca mpos se mantienen mientras se pr opor ci ona un m e c a nismo p ar a acceder fácilmente a cada dato de los registros.
Cómo definir interfaces en estructuras Las interfaces son un modo de as egurars e de que cualqui era que use la clase cumple con todas las reglas i mpuestas p a r a hacerlo. Estas pueden incluir la i mplement aci ón de ciertos métodos, propi edades y eventos. C u an d o se expone una interfaz, sus usuar ios deben heredarla y al hacerlo están obligados a crear ciertos métodos y así sucesivamente. Esto as egu ra que la clase y/o es tructur a se use de forma correcta.
181
Tambi én se puede incluir una interfaz dentro de una estructura. El listado 7.8 muest ra c ómo implementar correctamente un a interfaz. L istado 7.8. Cómo implementar una interfaz en una estructura class Listing7__8
{ interface
¡Interface
( void M e t h o d () ;
} struct MyStruct
: Ilnterface
{ public
void M e t h o d ()
{ System. Console.WriteLine ("Structure
public
static
Method") ;
void M a i n ()
{ MyStruct DemoStructure = new M y S t r u c t (); D e m o S t r u c t u r e .M e t h o d ();
} } Este código crea una interfaz l lamada IInterface. Esta interfaz contiene la definición de un mét odo l lamado Method. Se crea una estructura y al final de su nombre se incluyen dos puntos seguidos del nombre de la interfaz que desea derivar. El método, que simplemente escribe un a línea de texto en la consola, se incluye en la estructura. En la figura 7.5 es ilustra el resultado del programa. Para demost rar lo i mportante que es la interfaz. si elimine las cuatro líneas que c o mpo nen el mét odo Method en la estructura MyStruct y vuelve a compi lar el prog rama, o bt endrá el siguiente mensaje de error: Classi.es (8,9): error CS0535: 'Listing7_8.MyStruct ' no implementa e 1 miembro de interfaz L isting7_8.IInterface.Method()'
El comp il ado r de C# determinó que no i mplement amos todos los métodos e s tipulados por la interfaz. Como no se implemento el método correcto, el pro gr ama no se pudo compilar, indicando de esta for ma que no se cumplen todas las reglas.
Cómo usar los tipos simples de C# como estructuras Los tipos primitivos (int. uint, long y similares) descritos en un capítulo anterior en realidad se implementan como est ructur as en el C L R de NET. La
182
tabla 7.1 enumera las p al abr as clave con valor variable y los nombres de las e st ructur as N E T que actual mente los implemcntan. C:\WINDOWS\System32\cmd.exe
D ■
C :\>Lis t ing ? - 8 .exe Structure Method C :\>
Figura 7.5. Cómo implementar un interfaz en una estructura
Tabla 7.1. Nombres de estructura
NET para tipos de valor
Palabra clave d e C #
N o m b r e d e estructura . N E T
sbyte
S ys t e m , .S B y t e
byte
S ys t e m ..B y t e
short
S ys tern, .I n t i 6
us ho rt
Sys t e m .U i n t 16
int
S ys t e m . I n t 3 2
uint
S ys t e m , .U i n t 32
long
.I n t 6 4 Sys t e m ,
ulong
,U i n t 64 Sys t e m .
char
S ys t e m . Char
float
Sy st e m . ,S i n g l e
double
.D o u b l e Sys t e m ,
bool
S ys t e m . .B o o l e a n
decimal
S ys te m.,D e c i m a l
Este es que ma es parte de lo que hace que el código C# sea compat ibl e con otros lenguajes NET. Los valores C # se asigna a las estructuras N E T que pue-
183
de usar cualquier lenguaje NET. porque el C L R puede usar cualquier estructura N E T La asignación de las palabras claves de C# con estructuras N E T también permite que las estructuras usen técnicas como la sobrecarga de operadores para definir el comportamiento del valor cuando se usan en una expresión con un o per a dor. Se analizará la sobrecarga de operadores cuando se traten las clases C#. Si lo desea, puede usar los nombres reales de est ructur a . NET en lugar de las pal abr as clave de C#. El listado 7.9 muest ra el aspecto que tendría el listado 7.5 si se escribiera usando los nombres de est ructuras NET. Listado 7.9. C ómo usar los nombres de tipo de estructura NET class Listing7_9
{ struct
Point
{ public public
System.Int32 System.Int32
X; Y;
public
Point(System.Int32
InítialX,
System.Int32
ImtialY)
{ X = InítialX; Y = In 1 1i a 1Y ;
public
System.Boolean
IsAtOrigm
{ i f ((X == 0) && (Y == 0)) return true;
else return
public
static
false;
void M a i n ()
{ Point M y F i r s t P o m t = new Point (100, Point M y S e c o n d P o m t = new Point ();
200) ;
if (MyFirstPoint.I s A t O r i g i n () == true) System.Console.WriteLine("MyFirstPoint is at the origin."); else System.C on so le .W ri te Li ne ("MyFirstPoint is not at the
o rig in ."); if ( My Se c o n d P o m t .IsAtOrigin () == true) System. Console .WriteLine (" M y S e c o n d P o m t
is
at
the
o rig in ."); else System.C on so le .W ri te Li ne (" M y S e c o n d P o m t
origin.") ; } }
184
is not
at
the
Resumen Las est ructuras permiten ag r up ar un conjunto de variables con un solo n o m bre. Las variables pueden declararse usando el identificador de estructura. Las estructuras se declaran usando la pa la br a clave de C# struct. Todas las est ructur as de C# tienen un nombre y una lista de miembros de datos. C# no pone ningún límite al número de miembros de datos que puede contener una estructura. Se accede a los miembros de est ructuras mediante la notación StructName . MemberName. Los mi embros se pueden usar en cualquier parte donde esté p e r mitido usar su tipo de datos, incluyendo e xpresiones y par ámet ros de métodos. Las estructuras pueden i mplementar tanto métodos como variables. Los mi em bros de est ructuras se invocan usando la notación StructName .MethodName y se usan igual que los nombres de método de clase. Las est ructur as también implementan métodos especiales llamados constructores, que micializan la es t ruct ura con un estado conocido antes de u sar la estructura. Los tipos de valor de C# son asignados a est ructuras definidas por el C LR de N E T Esto es lo que permite que otros códigos de N E T usen los datos. Todas las variables son compatibles con C L R de N E T porque las variables se definen usando est ructuras compatibles con el C LR
185
Parte II
Programación orientada a objetos con C#
187
| Escribir código orientado a objetos Los lenguajes de p rog ra ma ci ón siempre se han diseñado en torno a dos c on ceptos fundamentales: los datos y el código que oper a sobre los datos. Los len guajes han evolucionado a lo largo del tiempo p a r a c a mb i ar el modo en que estos dos conceptos interactúan. En un principio, lenguajes como Pascal y C invitaban a los p rogr amadore s a escribir software que t ra ta ra al código y a los datos como dos cosas separadas, sin ni nguna relación. Este enfoque dio a los progr amadore s la libertad, pero tambi én la obligación, de elegir el modo en que su código gest io na los datos. Además, este enfoque obl igaba al p ro gr a ma do r a traducir el mundo real que se quería model ar us and o soft ware a un modelo específico p a r a ordenadores que u sar a datos y código. Los lenguajes c omo Pascal y C se c onst ruyeron en torno al concepto de p r o c e dimiento. Un procedi miento es un bl oque de código con nombre, exact ament e igual que los actuales métodos de C#. El estilo de s oftware desarrol lado usando estos lenguajes se l lama p ro g r a m a c ió n p r o c e d u r a l. En la prog ramaci ón procedural. el p r og r am ad o r escribe uno o más procedi mientos y t r ab a ja con un conjunto de variables independientes definidas en el programa. Todos los procedimientos pueden verse desde cualquier parte del códi go de la aplicación y todas las variables pueden ser mani pul ad as desde cualquier parte del código.
189
En los años 90. la programación proccdural dio paso a lenguajes como Smalltalk y Simula, que introdujeron el concepto de objeto. Los inventores de estos l engua jes se dieron cuenta de que el ser humano no expr esa ideas en términos de bloques de código que actúan sobre un grupo de variables; en su lugar, expresan ideas en términos de objetos. Los objetos son entidades que tienen un conjunto de valores definidos (el estado del objeto) y un conjunto de operaciones que pueden ej ecut ar se sobre ese objeto (los com po rtam ien tos del objeto). Por ejemplo, imagine un cohete espacial. Un cohete espacial tiene estados, como la cantidad de combustible o el número de pasajeros a bordo, y comportamientos, como "despegar" y "aterrizar". Ademas, los objetos pertenecen a clases. Los objetos de la misma clase tienen el mismo estado y el mismo conjunto de comportamientos. Un objeto es un caso concreto de una clase. El cohete espacial es una clase, mientras que el cohete espacial llamado Discovery es un objeto, un caso concreto de la clase cohete espacial.
NOTA: En realidad, ni siquiera en la programación procedural son visibles todos los procedimientos ni todas las variables. Igual que en C#, los len guajes procedurales tienen reglas de ámbito que controlan la visibilidad del código y de los datos. Por lo general podemos hacer visibles los proce dimientos y los datos (a los que nos referiremos en este capítulo como elementos) en el procedimiento, archivo fuente, aplicación o nivel externo. El nombre de cada ámbito es autoexplicativo. Un elemento visible en el nivel de procedimiento sólo es accesible dentro del procedimiento en el que se define. No todos los lenguajes permiten crear procedimientos dentro de otros procedimientos. Un elemento visible en el nivel de archivo fuente es visible dentro del archivo en el que se define el elemento. En el nivel de aplicación, el elemento es visible desde cualquier parte de código en la misma aplicación. En el nivel externo, el elemento es visible desde cual quier parte de código en cualquier aplicación. El punto principal es que, en programación procedural, la interacción entre datos y código está controlada por los detalles de implementación, como el archivo fuente en el que se define una variable. Una vez que se decide hacer visible una variable fuera de sus propios procedimientos, no se obtiene ayuda para proteger el acceso a esa variable. En aplicaciones grandes con varios miles de variables, esta falta de protección suele acarrear fallos difíciles de encontrar. El desarrollo de software orientado a objetos tiene dos claras ventajas sobre el desarrollo de software procedural. La primera ventaja es que se puede especificar lo que debe hacer el software y cómo lo hará usando un vocabulari o familiar a los usuarios sin preparación técnica. El software se est ructura u sando objetos. Estos objetos pertenecen a clases con las que el usuario del mundo de los negocios, al
190
que está destinado, está familiarizado. Por ejemplo, d urante el diseño de soft ware AT M. se usan clases como CuentaBancaria. Cliente. Presen tación y similares. Esto reduce el trabaj o necesario p a ra t raduci r una situación del mundo real al modelo de s oftware y facilita la comuni caci ón con la gente ajena al software y que está i nteresada en el pro duc to final. Este modo más sencillo de diseñar s oft wa re ha conducido a la apari ci ón de un es tándar p ar a describir el diseño del software orientado a objetos. Este lenguaje es el Lenguaje unificado de modelado o UML. La segunda ventaja del software orientado a objetos se demu es tra durante la implementación. El hecho de que ahor a podemos tener ámbitos de nivel de clases, permite o cultar variables en las definiciones de clase. C ad a objeto t endrá su p r o pio conjunto de variables y estas variables por lo general solamente serán a cces i bles mediante las operaciones definidas por la clase. Por ejemplo, las variables que contienen un estado del objeto CuentaBancaria sólo serán accesibles l lamando a la operación Retirada ( ) o Depósito ( ) asociada a ese objeto. Un objeto CuentaBancaria (o cualquier otro objeto) no tiene acceso a otro estado pri vado del objeto CuentaBancaria. como el balance. A este p ri nci pio se le llama encapsulación. El desarrollo de software orientado a objetos se fue haciendo más pop ul ar a medida que los pr ogr amad or es ad op t ab a n este nuevo modo de di señar software. C# es un lenguaje orientado a objetos y su diseño g ar ant iza que los prog ramad ores de C# sigan los conceptos correctos de la prog ramaci ón ori ent ada a objetos.
NOTA: SmallTalk, Java y C# son lenguajes orientados a objetos puros porque no se puede escribir un programa sin usar objetos. A otros lengua jes, como C y Pascal, se les llama lenguajes procedurales o orientados a objetos porque no disponen de compatibilidad integrada que permita crear objetos. Existe un tercer tipo de lenguaje híbridos, como C++, en los que se puede elegir si usar o no objetos. Bjame Stroustrúp, el ffivefttor de C++, también era tina versión mejorada del lenguaje de programación C La cea»* ’ patibilidad con el código C existente ayudaba a <$u$ C*H- se convirtiera en un lenguaje importante. En este capítulo se estudian los conceptos que componen un lenguaje orientado a objetos, empezando por sus componentes esenciales (las clases y objetos), hasta los términos más avanzados (abstracción, tipos de datos abstractos, encapsulación. herencia y polimorfismo). Se t rat arán los conceptos básicos y se p r o c ur ar á evitar los detalles específicos sobre cómo están implementados estos conceptos en CU. Estos detalles específi cos se t rat arán en capítulos posteriores.
191
Clases y objetos En primer lugar, esta sección vuelve a tratar la diferencia entre un objeto y una clase. Este libro usa mucho estos dos términos y es importante distinguir entre ellos. Una clase es una coleccion de código y de variables. Las clases gestionan el estado, en forma de las variables que contienen, y comportamientos, en forma de los métodos que contienen. Sin embargo, una clase sólo es una plantilla. Nunca se cr ea una c las e en el código. En su l ugar, se cr ean objetos. Por ejemplo. C uen taB a n c a r i a es una clase con una variable que contiene el saldo de la cuenta v los métodos Retirada (). Depósito () y Mo str ar S a l d o (). Los objetos son casos concretos de una clase. Los objetos se construyen usando una clase como plantilla. Cuando se crea un objeto, éste gestiona su p r o pio estado. El estado de un objeto puede ser diferente del estado de otro objeto de la mi sma clase. Imagine una clase que define a una persona. Una clase persona va a tener estado (quizas, una cadena que representa el nombre propio de la persona) \ comport ami ent o, mediante métodos como I r ATr aba jar ( ) . C o mer í ) e I r A D o r m i r (). Se pueden crear dos objetos de la clase persona, cada uno con un estado diferente, como se muest ra en la figura 8.1. La figura 8.1 muest ra la clase persona y dos objetos persona: uno con el nombre propio "Alice" v otro con el nombre propio "Bob". El estado de cada objeto se al mac ena en un conjunto de variables diferentes. Vuelva a leer la frase anterior. Esta frase c on tiene un punto esencial par a co mprender el funcionamiento de la pr ogr amaci ón orientada a objetos. Un lenguaje admite objetos cuando no se necesita crear un código especial para disponer de un conjunto de variables cada vez que se crea un objeto diferente.
NOTA: Si un lenguaje admite la gestión automática del estado dentro de objetos pero carece de las otras características descritas en esta sección, suele recibir el nombre de lenguaje basado en objetos. Visual Basic 6 admi te objetos, pero no admite la herencia de implementación; por tanto, no se le considerar como un auténtico lenguaje orientado a objetos. Auténticos len guajes orientados a objetos son SmallTalk, Java y C#.
Terminología del diseño de software orientado a objetos Se e nc ontr ara con muchos términos cuando lea libros sobre el desarrollo de software orientado a objetos y probablement e se encuentre con muchos de estos términos cuando trabaje con codigo C#. Algunos de los términos más usados son:
192
Abst racci ón Tipos de datos abstractos Encapsulación He ren ci a •
Polimorfismo
Las siguientes secciones definen cada uno de estos términos con detalle. P erso n + F irs tN a m e : S tring + G o T o W o rk () : void + E a t() : void + G o T o S le e p () : void
P erso n 1 : P e rso n
P e rso n 2 : P erso n
F irs tN a m e : S trin g = A lic e
F irs tN a m e : S tring = B ob
Figura 8.1. E s t a c l a s e p e r s o n a t i en e d o s o b j e t o s p e r s o n a .
Abstracción Es importante darse cuenta de que el objetivo de la pro gr amaci ón no es r epr o ducir t odos los aspect os posibles del mundo real de un concepto dado. Por ej em plo. cuando se p r o gr a ma una elase P e r s o n . no se intenta model ar todo lo que se conoce sobre una persona. En su lugar, se t rabaja dentro del contexto de la apl ica ción específica. Sólo se modelan los elementos que son necesarios p a r a esa a p li cación. Algunas caracterí st icas de una persona, como la nacionalidad, pueden existir en el mundo real, pero se omiten si no son necesarias p ar a la aplicación en cuestión. Una persona en una aplicación banc ari a est ará interesada en aspectos diferentes de los de. por ejemplo, una persona en un juego de acción. Este con cep to recibe el nombre de a b stra cció n y es una técnica necesaria p ar a el manejo de los conceptos infinitamente complejos del mundo real. Por tanto, cua ndo se haga preguntas sobre objetos y clases, tenga siempre en cuenta que debe hacerse estas preguntas en el contexto de una aplicación específica.
Tipos de datos abstractos Los tipos de datos abs tra ct os fueron el pri mer intento de det erminar el modo en que se usan los datos en programaci ón. Los tipos de datos abst ract os se c r ea ron porque en el mun do real los datos no se componen de un conjunto de v a r i a
193
bles independientes. El mundo real está c ompuest o de conjuntos de datos relacio nados. El estado de una persona p ar a una aplicación determinada puede consistir en. por ejemplo, nombre, apellidos y edad. Cu an do se quiere cr ear una persona en un progr ama, lo que se quiere crear es un conjunto de estas variables. Un tipo de datos abst ract os permite present ar tres variables (dos cadenas y un numero entero) como una unidad y t raba jar cómodamente con esta unidad par a contener el estado de una persona, como se ve en este ejemplo: struct Person
{ pub l i c public pub l i c
S tri ng FirstName; St ri n g LastName; int A g e ;
} Cuand o se asigna un tipo de datos a una variable del código, se puede usar un tipo de datos prim itivo o un tipo de datos abstracto. Los tipos de datos primitivos son tipos que CU reconoce en cuanto se instala. Ti pos como int. long. char y string son tipos de datos primitivos de C#. Los tipos de datos abst ract os son tipos que C# no admite cuando se instalan Antes de poder us ar un tipo de datos abs tra ct o hay que declararlo en el código Los tipos de datos abst ract os se definen en el código en lugar de hacerlo en el compi lador de CU. Por ejemplo, imagine la est ructur a (o clase) Person. Si escribe código CU que usa una estructura (o clase) Person sin escribir código que le indique al compilador de C# a qué se parece una est ructura (o clase) Person y cómo se comport a, se obtiene un error de compilación. Observe el siguiente código: class
MyClass
{ s ta t i c
voicl M a i n ()
{ Pe rso n
M y Pe rso n;
P e r s o n .F i r s t N a m e
=
"Malgoska";
} } Si se compi la este código el compi lador de C# genera el siguiente error. error CS0234: El tipo 'Person' no existe en 'MyClass' (¿falta una
o el nom b r e del e s p a c i o de n omb res la clase o el e s p a c i o de n ombres r e f e r e n c i a de ensamblado?)
El probl ema de este código es que el tipo de datos Person no es un tipo de datos primitivo y no está definido por el lenguaje CU. Como no es un tipo pr imi tivo. el compi lador de CU considera que se t rata de un tipo de datos abst ract o y revisa el código buscando la declaración de un tipo de datos Person. Sin e m ba r go. como el compi lador de CU no puede en co nt rar información sobre el tipo de
194
datos abstracto Person genera un error. Tra s definir un tipo de datos abstracto, puede usarse en el código C # exact ament e igual que si fuera un tipo de datos primitivo. Las est ructur as y las clases de C# son ejemplos de tipos de datos abstractos. Una vez que se ha definido una est ructura (o clase), se pueden u sar las v a r i a b l e s de ese t i p o d e n t r o de o t r a e s t r u c t u r a (o cl as e ). L a e s t r u c t u r a LearningUnit. por ejemplo, contiene dos variables Person: struct
LearningUnit
{ public public
Person Person
Tutor; Student;
}
Encapsulación Mediante la encapsulación. los datos se ocultan, o se encapsulan. dentro de u na clase y la clase implementa un diseño que permite que otras partes del código accedan a esos datos de for ma eficiente. Imagine la encapsulación como un envol torio protector que rodea a los datos de las clases de C#. Cómo ejemplo de encapsulación. observe la es tructur a Point con la que t rabajó en un capítulo anterior. struct
Point
{ public public
int X; int Y;
} Los miembros de datos de esta es tructur a son públicos, lo que permite que cualquier parte de código que acceda a la e st ru ctur a acceda también a los m i em bros de datos. C omo cualquier parte de código puede acceder a los miembros de datos, el código puede asi gnar a los valores de los miembros de datos cualquier valor que pueda representarse en un valor int. No obstante, puede surgir un p robl ema al permitir que los clientes asignen los valores de los miembros de datos directamente. Supongamos que se usa la es truc tura Point par a representar una pantalla de ordenador con una resolución de 800 x 600. En ese caso, sólo tiene sentido permitir al código que asigne a X valores entre 0 y 800 y a Y valores entre 0 y 600. No obstante, con el acceso público a los miembros de datos, no hay nada que impida al código asi gnar a X el valor 32. 000 y a Y el valor 38.000. El compi lador de C# lo permite porque esos valores son posibles en un entero. El prob lema es que no tiene sentido permitir valores tan elevados. La encapsul aci ón resuelve este problema. Básicamente, la solución está en m ar c ar los mi embros de datos como privados, par a que el código no pueda a c c e der a los miembros de datos directamente. A continuación puede escribir métodos en una clase de punt o como SetX () y SetY (). Los métodos SetX () y SetY ()
195
pueden a si gnar los valores y también pueden contener código que genere un error si se trata de llamar a SetX ( ) o a SetY ( ) con par ámet ros con valores demasiado grandes. La figura 8.2 muest ra el posible aspecto de una clase Point. P oint -X : int -Y : int + S e tX () : bool + S e tY () : bool + G e tX () : int + G e tY () : int
Figura 8.2. Las variables de miembros en la clase Point están encapsuladas
NOTA: El signo meaos delante de los miembros de datos es una notación UML que indica que los miembros tienen una visibilidad privada. El signo más delante éé tos miembros de datos es una notación UML que indica que tos miembros tienen una visibilidad pública. La técnica consistente en m ar c ar como privados los miembros de datos resuel ve el probl ema de que el código establezca sus valores directamente. Si los m i em bros de datos son privados, sólo la propia clase puede verlos y cualquier otro código que intente acceder a los miembros de datos genera un error del compilador. En lugar de acceder directamente a los mi embros de datos, la clase declara métodos públicos llamados SetX ( ) y SetY ( ) . El código que quiere asi gnar los valores de los puntos X e Y llama a estos métodos públicos. Estos métodos p u e den ac ept ar el nuevo valor de las coordenadas y un parámet ro, pero también pueden c o m pr o ba r los nuevos valores p ar a as egurars e de que están dentro de los límites adecuados. Si el nuevo valor está fuera de los límites, el método devuelve un error. Si el nuevo valor está dentro de los límites, el método puede establecer un nuevo valor. El siguiente seudo código mu est ra cómo se puede i mplement ar el m ét o do SetX (): bool
SetX(int
NewXValue)
{ íf(NewXValue ís out return false; X = NewXValue; return true;
of range)
} Este código ha e nc aps ul ado el mi embro de datos de la c oor denada X y p er mi te a los invocadores as ignar su valor a la vez que impide que le asignen un valor no válido. Cómo los valores de las coor denadas X e Y en este diseño son privados, las otras partes de código no pueden exa mi na r sus valores actuales. La accesibilidad
196
p ri vada en la prog ramaci ón orientada a objetos evita la llamada lean el valor actual o guar den el nuevo variables privadas, se pueden implement ar métodos que devuelven el valor actual de las coordenadas. En este diseño, la clase enc ap sul a los valores de otras partes del código lean y escriban sus valores. La ot ra ventaja: el método que realiza el acceso impide surdos a los mi embros de datos.
que las partes que realizan valor. Para exponer estas como GetX () y GetY () X e Y aunque permite que encapsulación proporciona que se asignen valores a b
Herencia Algunas clases, como la clase Point. se diseñan partiendo de cero. El e st a do y los co mpo rt ami ent os de la clase se definen en ella misma. Sin embargo, otras clases toman prestada su definición de otra clase. En lugar de escribir otra clase de nuevo, se pueden t omar el estado y los comport ami ent os de otra clase y usarlos como punto de par ti da par a la nueva clase A la acción de definir una clase usando otra clase como punto de parti da se le llama herencia.
Herencia simple S up on gamos que estamos escribiendo código usando la clase Point y nos damos cuenta de que necesitamos t r aba j ar con puntos tridimensionales en el m is mo código. La clase Point que ya hemos definido modela un punt o en dos dimensiones y no podemos usarlo p a r a definir un punt o tridimensional. Decidi mos que hace falta escribir un punt o tridimensional llamado Point 3D. Se puede diseñar la clase de una de estas dos formas: Se puede escribir la clase Point3D par ti endo de cero, definiendo m i e m bros de datos llamados X. Y y Z y escribiendo métodos que lean y escriban los mi embros de datos. Se puede derivar de la clase Point. que ya implementa los mi embros X e Y. Heredar de la clase Point propor ci ona todo lo necesario par a trabaj ar con los mi embros X e Y. por lo que todo lo que hay que hacer en la clase Point3D es i mplementar el mi embro Z. La figura 8.3 mues tra el aspecto que podría tener la clase Point3D en UML.
NOTA: l& notación UML que índica que ios miembros tienen visibilidad protegida es el sútnbolo de ¿ libra delate dé los miembros de datos.Visibifí^ad protegida sijpjiB¿a que la visi&ttidad es pública para las ciases deri vadas y privadas para todas las demás clases. La figura 8.3 refleja la herencia simple. La herencia simple permite que una clase se derive de una sola clase base. Este tipo herencia también es conocido
197
como derivar una clase de otra. Parte del estado y del comport ami ent o de la clase Point3D se derivan de la clase Point. En este caso, la clase u sa da como punto de par ti da recibe el nombre de clase base y la nueva clase es la clase derivada. En la figura 8.3 la clase base es Point v la clase derivada es Point3D. Point #X : int #Y : int +SetX() +SetY() +GetX() +GetY()
: bool : bool : int : int
Po¡nt3D -Z : ¡nt +GetZ() : ¡nt +SetZ() : bool Figura 8.3. La clase P o m t 3 D hereda los m étodos y las variables de la clase Point.
Derivar una clase de otra aut omát icame nte permite que los miembros de datos públicos (y protegidos) y a los métodos públicos (y protegidos) de la clase base estén di sponibles par a la clase derivada. Como los métodos GetX ( ) .GetY ( ) . Set X ( ) y Set Y ( ) están ma rc a do s como públicos en la clase base, están aut omáticamente disponibles par a las clases derivadas. Esto significa que la clase Po i nt 3D dispone de los métodos públicos GetX ( ) . GetY ( ) . SetX ( ) y Set Y ( ) ya que se derivaron de la clase base Point. Una sección de codigo puede crear un objeto de tipo Point 3D y llamar a los métodos GetX ( ) . GetY ( ) . SetX () y SetY (). aunque los métodos no esten implementados explícitamente en esa clase. Se derivaron de la clase base Point y pueden ser usados en la clase derivada Point3D.
Herencia múltiple Algunos lenguajes orientados a objetos también permiten la herencias m ú lti ple. lo que permite que una clase se derive de más de una clase base. C# solo permite herencias simples. Esta restricción se debe a que el C L R de N E T no admite clases con varias clases base, principalmente porque otros lenguajes N ET . como Visual Basic, no admiten p or sí mismos herencias múltiples. Los lenguajes que admiten herencia múltiple, como C++. también han ev idenciado la dificultad de us ar correctamente la herencia múltiple.
198
N01TÁ: Si 'sje; quiere usar la herencte otís'é' fU,é¿pReí o j h(?rsde tes
tención.kLa contención i^ru?t^ui^variable se qwfere derivar. Bá'leiíí iiiüy D esp ertador delega la funcionalidad eni$;ykiiabl# miembro fcéi* se fuede utilizar en para simular herencias múltiples en .NET (a
y
_
Polimorfismo La herencia permite que u na clase derivada redefina el comport ami en to e s pe cificado par a una clase base. S up ongamos que creamos una clase base Animal. Hacemos esta clase base abs t ra c ta porque queremos codificar animales genéricos siempre que sea posible, pero también crear animales específicos, como un gato y un perro. El siguiente fr agmento de código mues tra cómo declarar la clase con su método abstracto. Abstract
class Animal
{ public
abstract
void MakeNoise () ;
} Aho ra se pueden der ivar animales específicos, como Cat y Dog, a partir de la clase base abst ract a Animal: class
Cat
: Animal
{ public
override
void M a k e N o i s e ()
{ Consolé.WriteLine("Me o w !") ;
} } class
Dog
: Animal
{ public
override
void M a k e N o i s e ()
{ C o n s o l é . W r i t e L i n e (" W o o f !");
} } O b s e r v e qu e c a d a c l a s e t i en e su p r o p i a i m p l e m e n t a c i ó n del m é t o d o MakeNoise (). Ahora la situación es la indicada p ar a el polimorfismo. Como se apreci a en la figura 8.4. tenemos una clase base con un método que está a n ul a
199
do en dos (o más) clases derivadas. El p o lim o rfism o es la c apaci dad que tiene un lenguaje orientado a objetos de l lamar correctament e al método anul ado en f u n ción de qué clase lo esté llamando. Esto suele producirse cuando se al mac ena una colección de objetos derivados. El siguiente fragmento de código crea una colección de animales, que recibe el a pr opia do nombre de zoo. A continuación añade un perro y dos gatos a este zoo. ArrayList Zoo; Zoo - new A r r a y L i s t (3); Cat Sasha, Koshka; Sasha = new C a t (); Koshka - new C a t (); D o g Milou; Milou ■= new Dog (! ;
Figura 8.4. Este ejemplo de herencia muestra una clase base y dos clases derivadas.
La coleccion zoo es una colección polimórfica porque todas sus clases derivan de la clase abstracta A n i m a l . Ahora se puede iterar la colección y hacer que cada animal emita el sonido correcto: f or e a c h
(An ima 1 a m
Zoo)
{ a . M a k e N o i s e ();
} Si ejecuta el codigo anterior p roducir á el siguiente resultado: Wo o f ! Me ow ! Me ow !
200
¿Que está p as and o? C om o C# permite el pol imorfismo, en el tiempo de ej ecu ción el p r o gr a ma es lo suficientemente inteligente como p a r a l lamar a la versión perro de MakeNoise cuando se obtiene un perro del zoo y la versión gato de MakeNoise cuando se obtiene un gato del zoo. El listado 8.1 mu es tr a el código C # completo que explica el polimorfismo. Listad o 8.1. Las clases Cat y Dog evidencia el polim orfismo using using
System; System.Collections ;
namespace
PolyMorphism
{ abstract
class
Animal
{ public
abstract
void MakeNoise () ;
} class Cat
: Animal
{ public
override
void M a k e N o i s e ()
{ C o n s o l e . W r i t e L i n e (" M e o w !");
class
Dog
: Animal
{ public
override
void MakeNoise
{ C ons o l e . W r i t e L i n e ( " W o o f ! ");
} } class
PolyMorphism
{ static
int M a i n (s t r i n g [] args)
{ ArrayList Zoo; Zoo = new A r r a y L i s t (3); Cat Sasha, Koshka; Sasha = new Cat () ; Koshka = new Cat () ; Dog M i l o u ; Milou = new Dog(); Z o o .Add(Mi 1o u ) ; Z o o .A d d (K o s h k a ); Z o o .A d d (S a s h a );
201
foreach
(Animal
a in Zoo)
{ a . M a k e N o i s e ();
} // espera a que el usuario acepte los resultados Consolé. W r i t e L m e ("Hit Enter to termínate ..."); C o n s o l é .Read () ; r e t u rn 0 ;
Resumen C# es un lenguaje orientado a objetos y los conceptos que se usan en los lenguajes orientados a objetos también se aplican a C#. Los tipos de datos abstractos son tipos de datos que se definen en el propio código. Los tipos de datos abst ract os no forman parte de C#. a diferencia de los tipos de datos primitivos. Se pueden usar tipos de datos abstractos en el código de la mi sma for ma que los tipos de datos primitivos, pero sólo después de haberlos definido en el código. La encapsulacion es el acto de diseñar clases que proporcionen un conjunto completo de funcionalidad a los datos de las clases sin exponer directamente esos datos. Si se quiere escribir codigo que evite que otras partes del código pro po rci o nen valores no válidos a la clase, la encapsulación permite "ocultar" los miembros de datos al hacerlos privados, mientras crea métodos que acceden a ellos v e s t a blecen los valores de los mi embros de datos como públicos. Otros fragment os de codigo pueden l lamar a los métodos, que pueden c o mp r ob a r los valores y emitir un error si los valores no son apropiados. La herencia permite definir una clase por medio de otra. Las clases deriv adas heredan de la clase base. Las clases deriv adas aut omát icamente heredan el estado y los comport ami ent os de la clase base. Se puede usar la herencia para añadir nuevas funciones a clases ya existentes sin necesidad de rescribir desde cero una clase completamente nueva. Algunos lenguajes orientados a objetos permiten t a n to herencias simples como herencias múltiples, aunque C# solo permite la her en cia simple. El p o lim o rfism o permite t rat ar de forma uniforme a una colección de clases derivadas de una sola clase base. Las clases derivadas se recuperan como una clase base v C# aut omát icame nte llama al método correcto en la clase derivada. Este capítulo t rata los conceptos orientados a objetos en general, sin estudiar como se usan estos conceptos en el código C#. El resto de los capítulos de esta parte del libro explican como se i mplementan estos conceptos en C#.
202
Clases de C#
El diseño de C# está muy influido por los conceptos del desarrollo de software orientado a objetos. C omo la clase es la parte fundamental del software orientado a objetos, no es de sorprender que las clases sean el concepto más importante de C#. Las clases en el mundo del desarrollo de s oftware orientado a objetos contie nen codigo y datos. En C#. los datos se implementan usando mi embros de datos y el código se i mplementa us ando métodos. Los m iem bros de datos son cualquier elemento al que se pueda p as ar datos dentro y fuera de la clase. Los dos princi pales tip os de mi embros de datos son los ca mpos y las propiedades. En el código C# se pueden escribir tantos mi embros de datos y métodos como se desee, pero todos deben incluirse en una o varias clases. El compi lador de C# emite un error si se intenta definir al guna variable o se implementa algún método fuera de una definición de clase. Este capítulo t rata de los fundament os de la construcción de clases. Explica cómo crear constructores y destructores, añadir métodos y m ie m bros. además de a us ar clases después de crearlos.
Cómo declarar una clase La acción de declarar una clase resulta similar a declarar una estructura. La principal diferencia reside en que la declaración empi eza con la pa la br a clave de
205
C# class y no con struct. Aun qu e ya hemos visto una definición de clase en anteriores capítulos, vamos a revisar el diseño de una declaración de clase: Modificadores de clase opcionales La palabra clave class Un idcntificador que da nombre a la clase Información opcional de la clase base El cuerpo de la clase Un punt o y coma opcional La declaración de clase mí nima en C# es p ar eci da a lo siguiente: class
MyClass
{ } Las llaves delimitan el cuerpo de la clase. Los métodos y variables de clase se colocan entre las llaves.
El método Main C ad a aplicación de C# debe contener un método llamado Main. Esto es el punt o de e nt rada p a r a que la aplicación se ejecute. Se puede colocar este método dentro de cualqui er clase del proyect o porque el c ompi lador es lo b as tant e inteli gente como p a r a buscarl o cuando lo necesite. El mét odo Main debe cumplir dos requisitos especiales p a r a que la aplicación funcione correctamente. En primer lugar, el método debe declararse como public. Esto as egu ra que se pueda acceder al método. En segundo lugar, el método debe declararse como static. La pal abr a clave static as e gu ra que sólo se puede abrir una copia del método a la vez. Teni endo en cuenta estas reglas, obser vemos este código: class
Class1
{ public
static void Main()
{ } } Como puede apreciarse, este ejemplo contiene una clase llamada Class 1. Esta clase contiene el método Main de la aplicación. Es en este método Main donde se coloca todo el código necesario p ar a ejecutar su aplicación. Aunque es correcto colocar este método en la misma clase y el mismo archivo que el resto del código de la aplicación, conviene cr ear una clase y un archivo s eparados p ar a el
206
método Main. Esta operación sirve de ayuda a los demás prog ramad ore s que quieran t rab aj ar con este código.
Cómo usar argumentos de línea de comandos Mucha s de las aplicaciones de la p lat af orma W indows aceptan par ámet ros de línea de comandos. Para acept ar p ar ámet ro s de línea de comandos dentro la a pl i cación C#. es necesario declarar una matriz de cadenas como único par amet ro del m ét o do Main, como se puede a pr eci ar en el siguiente código: class
Classl
{ public
static void M a i n ( s t r i n g []
args)
{ foreach (string arg i n args) System.Cons ole.WriteLine (arg) ;
} } Aquí, el método Main está contenido en una clase típica. Observe que el m ét o do Main tiene un p ar ám et ro definido, una matriz de c adenas que será a l m a cenada en la variable args. Usa el comando foreach para recorrer todas las cadenas almac enada s déla matriz args y a continuación se escriben esas c a d e nas en la consola. N O T A : Si se está usando Visual Studio .NET para programar C#, la ma triz de cadenas se añade automáticamente cuando se crea una aplicación de consola. Si se ejecuta la aplicación anterior sin parámetros, no ocurrirá nada. Si se ejecuta una aplicación como la siguiente: Sampleap.exe
parameterl
p a r a m e t:er 2
el resultado será algo como esto: pa r a m e t e r J par ame t e r 2
Los ar gument os de línea de c omando son un modo excelente de prop or ci on ar modificadores a la aplicación; por ejemplo, par a activar un archivo de registro mientras se ejecuta la aplicación.
Cómo devolver valores C uando se crea una aplicación, suele ser útil dev olver un valor a la aplicación que la inició. Este valor indica al p rog rama que hace la llamada o a la secuencia
207
de comandos por lotes si el p ro g ra ma se ejecutó con éxito o falló. P ar a ello, basta con asignar al método Main un valor de devolución int en lugar de void. C uand o el método Main está listo par a finalizar, sólo hay que usar la pal ab ra clave return y un valor que devolver, como se puede apr eci ar en el siguiente ejemplo: class
Classl
{ public
static
int M a i n ( s t r i n g [] args)
{ íf
(args [0]
return
1;
return
0;
==
"fail")
} } Esta aplicación acepta un par ámet ro f a i l o cualquier otra pal abr a (quizás,
success). Si la palabra fail se p as a a esta aplicación como parámetro, el p r o gr a ma devuelve el valor 1, lo que indica un fallo en el programa. En caso contrario, el p r o gr a ma devuelve 0. lo que indica que el p r o gr a ma finalizó con normalidad. Se puede c om p r o b a r el funcionamiento del p ro g r a m a simplemente haciendo que un archivo p or lotes ejecute la aplicación y que luego realice a l g u nas acciones dependiendo del código devuelto. El siguiente código es un simple archivo por lotes que realiza esta función: @echo off retval.exe goto
success
a n s w e r :errorlevel
:answe r 0 echo Program had goto end
return code
0
(Success)
:answe r 1 echo Program had return code goto end
1
(Failure)
:end echo D o n e !
En el código anterior se puede apr eci ar que se llama al p ro g ra ma con un par ámet ro success. Al ejecutar este p ro g ra ma por lotes, aparece el siguiente mensaje: Program had
return code
0
(Success)
Si se edita este p ro gr am a por lotes par a que pase la pal ab ra fail como parámet ro, entonces a pa rec er á un mensaje que confirma que el p ro gr ama terminó con un código de salida 1.
208
El cuerpo de la clase El cuerpo de la clase puede incluir instrucciones cuya función se incluya en una de estas categorías. Constantes Campos Métodos Propiedades Eventos Indizadorcs Operadores •
Constructores Destructores Tipos
Cómo usar constantes Las constantes de clase son variables cuyo valor no cambia. El uso de c ons t a n t e s p e r m i t e q ue el c ó d i g o r e s u l t e m á s l egi bl e p o r q u e p u e d e n i n cl u i r identificadores que describan el significado del valor a cualquiera que lea el códi go. Se puede escribir un código como el siguiente: if (Ratio == 3.14159) System.Console.WriteLine ("Shape
ís
a circle");
El compilador de C# acept a perfectamente este código, pero puede ser un poco difícil de leer, especialmente p ar a alguien que lea código por pr imer a vez y se pregunte qué es el valor de coma flotante. Si se le da un nombre a la constante, por ejemplo pi, se puede escribir el siguiente código: i f (Rat io == P i ) System.Console.WriteLine("Shape
ís
a circle") ;
El uso de constantes presenta otras ventajas: Se le da valor a las constantes cuando se las declara y se usa su nombre, no su valor, en el código. Si por al guna razón es necesario ca mbi ar el valor de la constante, sólo hace falta cambi arl o en un sitio, donde se declara la constante. Si se escribe en las instrucciones de C# el valor real, un cambio en el valor significaría que h abr ía que revisar todo el código y c amb ia r ese valor.
209
Las constantes indican cspecificamcntc al compi lador de C# que. a dife rencia de las variables normales, el valor de la constante no puede c a m biar. El compilador de C# emite un error si algún código intenta ca mbi ar el valor de una constante. La es tructur a de la declaración de una constante es la siguiente: La pal abra clave c o n s t Un tipo de dato par a la constante Un identificador Un signo igual •
El valor de la constante
La definición de una constante p ar a pi sería: class M y C l a ss
I public
const
public
stat i c
double vo i d
Pi
-
3.141592 6 5 3 5 8 9 7 9 3 2 3 8 4 6 2 6 433832795;
M a i n ()
{ } }
NOTA: Este capítulo usa la palabra clave p u b l i c para indicar que los elementos del cuerpo de clase están disponibles para el resto del código de la aplicación. Posteriormente se examinan algunas alternativas a la palabra clave p u b l i c que pueden usarse cuando se hereda una clase de otra. C# permite colocar varias definiciones de constantes en la misma linea, s iem pre que todas las constantes tengan el mismo tipo. Se pueden s eparar las defini ciones de constantes con una coma, como en el siguiente ejemplo: •/ia s s M y C 1ass pub i i
const
mt
One
=
1,
Two
= 2,
Th ree
= 3;
p 11b 1 ic s t a t ic v o i d M a i n O
i ) }
Cómo usar campos Los cam pos son miembros de datos de una clase. Son variables que pertenecen a una clase v. en términos de progr amaci ón orientada a objetos, gestionan el
210
estado de la clase. Se puede acceder a los c ampo s definidos en una clase m e diante cualqui er método definido en la mi s ma clase. Un c a mp o se define exact ament e igual que cualqui er variable. Los campos tienen un tipo y un i dent ifi cados Tambi én se puede definir un c a mpo sin un valor inicial: class
MyClass
{ public
int
Fieldl;
public
static void M a i n ()
{ } } T amb ién se puede definir un ca mp o con un valor inicial: class
MyClass
{ public
int
Fieldl
public
static
= 123;
void M a i n ()
{ } } Para indicar que los campos son de sólo lectura escriba antes de la declaración la pal abr a clave r e a d o n l y . Si usa la pal ab ra clave r e a d o n l y . deberá escri birla j us t o antes del tipo del campo: class
MyClass
{ public
readonly
int
Fieldl;
public
static void M a i n ()
{ } } Se puede establecer el valor de un c a mpo de sólo lectura al dec la rar el campo o en el const ructor de la clase. Su valor no puede establecerse en ningún otro momento. Cual qui er intento de ca mb ia r el valor de un ca mpo de sólo lectura es detectado por el compi lador de C# y devuelve un error: error CS0191: No se puede asignar un campo de solo lectura (excepto en un constructor o inicial izador de variable)
Los campos de sólo lectura se parecen a las constantes en la medida en que se inicializan cuando se crea un objeto de la clase. La principal diferencia entre u na constante y un ca mpo de sólo lectura es que el valor de las constantes debe establecerse en tiempo de compilación; en otras pal abr as, deben recibir su valor
211
cuando se escribe el código. Los ca mpos de sólo lectura permiten es tablecer el valor del c a mpo en tiempo de ejecución. Como se puede a si gnar el valor de un ca mpo de sólo lectura en un cons tructor de clase, se puede determinar su valor en tiempo de ejecución y establecerlo cuando el código se está ejecutando. Supongamos , por ejemplo, que estamos escribiendo una clase de C# que g e s tiona la lectura de un grupo de archivos de una base de datos. Quizás queramos que la clase publique un valor que especifique el número de archivos que hav en la base de datos. Una constante no es la elección correcta par a este valor porque el valor de una constante debe establecerse cuando se escribe el código y no podemos saber c u á n tos archivos contiene la clase hasta que se ejecute el código. Podemos poner estos datos en un campo cuyo valor puede establecerse después de que la clase haya empez ado a ejecutarse y se puede det erminar el n umero de archivos. C omo el numero de archivos no cambi a d urante la existencia de la clase, quizás queramos señalar el campo como de solo lectura p ar a que los otros fragmentos de código no puedan c ambi ar su valor.
Cómo usar métodos Los m étodos son bloques de codigo con nombre en una clase. Los métodos proporci onan los comport ami ent os de las clases. Pueden ejecutarse por cualquier otro fragment o de código en la clase y. como se expl icara en un capitulo p os te rior. también pueden ejecutarlos otras clases.
Cómo usar propiedades En un capitulo anterior se exami na la est ructur a Point que puede describir un punto en una pantalla con una resolución de 640 x 480 píxeles. Un modo de implementar esta est ructur a es definirla con miembros de datos públicos que d es criban las coordenadas del punto: st.ruct
Point
( pubi ic int X ; public int Y;
} Al u s a r una e s t r u c t u r a co mo ésta, los clientes p ueden u s a r la s int axi s struct urename field para t rabaj ar con uno de los campos de la estructura: Point MyPoint M y P o i n t .X =
= new P o i n t ();
10 0;
Los clientes pueden escribir fácilmente este código. Los clientes pueden a c c e der al ca mpo directamente y usar su valor en una expresión.
212
El p robl ema de este enfoque es que los clientes pueden est abl ecer un campo que C# permite, pero que no es logico par a el código. Por ejemplo, si esta gest io nando una est ructura P o i n t que describe un punt o en una pantalla de 640 \ 480 pixeles. el valor lógico más alto par a X debería ser 639 (suponiendo que los valores legales de X están entre 0 y 639). Sin embargo, como se especifica que la coor denada X es un tipo i n t . los clientes pueden establecer como valor cualquier valor a ut ori zado dentro de los límites de los tipos enteros: MyPoint.X
= -bOO;
MyPoint.X
= 9000;
El compi lador C# acept a este código porque los valores que se asignan a X están dentro de los límites válidos par a los v alores enteros. Una solución a este probl ema es hacer que los valores sean privados, con lo que serían inaccesibles p ar a otros fragmentos de código y luego añadir un método publico a la es tructur a que establece los valores de X: struct
Point
{ p r í v a t e i n t X; p r í v a t e i n t Y; public
bool
SetX(int
NewXValue)
{ íf ( (NewXValue < 0) return false; X = NewXValue; return true;
¡ | (NewXValue
• 63 9) )
La ventaja de este enfoque es que obliga a los clientes que quieran as ignar un v alor a X a llamar al método para realizar la tarea: Point MyPoint
= new Point () ;
M y P o i n t .SetX (100) ;
La ventaja del método es que se puede escribir código que valide el nuevo valor antes de que se al macene realmente en el c a mp o y el código del método puede rechazar el nuevo valor si. por lógica, no es adecuado. Así pues, los clien tes llaman al método par a establecer un nuevo valor. Aunque este enfoque funciona, llamar a un método par a asi gnar un valor re quiere un poco mas de código que as ignar un valor directamente. Para el código es mas natural asignar un valor a un campo que llamar a un método para que lo asigne. En el mejor de los casos, quer remos lo mejor de los dos elementos, que los clientes puedan leer y escribir directamente los valores de los ca mpos usando
213
instrucciones de asignación simples, pero también querremos que el código inter venga por ant ici pado y haga todo lo necesario p a r a obtener el valor de un campo o validar el nuevo valor antes de que se asigne. Afor tunadament e, C# ofrece esta posibilidad con un concepto de clase llamado p ro pieda d. Las propiedades son mi embros identificados que pr opor ci onan acceso al e s ta do de un objeto. Las propiedades tienen un tipo y un identificador y tienen uno o dos fragment os de código asociados a ellos: una base de código get y una base de códi go set. Estas bases de código reciben el nombre de descriptores de a cc e so. C uan do un cliente accede a u na propiedad se ejecuta el descriptor de acceso get de la propiedad. Cu an do el cliente establece un nuevo valor p a r a la p rop ie dad se ejecuta el descri ptor de acceso set de la propiedad. Para mos trar cómo funcionan las propiedades, el listado 9.1 usa una clase Point que expone los valores de X e Y como propiedades. L istad o 9.1. Valores Point como propiedades de clase class
Point
{ prívate prívate public
int int
XCoordmate; YCoordmate;
int X
{ get
( return
XCoordmate;
} set { í f ((valué >= 0) && (valué < 640)) X C o o r d m a t e = valué;
} } public
int Y
{ get
{ return
YCoordmate;
} set { íf ( (valué >= 0) && (valué < 48 0)) YCoordinate = valué;
} } public
static
void M a i n ()
{ Point MyPoint
214
= new P o i n t ();
MyPoint.X = 100; M y Point.Y = 2 00; System.Console . W r i t e L m e ( M y P o m t .X ) ; S y s t em .C on so le .W ri te Li ne (M yP oi nt .Y ); MyPoint.X = 600; My Po in t. Y = 600 ; System.Console.WriteLine ( My Po m t . X ) ; System. Con s o l e . W r i t e L m e (MyPoint. Y ) ;
Este código dcclara una clase Point con los valores X e Y como p r op i ed a des. El método Main ( ) crea un nuevo objeto de la clase Point y accede a las propiedades de la clase. La clase Point define dos ca mpos privados que contienen los valores de las coor denadas del punto. C omo estos ca mpos son privados, el código que se e n cuentra fuera de la clase Point no puede acceder a sus valores. La clase también define dos propi edades públ icas que permiten que otros fragment os de código trabajen con los v alores de las coordenadas del punto. Las propiedades son publ i cas y pueden ser usadas por otras partes del codigo. La propiedad X es la p rop ie dad pública que gestiona el valor del campo privado XCoord ina te y la propiedad Y es la propiedad pública que gestiona el valor del campo privado YCoordi nate. Las dos propiedades tienen un descriptor de acceso get y otro set. La figura 9.1 muest ra el resultado del listado 9.1. CV
C:\WINDOWS\System32\cmd.exe
C:\> L is t 100 200 600 200
-ini
in g 9 ~ l. exe
C :\> _
Figura 9.1. Las propiedades de clase ayudan a almacenar los valores del punto
Descriptores de acceso get Los descriptores de acceso get solamente devuelven el valor actual del campo correspondiente: el descri ptor de acceso de la propiedad X devuelve el valor ae-
215
tual de Xcoordinate y el descri ptor de acceso de la propiedad Y devuelve el valor actual de Ycoordinate. Cada descriptor de acceso get debe devolver un valor que coincida o pueda ser convertido implícitamente al tipo de la propiedad. Si el descriptor de acceso no devuelve un valor, el comp il ad or de C# emite el siguiente mensaje de error: error CS0161: 'MyClass.Pr op e r t y .ge t ' : no todas codigo devuelven un valor
las
rutas
de
Descriptores de acceso set Los descriptores de acceso set del ejemplo son un poco más compl icados porque tienen que validar el nuevo valor antes de que sea realmente as ignado al campo asociado. Observe que el descriptor de acceso usa la p al ab ra clave valué. El valor de este identificador es el valor de la expresión que aparece después del signo de igualdad cuando se llama al descriptor de acceso set. Por ejemplo, examine la siguiente instrucción del listado 9. 1: MyPoint.X
= 100;
El c ompi lador de C# det ermina que la instrucción está asi gnando un nuevo valor a la propiedad X del objeto P o i n t . Ejecuta el descriptor de a cceso set de la clase p a r a realizar la asignación. Como se está asignando a la propiedad el valor 100. el valor de la p al abr a clave valué del descriptor de acceso set será 100. Los descriptores de acceso set del listado 9.1 asignan el valor al campo correspondiente, pero sólo si el valor está dentro de los limites válidos. En C#. no está permitido devolver valores de los descriptores de acceso.
4
la a sig id a c ió ji
ftó o ifr se ptíeilea ustr ó c c e p e i c | » t * íaíSrSjar de cüáiíqmer eitor. Estas excepciones sé estudian m posté* r i o r r
*
'
/
’* '
%
Propiedades de sólo lectura y de sólo escritura Las propiedades del listado 9.1 pueden leerse usando su descriptor de acceso
get y puede escribirse en ellas usando su descriptor de acceso set. Estas p r o piedades reciben el nombre de p r o p ied a d es de lectura y escritura. Cua nd o se diseñan las clases de C#. hay que i mplementar una propi edad de sólo lectura o de sólo escritura. En C#. esto es sencillo. Si hace falta i mplementar una propiedad de sólo lectura, se especifica una propiedad con un descri ptor de acceso get pero sin descri ptor de acceso set:
216
int X
{ ge t
{ retur n
XCoordmate;
} } Si hace falta implementar una propiedad de solo escritura, se especifica una propi edad con un descri ptor de acceso s e t pero sin descriptor de acceso g e t : int X
{ set { íí ( (valué
> = 0)
XCoordmate
&&
(valué
<
640))
= valué;
} }
Cómo usar eventos C# permite que las clases informen a otras partes del codigo c uando se p r o d u ce una acción sobre ella en la clase. Esta ca paci dad recibe el nombre de m e ca n is mo de evento x permite informar a los elementos que realizaron la llamada si se produce un evento en la clase de C#. Puede diseñar clases de CU que informen a otros fragment os de código c uando se pr odu zcan determinados eventos en la c l a se. La clase puede devolver una notificación de evento al fr agment o de codigo original. Quizás quiera usar un evento par a informar a otros fragmentos de códi go de que se ha compl et ado una operación muy larga. Suponga, por ejemplo, que quiere diseñar una clase de CU que lea una base de datos. Si la actividad sobre la base de datos va a requerir mucho tiempo, será mejor que otras partes del código realicen otras acciones mientras se lee la base de datos. Cu an do se completa la lectura, la clase de C U puede emitir un ev ento que indique "la lectura se ha c o m pletado". Tamb ién se puede i nformar a otras partes del código cuando se emita este ev ento y el codigo puede realizar la acción indicada c uando reciba el ev ento de la clase de C#.
Cómo usar indizadores Algunas de las clases pueden ac tu ar como contenedores de otros valores. Supongamos , por ejemplo, que estamos escribiendo una clase de CU llamada R a i n b o w que permite a los clientes acceder a los valores de cadenas que n o m bran los colores del arco iris en orden. Queremos que los elementos que realizan llamadas puedan retirar los valores de las cadenas, de modo que usamos algunos métodos públicos que permiten a los elementos que realizan llamadas tener acceso a los valores. El listado 9.2 muest ra un ejemplo de este tipo de codigo.
217
Listad o 9.2. Una clase con un conjunto de valores de cadena lass
Painbow
public
int
( return
7;
public
bool
GetNumberOfCol ors ()
GetColor( m t
Colorlndex,
out
string
{ bool
ReturnValue;
ReturnValue = true; s w i t c h (Colorlndex)
{ case 0: ColorName break;
= "Red";
case 1 : ColorName break;
= "Orange";
c a s e 2: ColorName break; case 3: ColorName break; case 4 : ColorName break; case 5 ; ColorName break;
=
"Yellow";
= "Green";
= "Bl ue ";
= "Indigo";
c a s e 6: ColorName = "Violet"; break; default: ColorName = "" ; ReturnValue = false; break;
} return
public
ReturnValue;
static
void M a i n ()
{ int ColorCount; int Colorlndex; Rainbow M y R a m b o w ColorCount
= new Rainbow () ;
= M y R a i n b o w .GetNumberOfCol ors () ;
ColorName)
string ColorName; bool Success; for (Colorlndex
= 0;
Colorlndex
< ColorCount;
Col orIndex + + )
{ Success = MyRainbow.GetColor(Colorlndex, C o l o r N a m e ); if(Success == true) System. Consolé . W n t e L m e (ColorName ) ;
out
} } } La clase Raínbow del listado 9.2 tiene dos métodos públicos:
GetCol orCount ( ) . que devuelve el número de colores de la clase •
GetColor ( ). que. bas án do se en un numero de color, devuelve el n o m bre de uno de los colores de la clase
El método Main ( ) crea un nuevo objeto de la clase Rainbow y pide al objeto el número de colores que contiene. A continuación se coloca en un bucle for y solicita el nombre de cada color. El nombre del color aparece en la co ns o la. El resultado de esta aplicación puede verse en la figura 9.2. cV C:\WINDOWS\System32\cmd.exe
-=!Ql
C:\>List ing9--2 .exe Red Orange Vellou Gi'eert Blue Indigo U iolet C:\>_
■ ■ M
M
—
—
Ld
Figura 9.2. Los indizadores recuperan los nombres de los colores.
La clase mantiene una colección de valores del mismo tipo de valor, lo que es muy parecido a una matriz. De hecho, esta clase Rainbow t ambi én puede i mplementarse como una matriz y el elemento que realiza la llamada puede usar los corchetes de la sintaxis del descriptor de acceso a la matriz de elementos para recuper ar un nombre de color en particular: ColorName
= Rainbow[ColorIndex] ;
219
Los indizadores permiten que se acceda a las clases como si fuera una matriz. Para especificar qué se debe devolver cuando el elemento que hace la llamada usa corchetes par a acceder al elemento de la clase, se usa un fragmento de código llamado descrip tor de acceso de indizador. Teniendo esto en cuenta, se puede reescribir la clase Rainbow para que permita a los elementos que la llaman acceder a los nombres de los colores u s a n do corchetes en su sintaxis. Se elimina el método GetColor () y se sustituye por un indizador. y se sustituye el método GetColorCount () por una p rop ie dad de sólo lectura llamada Count. como se muest ra en el listado 9.3. L istad o 9.3. Clase Rainbow con un indizador c ]a s s R a in bow
{ public
int Count
{ get f return
7;
} } public
string
this[int
Colorlndex]
{ get
{ switch(Colorlndex)
{ case 0 : return case 1 : return case 2 : return case 3: return case 4 : return case 5 : return case 6 : return default : return
public
static
"R e d "; "Orange" " Ye 11ow" "Gree n" ; "Blue"; "Indi g o " "Violet" "";
void M a i n ()
{ in t
220
Colorlndex;
Rainbow M y R a m b o w = new Rainbow () string
ColorName;
for(ColorIndex ColorIndex++)
= 0;
Colorlndex
< M y R a i n b o w .Count ;
I ColorName = M y R a i n b o w [C o 1orIndex ] ; System. Consolé . W n t e L i n e (ColorName) ;
} } } Los indizadorcs se parecen a las propi edades porque tienen descriptores de acceso get y set. Si fuera necesario, se puede omitir cua lqui era de estos descriptores de acceso. El indizador del listado 9.3 no tiene el descri ptor de a c c e so set, lo que significa que la clase se puede leer, pero no se puede escribir en ella. Los indizadorcs se es truc tu ran según los siguientes elementos: •
Un tipo que indica el tipo de datos que devuelve el descri ptor de acceso La palabra clave this. Un corchete de apertura. Una lista de parámet ros, es tructur ada igual que una lista de p ar ámet ros de un método. Un corchete de cierre. Un cuerpo de código, entre llaves.
El indizador del listado 9.4 recibe un ar gume nt o integral y devuelve una c ade na. de forma par eci da al método GetColor () del listado 9 2 L istado 9.4. Un argum ento entero que devuelve una cadena string
ColorName;
f or (Col or Index
= 0;
Colorlndex
< M y R a m b o w .Count ; Co 1 o r Index + + )
{ ColorName == MyRainbow [Col or Index ] ; System.Consolé.WriteLine (Col o r Ñ a m e ) ;
} El nuevo código us a los corchetes de la sintaxis del elemento de matriz para obtener el nombre de un color del o b j e t o M y R a i n b o w . Esto hace que el compilador de C# llame al código indizador de la clase, que pa sa el valor de Colorlndex como parámet ro. El indizador devuelve una cadena y la cadena se escribe en la consola.
221
Una clase puede i mplementar más de un indizador. siempre que los indizadores tengan diferentes listas de parámetros. Las listas de parámet ros de los indizadores pueden tener más de un par ámet ro y pueden ser de cualquier tipo que se pueda us ar en el código. No es necesario que los indizadores usen valores enteros como indizadores. Se podrí a haber implementado igualmente en la clase Rainbow un indizador que acept ase un valor double: publie
strmg
this [double
Colorlndex]
NOTA: Como en las propiedades, C# no permite que los descriptores de acceso s e t devuelvan valores. Sin embargo, pueden usarse excepciones para informar de cualquier error.
Cómo usar operadores Un o per ado r permite definir el comport ami ent o de la clase cuando se usa en una expresión con un oper ador unario o binario. Esto significa que se puede ampl iar el comport ami ent o de operadores predefinidos par a que se ajusten a las necesidades de las clases. Por ejemplo, la clase Point puede i mplement ar c ód i go que especifique que se pueden s u ma r dos objetos Point con el operador +. El resultado de la suma sera un tercer objeto Point cuyo estado es el resultado de s umar los otros dos puntos.
Cómo usar constructores Los co nstructores de estructura son métodos especiales que se ejecutan c u a n do se crea una v ariable del tipo de la estructura. Los constructores suelen usarse para inicializar una est ructur a con un estado conocido. Se pueden usar constructores en las clases de C# de la misma forma que las est ructuras. Se pueden definir tantos constructores como se desee, siempre que cada const ructor tenga una lista de parámet ros diferente. Se puede escribir una clase Point con constructores de la misma forma que se hizo con la estructura Poi nt. como se puede apr eci ar en el listado 9.5. Listado 9.5. Una clase Point con dos constructores d a s s P o int
{ p u b 1ic int X; publio int Y; public
Point ()
{ X - 0;
222
Y = 0;
} public
P o m t (int
InitialX,
int
InitialY)
{ X = ImtialX; Y = ImtialY;
)
public
static void M a i n ()
{ P o m t M y F i r s t P o m t = new Point(100, Point MySecondPoint = new P o m t {) ;
200);
( } La clase Point del listado 9.5 tiene dos campos públicos: X e Y. Tambi én i mplementa dos constructores. Uno de ellos no usa ningún p ar ámet ro y el otro usa dos parámetros. Los constructores de las clases de C# son muy parecidas a las e st ructuras de C#. Los constructores de clase no devuelven valores y su nombre debe coincidir con el nombre de la clase. La principal diferencia entre los c o ns tructores de estructura y los constructores de clase estriba en que los constructores de clase pueden implementar un const ructor sin parámetros, pero un constructor de es tructur a no puede hacerlo. Si se define una clase sin constructores. C# proporci ona un const ructor por defecto. Este constructor por defecto asigna a todos los campos de las clases sus valores por defecto. Si se inicializa con el signo igual cualqui er ca mp o de una clase, éste se inicializará antes de que se ejecute el constructor: class
Point
{ public public
int X = 100; int Y;
public
P o m t (int
ImtialY)
{ Y = I m t i a l Y + X;
} } En esta clase Point. una declaración de asignación inicializa el ca mpo X y el const ructor y inicializa el ca mp o Y. Al compilar este código, el compi lador de C# se as egu ra de que el c a mpo X se inicial ice en pri mer lugar.
Cómo usar destructores Las clases de C# pueden definir un destructor, que es un método especial que se ejecuta cuando el C L R (Ent orno común de ejecución) destruye los objetos de
223
la clase. Puede pens ar en los destructores como en lo contrario de los c on s tr uc tores: los constructores se ejecutan cuando se crean objetos y los destructores se ejecutan cuando el recolector de objetos no utilizados destruye los objetos. Este proceso tiene lugar de modo oculto sin consecuencias p ar a el progr amador. Los destructores son opcionales. Es perfectamente válido escribir una clase de C# sin un dest ructor (y hasta ahora, es lo que hemos estado haciendo en los ejemplos). Si se escribe un destructor, sólo se puede escribir uno.
NOTA: A diferencia de los constructores, no se puede definir más de un destructor para una clase. Los destructores se disponen de la siguiente forma. El símbolo virgulilla (~). El identificador del destructor, que debe cor responder al nombre de la clase. Un conjunto de paréntesis. El listado 9.6 actualiza la clase Point del listado 9.5 con un destructor. Listado 9.6. Una clase point con un destructor class Point
{ public public
int. X; int Y;
publie
P o m t ()
{ X - 0; Y = 0;
} public
Point (int
InitialX,
{ X = I nitialX; Y = In 1 1 1 a 1Y ;
} - P o i n t () { X - Ü; Y - 0;
} public
224
st.atic void Main ()
int
ImtialY)
Point MyFirstPoint = new Point(100, 200); Point M y S e c o n d P o m t = new Point ();
} } Los destructores no devuelven valores; ni tampoco pueden aceptar parámetros. Si se intenta llamar a un dest ructor en el código, el compi lador de C# emite un error. En muchos lenguajes orientados a objetos, se llama a los destructores de clase cuando la variable ya no puede ser usada. Supongamos, por ejemplo, que escribi mos un método y declaramos un objeto P o i n t como una variable local del méto do. C uando se llama al método, se crea el objeto P o i n t y el método puede traba jar con el. C uand o el método llega al final de su bloque de código, el objeto P o i n t va no volverá a usarse. En lenguajes como C'++. esto hace que se llame al const ructor de la clase cuando ac aba el método. En C#. esto no tiene por que ocurrir De hecho, no se puede llamar a un des tructor de clase. Recuerde que el C L R implementa una utilidad llamada recolector de objetos no utilizados que destruy e objetos que ya no se usan en el codigo. La recolección puede tener lugar mu ch o después de que el objeto deje de ser accesible. Obs er ve el método M a i n ( ) en el listado 9.7: Listado 9.7. Demostración del uso del recolector de objetos no utilizados mediante la estructura Point pubiic
static void M a i n ()
{ Point MyFirstPoint = new P o i n t (100, Point MySecondPoint = new P o i n t ();
200);
} El método M a i n ( ) crea dos objetos locales P o i n t . Cuando el método M a i n ( ) termina su ejecución, los objetos locales P o i n t ya no pueden volver a ser usados y el C L R los registra como objetos que pueden ser destruidos cuando se ejecute el recolector de objetos no utilizados. Sin embargo, el recolector de objetos no uti lizados no siempre se ejecuta i nmediatamente, lo que significa que no siempre se llama al dest ructor del objeto inmediatamente.
NOTA: Se llama a los destructores de las clases de C# cuando se destruye un objeto, no cuando su variable deja de ser accesible. Por ejemplo, s uponga que quiere escribir una clase de C# que gestione un archivo en un disco. Escribiremos una clase llamada F i l e con un constructor que ab ra el archivo y un dest ructor que cierre el archivo: class File
{
225
File (string
Ñame)
{ // abre el archivo
} -File ()
{ // cierra el archivo
} } Si queremos que esta clase trabaj e en un método: public
statíc void M a m ()
{ File MyFile
= new F i l e ("myfi1e .t x t ");
} El dest ructor de la clase File cierra el archivo, pero en realidad el destructor no se ejecuta hast a que el recolector de objetos no utilizados no se activa. Esto significa que el archivo puede todaví a estar abierto mucho después de que la variabl e MyFile sea inaccesible. Para asegurarnos de que el archivo se cierra lo antes posible, añadimos a la clase File un método Cióse (), que cierra el archivo cuando se le llama.
Cómo usar los tipos de clase Las clases pueden u sar uno de los tipos integrados en C # (int, long o char, por ejemplo) y pueden definir sus propios tipos. Las clases pueden incluir declaraciones de otros elementos, como e st ructur as o incluso otras clases.
Cómo usar la palabra clave this como identificador En C # se puede emplear la pal abr a clave this par a identificar un objeto cuyo código se está ejecutando, lo que a su vez permite hacer referencia a ese objeto. La p al ab r a clave this se puede empl ear de varias maneras. Y a hemos visto cómo se u s a en un i ndi zador. T a m b i é n se puede u s a r como prefi jo de un identificador de variable p ar a advertir al compi lador de que una determinada
226
expresión debe hacer referencia a un ca mpo de clase Observe, por ejemplo, la clase Point en el listado 9.8. Listado 9.8. C am pos y parámetros con el mismo nombre class
Point
{ public public
int X; int Y;
Point (int X,
int
Y)
{ X = X; Y = Y;
) public
static voicl Main ()
{ } 1 Este código no tiene el compo rt ami ent o esperado p orque los identificadores X e Y se emplean en dos ocasiones: como identificadores de ca mpo y en la lista de p ar ámet ros del constructor. El código debe diferenciar el identificador de campo X del identificador de la lista de par ámet ros X. Si el codigo es ambiguo, el compi lador de C# interpreta que las referencias a X e Y en las declaraciones del const ructor se refieren a los par ámet ros y el codigo establece los par ámet ros con el valor que ya contienen. Se puede usar la p al abr a clave this p ar a diferenciar el identificado!- de c a m po del identificador de parámetro. El listado 9.9 muest ra el código corregido, usando la pal abr a clave this como prefijo del c a mpo de nombre. Listad o 9.9. Cómo usar this con campos class
Point
{ public public
int X; int Y;
Point(int X,
int Y)
{ t h i s .X = X ; this.Y = Y;
} public
static void M a i n ()
{ }
227
El modificador static Cu an do se define un ca mpo o un método en una clase, cada objeto de esa clase creado por el código tiene su propi a copia de valores de ca mpo y métodos. M e diante la pal abr a clave static. se puede invalidar este comport ami ent o, lo que permite que varios objetos de la mi sma clase compar tan valores de c a mpo y métodos.
Cómo usar campos estáticos Ret omemos el ejemplo de la clase Po i n t . Una clase Po i nt puede tener dos campos par a las coordenadas ,v e v del punto. Todos los objetos creados a partir de la clase Point tienen copias de esos campos, pero cada objeto puede tener sus propios v alores para las coordenadas x e v. Asignar un v alor a las coordenadas x e v de un objeto no afecta a los v alores de otro objeto: Point MyFirstPoint = new P o m t (100, 200); Point MySecondPoint = new P o i n t (150, 2 50);
En este ejemplo se crean dos objetos de la clase Point. El primer objeto asigna a su copia de las c o o r d e n ad a sx e v los v alores 100 y 200. respectiv amente y el segundo objeto asigna a su copia de las coordenadas .v e v los v alores 150 y 250. respectiv amente. C ad a objeto gu ar d a su propia copia de las c o o r d e na da s x e r. Si se coloca el modi fi cador static antes de una definición de ca mpo se está indicando que todos los objetos de la misma clase compar tir án el mismo valor. Si un objeto asigna un valor estático, todos los demás objetos de esa mi sma clase c ompar tir án ese mismo valor. Observ e el listado 9.10. L istad o 9.10. C am pos estáticos class
Pomt
{ public public
static static
public
mt
int X C o o r d m a t e ; m t '/Coordínate;
X
( get
{ return
XCoordmate;
} } public
{ get
228
mt
Y
return
public
YCoordmate;
static void M a i n ()
{ Point MyPoint = new P o i n t (); Sy st em .C on so le . W r i t e L i n e ( " B e f o r e " ) ; Sy s t e m .Console.WriteLine; System.Console . W n t e L m e ( M y P o m t .X) ; System.Console.WriteLine ( M y P o m t .Y) ; Point.XCoordinate Point. Y C o o r d m a t e
-
100;
=
2 00 ;
Sy st em .C on so lé .W r i t e L i n e ( " A f t e r " ) ; System.Consolé.WriteLine ("----=") ; Sy s t e m . C o n s o l é . W r i t e L i n e ( M y P o i n t .X); Sy st em .C on so lé . W r i t e L i n e ( M y P o i n t . Y ) ;
} } La clase Point del listado 9.10 tiene dos campos estáticos enteros llamados XCoord i nate e YCoord inate. Tambi én tiene dos propiedades de solo lectu ra. llamadas X e Y. que devuelven los valores de las variables estáticas. El método Ma i n () crea un nuevo objeto Point y escribe sus coordenadas en la consola. A continuación ca mbi a los valores de los campos estáticos y vuelve a escribir las coor denadas del objeto Point. El resultado puede verse en la figura 9.3.
Figura 9.3. El uso de cam pos estáticos simplifica la codificación.
Hay que tener en cuenta que los valores de las coor denadas del objeto P o i n t han cambiado, aunque los valores del propio objeto no hayan cambiado. Esto es
229
debido a que el objeto Point compar te campos estáticos con todos los demás objetos Point y c uando los campos estáticos de la clase Point cambian, todos los objetos de esa clase se ven afectados. Los ca mpos estáticos que se usan en expresiones no están prefijados con un identificador de objeto sino con el nombre de la clase que contiene los campos estáticos. La siguiente instrucción es un error p o r q u e MyPoint hace referencia a un objeto v XCoordinate hace referencia a un campo estático: M y P o i n t .XCoordinate
= 100;
Este codigo hace que se p r o du z ca el siguiente error del compi lador de C#: error CS0176: No se puede obtener acceso al miembro estático 'Point.XCoordinate' con una referencia de instancia; utilice un nombre de tipo en su lugar
El ca mpo estático debe estar prefijado con el nombre de la clase: Point.XCoordinate
=
10 0;
Cómo usar constantes estáticas Las constantes operan de la mi sma forma que los campos a menos que esten precedidas por la pal abr a clave static; en ese caso, cada objeto de la clase contiene su propi a copia de la constante. Sin embargo, hacer que cada objeto de una clase contenga su propia copia de una constante supone desperdiciar m e m o ria. Sup on gamos que estamos escribiendo una clase llamada Circle. que g e s tiona un círculo. Como estamos trabaj ando con un círculo, usaremos bast ant e el valor pi. Decidimos que pi sea una constante p a r a referirnos siempre a ella con un nombre, en lugar de con un enorme número de coma flotante. A ho ra bien ¿qué ocurre si creamos mil objetos cí rc ul o7 Por defecto, cada uno de ellos tiene su propi a copia de pi en memoria. Esto es un desperdicio de m e mo ria. especialmente porque p i es una constante y su valor nunca cambia. Es mas lógico que ca da objeto de la clase Circle use una sola copia de la constante pi Para eso sirve la pal abr a clave static. Si se usa la pal abr a clave static con una constante ca da objeto de una clase t rabaj a con una sola copia del valor de la const ant e en memoria: const
double
Pi
= 3.1415926535897932384626433832795;
En general hay que intentar que todas las constantes sean estáticas de modo que sólo haya u na copia del val or de la constante en memori a a la vez.
Cómo usar métodos estáticos Los métodos que se definen mediante la p al ab r a clave static reciben el n om br e de métodos estáticos. Los métodos que no se definen mediante la pal abra
230
clave static reciben el n ombre de m étodos de instancia. Los métodos estáticos están incluidos en una clase, pero no pertenecen a ningún objeto específico. Igual que los c ampos estáticos y las constantes estáticas, todos los objetos de una clase compar ten una copia de un método estático. Los métodos estáticos no pueden hacer referencia a ni nguna parte de un objeto que no esté t ambi én ma rc a do como estático, como puede a pr eci ar se en el listado 9.11. Listad o 9.11. M é t o d o s e st á t i c o s q u e l l a ma n a un m é t o d o d e i n s t an c i a d e c l a s e class
Listing9_9
{ public
static void M a i n ()
{ CallMethod () ;
} void
C a l l M e t h o d ()
{ System. Consolé . W r i t e L m e ("Helio
from
Cal lMethod () " ) ;
} } El código anterior no se compi la y el comp il ado r de C# emite el siguiente error: error CS0120: Se requiere una referencia campo, método o propiedad no estáticos ' L i s t i n g9 _9 .C al lM et ho d() '
a objeto para
el
El error del código del listado 9.11 es que hay un método estático, Main (). que intenta llamar a un método de instancia. CallMethod (). Esto no está permitido porque los métodos de instancia son parte de una instancia de objeto y los métodos estáticos no. P a ra corregir el código, el método estático Main ( ) debe crear otro objeto de la clase y l lamar al método de i nstancia desde el nuevo objeto, como mue st ra el listado 9.12. L istad o 9.12. M é t o d o s e s t á t i c o s q u e l l a m a n a un m é t o d o d e i n s t an c i a d e c l a s e class
Listing9_10
{ public
static void M a i n ()
{ Listing9_10 MyObject
- new Listing9_10 () ;
M y O b j e c t . C a l l M e t h o d () ;
} void
CallMethod()
{
231
S y s t e m .C o n s o l e .W r i t e L i n e ("Hel1o
from
C a l l M e t h o d ()");
} } La figura 9.4 muest ra el resultado del listado 9.12. C. C:\WIINDOWS\System32\cmd.exe
X
i , i - t i i i f) v i u II.* l i e t i'«.,;, G , l i m . - i . l t
Id Figura 9.4. Ejemplo de una llamada a un método estático desde dentro de la misma clase
Como todos los elementos de las clases estáticas, los métodos estáticos a p a r e cen sólo una vez en memoria, por lo que se debe m ar c ar el método Main ( ) como static. Cuand o el código N E T se c arga en memoria, el C L R empieza a ejecu tar el método Main ( ) Recuerde que sólo puede haber un método Main ( ) en m emori a a la vez Si una clase tiene varios métodos Main (). el C L R no sabría qué m ét odo Main ( ) ejecutar cuando el código lo necesite. Usar la p al ab ra clave static en el método Main ( ) hace que sólo haya disponible en memori a una copi a del método Main ( ) .
NOTA: Mediante el uso de parámetros de líneas de comandos en el compilador de C#, es posible incluir más de un método Main () dentro de una aplicación. Esto puede ser muy útil cuando se quiere probar más de un método para depurar el código.
Resumen C# es un lenguaje orientado a objetos y los conceptos que se emplean en los lenguajes orientados a objetos se pueden aplicar a C#. Las clases de C# pueden usar varios tipos de miembros de clase:
232
Las constantes dan nombre a un valor que no c ambi a en todo el código. El uso de constantes hace que el código sea más legible porque se pueden usar los nombres de las constantes en lugar de los valores literales. •
Los cam pos contienen el estado de las clases. Son variables que están asociadas a un objeto. Los métodos contienen el comport ami en to de la clase. Son fragment os de codigo con nombre que realizan una acción det erminada p a r a la clase.
•
Las pro p ied a d es permiten que los fragment os que hacen llamadas tengan acceso al estado de la clase. Los fragmentos que hacen la llamada acceden a las propiedades con la mi sma sintaxis object .identif ier que se emplea par a acceder a los campos. La ventaja de las propiedades sobre los campos es que se puede escribir código que se ejecuta c uando se consultan o se asignan los valores de la propiedad. Esto permite escribir códigos de validación p a r a evitar que se asignen nuevos valores a las propiedades o par a calcul ar dinámi cament e el valor de una propiedad que está siendo consultada. Se pueden i mplementar propiedades de lectura y escritura, sólo de lectura o sólo de escritura.
•
Los eventos permiten que la clase informe a las aplicaciones que hacen la l lamada cuando se produ zcan determinadas acciones en su interior. Las aplicaciones que hacen la llamada pueden suscribirse a los eventos de clase y recibir avisos cuando se pro du zcan dichos eventos. Los m d iza d o res permiten que se acceda a la clase como si fuera una m a triz. Las aplicaciones que hacen la llamada pueden u s ar la sintaxis de elemento de matriz de corchetes par a ejecutar el código descriptor de ac ce so del indizador de la clase. Los indizadores se usan cuando una clase contiene una colección de valores y es más lógico consi derarl a una matriz de elementos. Las clases pueden redefinir los operadores, como se vio en un capitulo anterior. Los operadores pueden ay ud a r a determinar cómo se comport a una clase cuando se usa en una expresión con un operador. Los co nstructores son métodos especiales que son ejecutados cuando se crean objetos en la clase. Se puede definir más de un constructor, cada uno con una lista de par ámet ros diferente. Tambi én se puede definir una clase sin constructores. En ese caso, el compi lador de C# genera un constructor por defecto que inicializa t odos los ca mpos del objeto con el valor cero.
•
Los destructores son métodos especiales que son ejecutados cuando se destruyen objetos en la clase. Una clase sólo puede tener un destructor. Debido a la interacción con el código N E T y el CLR. los destructores se ejecutan c uando el recolector de objetos no utilizados recoge un objeto, no c uando el código ya no puede ac ceder al identificador del objeto.
233
Las clascs pueden definir tipos propios y estos tipos pueden contener de finiciones de es tru ctur a e incluso definiciones de otras clases. Una vez definidos estos tipos, la clase puede usarlos de la mi sma forma que los tipos enteros en C#. La pal abr a clave this hace referencia a la instancia actual de un objeto. Se usa como prefijo p ar a diferenciar a un identificador de campo de un identificador de p ar á me tr o con el mi smo nombre. La palabra clave static advierte al compi lador de C# de que todos los objetos de la clase compart en una sola copia de un campo o de un objeto Por defecto, cada ca mpo y cada método de una clase de C# mantiene su propia copia de los valores del c a mp o en memoria. Los elementos de la clase que no usan la pal abr a clave static reciben el n ombre de m étodos de instancia. Los el eme n tos de clase que usan la pal ab ra clave static reciben el nombre de m étodos es tá tic o s .
234
ifil Cómo sobrecargar operadores
C# define el c omport ami ent o de los operadores cuando se usan en una e x pr e sión que contiene tipos de datos integrados en C#. Por ejemplo. C# define el comport ami ent o del oper ado r suma calcul ando la s uma de dos operados y ofre ciendo la s uma como el valor de la expresión. En C#. se puede definir el c omport ami ent o de la may oría de los operadores estándar para que puedan usarse en est ructuras y clases propias. Se pueden escri bir métodos especiales que definen el compo rt ami ent o de la clase cuando a p a r e cen en una expresión que usa un oper ador de C#. Esto permite que las clases se puedan empl ear en expresiones que par ecerí a más lógico que escribieran otras partes del código. S upongamos , por ejemplo, que est amos escribiendo una clase que gestiona un conjunto de archiv os de una base de datos. Si algún otro f r agmen to de código tiene dos objetos de esa clase, quer rá poder escribir una expresión que sume los archiv os y los al macene en un tercer objeto Esto parece una o p e r a ción de s uma y parece lógico que otros fragment os del código tengan partes como la siguiente: Records Records 1; Records Records2; Records Records3; Records3
= Recordsl
+ Records2;
237
La clase Records puede incluir un método que especifique cuántos objetos de la clase ac tua rán de una determinada forma cuando se usen en expresiones con el oper ado r de suma. Estos métodos reciben el nombre de im plem entaciones de operadores definidas p o r el usuario y la operación orientada a objetos para definir el c omport ami ent o de los oper adores de una clase recibe el nombre de sobreca rga de operador. Se emplea la p al abr a "sobrecarga" porque el cuerpo del código s obr ecar ga el significado del mi smo op er ador y hace que se comport e de for ma diferente, dependiendo del contexto en el que se use el operador. Todos los métodos de sobre carg a de operadores deben declarase con las p a l a bras clave static y public.
Operadores unarios sobrecargabas C# permite sobrecargar en sus clases y estructuras el comportamiento de estos operadores unarios: U n a n o mas Unario menos •
Negación lógica Ope rado r de compl ement o bit a bit Incremento prefijo Decrcmento prefijo La palabra clave true
•
La palabra clave false
Cómo sobrecargar el unario más Si se quiere s obre carg ar el unario más. el unario menos, una negación o un o per ador de complemento bit a bit en una clase o estructura, hay que definir un método con las siguientes características: •
Un tipo devuelto deseado
•
La palabra clave operator El oper ador que se quiere sobre carga r Una lista de par ámet ros que especifique un sólo par ámet ro del tipo o es tru ct ura que contiene el método del oper ad or s obrecargado
Ret omemos el ejemplo de la clase Point utilizada en un capítulo anterior S upongamos que queremos añadir un o per ador unario más a la clase que. cuando
238
se emplee, se asegure de que las dos coordenadas del punto sean positivas. Esto se implementa en el listado 10.1. L istado 10.1. Cómo sobrecargar el operador unario más class
Pomt
{ public public
int X; int Y ;
public
static
Pomt
operator
+
(Pomt
RValue)
{ Pomt
NewPomt
= new P o m t () ;
í f ( R Va lu e.X < 0) N e w P o m t .X = - (RValue .X ) ; e1se N e w P o m t . X = RValue.X; i f (R V a 1u e .Y < 0 ) N e w P o m t .Y = - (RValue .Y) ; e 1s e NewPoint.Y = RValue.Y; return
NewPomt;
} public
static void M a i n ()
{ Point MyPoint
= new Point ();
M y P o m t . X - -100; M y P o i n t .Y = 200; Sy st em .C on so lé . W r i t e L i n e ( M y P o i n t . X ) ; Sy st em .C on so lé . W r i t e L i n e ( M y P o i n t . Y ) ; MyPoint = + M y P o m t ; Sy st em .C on so lé .W ri t e L i n e ( M y P o i n t . X ) ; Sy st em . C o n s o l é . W r i t e L i n e ( M y P o i n t .Y );
} } El mét odo M a i n ( ) crea un objeto de tipo P o i n t y asigna a sus coordenadas iniciales los valores (100. 200). A continuación aplica el oper ador unario más al objeto y vuelve a as ignar el resultado al mismo punto. Por último, escribe las coordenadas \ e y en la consola. En la figura 10.1 se puede ver el resultado del listado 10.1. Las coordenadas del punto han cambi ado de (-100. 200) a (100. 200). El código con el oper ador s obrecargado se ejecuta cuando se llega a la siguiente instrucción: MyPoint
= +MyPoint;
239
ca C:\WINDOWS\System32\cmd.ene C :' .» I-i-l u n 1H l . . • *.: 1 Un 1HH 2 un C:\>_
Ld Figura 10.1. Sobrecarga del operador unario
Cuando se llega a esta instrucción, se ejecuta la s obrecarga del operador unario más para la clase Point. La expresión situada a la derecha del signo igual se u sar á como el p ar ám et ro del método.
NOTA: La expresión situada a la derecha de un operador de asignación suele ser denominada r v a lu e , que es la abreviatura de "valor derecho". La éxpresióná la izquierda del operador de asignación suele ser denomina da Iv a l ú e , que 68 U abreviatura de "valor izquierdo". El uso de RValue par» ap arar el método de sobrecarga de operadores determina que se f|éÉ;|^iKacb el r v s lu e de la asignación. Esto es sólo úna convenció de requisito. Si lo desea, puede asignar otro nombre a los id¿atifíaréor válido permitido por €#. Este método crca un nuevo objeto Point y a continuación exami na las c o or denadas del rvalue proporcionado. Si alguno de los p ar ámet ro s es negativo, sus valores se cambi an de signo, volviéndose por tanto valores positivos, y estos nuevos valores positivos se asignan al nuevo punto. Los valores que no son n e ga tivos se asignan al nuevo punto sin ninguna conversión. A continuación el método devuelve el nuevo punto. El valor devuelto por el o per ado r se usa c omo Iva lúe par a la declaración original. El tipo de retorno de las sobrecargas del operador par a el unario más. el unario menos, la negación o los operadores de c o mp l e mento bit a bit no tienen el mi smo tipo que el rvalue. Puede ser cualquier tipo de C# que sea adecuado p ar a el operador.
Cómo sobrecargar el unario menos Se puede ef ect uar la s obr ecar ga del unario menos de la mi sma maner a que se realiza la del unario más.
240
El l i s ta d o 10.2 s o b r e c a r g a el o p e r a d o r m en os p a r a g e s t i o n a r la cl ase Point. L istad o 10.2. S obrecarga del unario menos class
Point
{ public public
int X; int Y;
pubJic
static
Point
operator
-
(Point
RValue)
{ Point N e w P o m t if
= new Point () ;
(RValue.X > 0) N e w P o m t . X = - (RValue.X) ;
else N e w P o m t . X = RValue.X; if
(RValue.Y > 0) N e w P o m t . Y = - (RValue .Y) ;
else NewPoint.Y return
= RValue.Y;
NewPoint;
} public
static void M a i n ()
{ Point MyPoint = new P o i n t (); M y P o m t . X = -100; MyPoint.Y = 200; Sy s t e m . C o n s o l e . W r i t e L i n e ( M y P o i n t .X ); S yst em .C o n s o l e . W r i t e L i n e ( M y P o i n t . Y ) ; MyPoint = -MyPoint; Sy st em . C o n s o l e . W r i t e L i n e ( M y P o i n t .X ); System.Console.WriteLine ( M y Po mt .Y ) ;
Tras definir el nuevo oper ad or Point. simplement e se define la acción que debe realizar cuando se presente con una variable del tipo Point. El listado 10.2 declara la c o o r d e n a d a x como -100 y la c oor denada ;' como 200. Estos valores se escriben en la consola p ar a u na verificación visual y luego se usa el o per ador sobrecargado. Después de que la aplicación de ejemplo haya realizado la resta de la clase Point. los valores resultantes se escriben en la vent ana de la consola par a indicar que el comport ami ent o ha sido el esperado. La figura 10.2 muest ra el resultado del listado 10.2. Ha st a ahora, en este capítulo hemos estudiado el unario más y el unario m e nos. Estos operadores efectúan operaciones sobre un valor dado (por eso se lia-
241
man "unarios"). Los demás operadores mat emát icos básicos que pueden usarse sobre un valor se s obrecargan de la mi sma manera.
-=Jni
c;\ C:\WIINDOWS\System32\cmd.exe C :\> L ic t. in y l 0 2 . e x e 10« 200 100 -2 0 0 C :\> _
Figura 10.2. Sobrecarga del unario menos
En la siguiente sección se describe un oper ador de otro tipo, el oper ado r de compl ement o bit a bit.
Cómo sobrecargar complementos bit a bit El op er ad or de compl ement o bit a bit sólo tiene definiciones p ar a tipos int. uint. long y ulong. El listado 10.3 lo s obrecarga par a t rabaj ar con la clase po i n t . Listado 10.3. Sobrecarga del operador de complem ento bit a bit class
Point
{ public public
int X; int Y;
public
static
Point
operator
~
(Point
{ Point NewPoint = new Point () ; NewPoint.X = -RValue.X; NewPoint.Y = -RValue.Y; return
NewPoint;
} public
static
void M a i n ()
{ Point MyPoint
242
= new P o i n t ();
RValue)
M y P o i n t .X = 5; MyPoint.Y = 6 ; S ys te m.C on s ole . W r i t e L i n e ( M y P o i n t . X ) ; System.Console.WriteLine (MyPo i n t .Y) ; MyPoint = -MyPoint; S y s t e m . C o n s o l e . W ri te Li ne (M yP oi nt . X) ; S ys te m.C on s ole . W r i t e L i n e ( M y P o i n t . Y ) ;
} } El resultado de una operación de compl ement o bit a bit no se conoce con exactitud hasta que se ven los resultados hexadécimales de la operación. El l ista do 10.3 genera el compl ement o de los valores enteros 5 y 6. El resultado de esta operacion (que aparece en la figura 10.3) es -6 y -7. respectivamente. Cuand o se obs er van los valores hexadéci mal es de los valores de ent rada y salida, es fácil deducir lo que está ocurriendo en realidad. CV
'v;
C:\WINDOWS\System32\cmd.exe
- i□ i X i
I DI
C:\>Listingl0-3.exe
5 6
- 6
-7 C:\>_
i
Figura 10.3. Sobrecarga de un complem ento bit a bit
Tabla 10.1. Valores de entrada y salida para operaciones de com plem ento bit a bit
Input
Output
0x0000000000000005
0xf f f f f f f f f f f f f f f A
0x0000000000000006
0xf f f f f f f f f f f f f f f 9
Antes de s ob re car ga r un operador, es necesario entender per fectamente como funciona. En caso contrario podría obtener resultados inesperados.
243
Cómo sobrecargar el incremento prefijo Para sobrecargar los operadores de incremento prefijo o de decrcmento prefijo en una clase o estructura, se define un método con las siguientes caracteristicas: •
Un tipo devuelto que especifica el tipo de clase o e st ruc tur a que contiene el método del op er ado r sob re carga do
•
La palabra clave op era to r El operador que se s obrecarga Una lista de par ámet ros que especifica un solo par ámet ro del tipo de la clase o est ructur a que contiene el método del oper ado r s obrecargado
Por ejemplo, observe el listado 10.4. Esta clase modifica la clase Point para sobre carga r el operador de incremento prefijo. El oper ador se sobrecarga para au ment ar en una unidad las coordenadas .v e v. Listado 10.4. Cómo sobrecargar el incremento prefijo class Point public public
int X ; in t Y ;
public
static
Point
operator
++
(Point
RValue)
I Point
MewPomt
- new Point () ;
NewPoint.X - RValue.X + 1; N e w P o m t . Y = RValue. Y + 1; return N e w P o m t ;
} public
static
void M a m ()
{ Point M y P o m t
= new P o i n t ();
MyPoint.X = 10 0; M y P o m t . Y = 200 ; System.Consolé.WriteLine ( M y Po mt .X ) ; System. Con s ole. W r i t e L m e ( M y P o m t . Y) ; M y P o m t = ++MyPomt; System.Con solé.WriteLine ( M y P om t. X) ; System.Con.solé.WriteLine(MyPoint.Y);
} } Si se compila y ejecuta el código del listado 10.4 se escribe lo siguiente en la consola:
244
1o o 2 00
101 20 1
Cómo sobrecargar el decremento prefijo Ahora vamos a apr ender a s ob re carga r el operador de decrcmento par a g e s tionar la clase Point. El listado 10.5 contiene el listado completo par a s obr e c a rg ar el oper ador de forma muy pareci da a como se hizo con el oper ador de incremento prefijo que ac a ba mos de estudiar.
Figura 10.4. R e s u l t a d o d e la e j e c u c i ó n del c ó d i g o c o m p i l a d o del listado 1 0 . 4
Listado 10.5. S o b r e c a r g a del o p e r a d o r d e d e c r e m e n t o prefijo class Point
{ public public
int X; int Y;
public
static
Point
operator —
(Point
RValue)
{ Point
NewPoint
= new P o i n t ();
N e w P o m t . X = RValue.X - 1; NewPoint.Y = RValue.Y - 1; return NewPoint;
} public static void M a i n ()
{ Point MyPoint
= new P o i n t ();
245
MyPoint.X = 100; M y P o m t . Y = 200 ; S y s t e m . C o n s o l e . W ri te Li ne (M yP oi nt . X) ; System.Consolé.WriteLine ( M y Po mt .Y ) ; MyPoint = —M y P o i n t ; S yst em .C on so le .W ri t e L i n e ( M y P o i n t . X ) ; S yst em .C on so le .W ri t e L i n e ( M y P o i n t . Y ) ;
} } De nuevo, se pas a la coordenada v con el valor 100 y la coordenada v con el valor de 200. La figura 10.5 contiene el resultado de este p rog ram a después de que la so bre carg a del o per ador de decrcmento haya restado una unidad de v y de y. ca
C:\WINDOWS\System32\cmd.exe
X
C :\ >Lir; t inq 10-5 .e x e
100 200 99 199 C:\>.
Figura 10.5. S o b r e c a r g a del o p e r a d o r d e d e c r e m e n t o prefijo
C'uando se s obrecargan operadores siempre hay que estar preparado para lo peor. Siempre hay alguna posibilidad de que los datos que se están pas ando sean incorrectos y nos encontr aremos con que la función s ob re car ga da no puede g es tionar los datos. Los anteriores ejemplos no mos trab an ninguna de las exc ep ci o nes que pueden aparecer cuando se pasan valores erróneos o inesperados. Es recomendable experi ment ar con las funciones e intentar f orzar errores.
Cómo sobrecargar los operadores true y false Para sobrecargar los operadores true o false en una clase o estructura hay que definir un método con las siguientes características: Un tipo dev uelto boo 1 La palabra clave operator
246
El operador que se sobrecarga
Una lista de par ámet ros que especifica un solo par ámet ro del tipo de la clase o e st ru ct ur a que contiene el método del oper ado r s obre carga do El listado 10.6 es un buen ejemplo. Modifica la clase point par a que devuel va true si el punto está en el origen o false en caso contrario. Listado 10.6. Sobrecarga de los operadores true y false class Point
{ public public
int X; int Y;
public
static bool
operator
true
(Point
RValue)
{ i f ( (RV alue.X == 0) return true; return false;
&&
(R V alue.Y == 0))
} public
static bool
operator
false
(Point
RValue)
{ if ( (RValue.X = - 0) return false; return true;
&&
(RValue. Y = = 0))
} public
static void M a i n ()
( Point MyPoint MyPomt.X MyPomt.Y
= new P o i n t ();
= 100; = 2 00 ;
if (MyPoint) System.Con so le .W ri te Li ne ("The
point
is
at
the
origin.");
else System.Console.WriteLine("The point is not at the origin.");
La sobre carga de los operadores true y f al se permite que los objetos de la clase Point puedan usarse como expresiones booleanas. como en la instrucción if . Debido a que el objeto MyPoint no está en el origen, el objeto se evalúa como false. tal y como aparece en la figura 10.6. Si se sobrecarga el operador true o el operador false en una clase o es tructura. ambos deben ser sobrecargados. Si se s obrecarga uno de los dos. pero no el otro, el compi lador de C# emite un mensaje de error como el siguiente:
247
error CS0216: El o p era dor 'P o m t .o p er ato r t r u e ( P o i n t ) ' requi ere que t a m b i é n se d efi n a un ope r a d o r c o i n c i d e n t e 'false'
C :\>I
Ld Figura 10.6. Sobrecarga de los operadores tr ue y fa lse
Operadores binarios sobrecargables Estos son los operadores binarios que se pueden sobrecargar: Suma Resta Multiplicación División Resto •
AND
•
OR OR exclusivo Despl azami ent o a la izquierda Despl azami ent o a la derecha Igualdad Desigualdad M a yo r que Menor que
248
M ayo r o igual que Meno r o igual que Si quiere sob re carga r cualquiera de los operadores binarios de una clase o estructura, hay que definir un método con las siguientes características: Un tipo devuelto deseado La palabra clave operator El operador que se s obrecarga Una lista de par ámet ros que especifica dos parámetros, al menos uno de los cuales debe ser del tipo de la clase o es tructur a que contiene el método del operador s obre carga do La s obrecarga de operadores binarios brinda mucha flexibilidad. Pueden u s a r se diferentes par ámet ros par a los dos par ámet ros de la lista, lo que significa que se puede aplicar el operador a dos valores de diferentes tipos si se desea. Tambi én se puede us ar cualqui er tipo disponible como el valor devuelto por el oper ador sobrecargado. Si se quiere s umar un objeto y un valor de coma flotante para obtener un resultado booleano. se puede escribir un método sobrecargado como el siguiente: static public
bool
operator
+
(Point MyPoint,
float
FloatValue)
Se pueden definir v arias sobrecargas par a el mismo operador, pero solo si las listas de par ámet ros usan tipos diferentes: static public static public static public
bool bool bool
operator operator operator
+ + +
(Point MyPoint, (Point MyPoint, (Point MyPoint,
fioat FloatValue) int IntValuej uint UlntValuei
El listado 10.7 añade los operadores de igualdad y de desigualdad a la clase
Point. El oper ado r devuelve resultados booleanos que devuelven true si los dos objetos Point tienen las mismas coordenadas: en caso contrario, devuelven faise. Listado 10.7. Sobrecarga de los operadores de igualdad y desigualdad c 1 a s s Point
{ public int X; publi c int Y ; public
static bool
operator
-=
(Point
Pointl,
Point
Pomt2)
{ íf ( P o m t l . X != P o m t 2 . X ) return false; if( Pointl. Y != P o m t 2 . Y )
249
return false; return true;
} public
override
bool
Equals (object
o)
{ return
true;
} public
override
int
G e t H a s h C o d e ()
{ return
0;
} public
static bool
operator
!=
(Point
Pointl,
Point
Point2)
{ if(Pointl.X != Point2.X) return true; if (Point2.Y != Point2.Y) return true; return false;
} public
static void M a i n ()
{ Point MyFirstPoint = new P o i n t (); Point MySecondPoint = new P o i n t (); Point MyThirdPoint = new P o i n t (); M y F i r s t P o i n t .X = 100; M y F i r s t P o i n t .Y = 200; M y S e c o n d P o i n t .X = 500; MySecondPoint .Y - 750 ; M y T h i r d P o i n t .X - 100; M y T h i r d P o i n t .Y - 200; if(MyFirstPoint == MySecondPoint) System. Console . W r i t e L m e ("MyFirstPoint and MySecondPoint are at the same c oo rd inates."); else System.Console . W r i t e L m e ("MyFirstPoint and MySecondPoint are not at the same coordinates.") ; if(MyFirstPoint == MyThirdPoint) System.Conso le .W ri te Li ne ("MyFirstPoint are at the same c oo rd inates."); else Syst em .C on so le .W r i t e L i n e ("MyFirstPoint are not at the same coordinates.");
250
and
MyThirdPoint
and
MyThirdPoint
El método Main ( ) define tres puntos:
MyFirstPoint. con coordenadas (100. 200) MySecondPoint. con coordenadas (500. 750) MyThirdPoint, con coordenadas (100. 200) A continuación el método usa el o per ado r de igualdad par a det erminar si los puntos MyFirstPoint v MySecondPoint hacen referencia a la mismas coordenadas. Entonces usa el o per ador de igualdad p ar a determinar si los puntos MyFirstPoint vMySecondPoint hacen referencia a las mi smas coo r de na das. En la figura 10.7 se mues tra el resultado que se obtiene si se compila y ejecuta el código del listado 10.7. c* C:\WINDOWS\System32\cmd.exe
x|
C :\>List ingl0-7.exe MyFirstPoint and MySecondPoint are not at the sane coordinates. MyFirstPoint and MyThirdPoint are at the sane coordinates.
C:\>_
Figura 10.7. S obrecarga de los operadores de igualdad y desigualdad
Los siguientes pares de operadores deben ser s o brecargados conjuntamente. Igualdad y desigualdad Menor y m ayo r que Menor o igual que y mayor o igual que Si se s obrecarga uno de esto pares pero no el otro, el compi lador de C# emite un mensaje cómo el siguiente: error CS0216: El operador 'Point .ope rator = = ( P o m t , Point) ' requiere que también se defina un operador coincidente '!='
Operadores de conversión sobrecargabas Tambi én se pueden escribir métodos de sobrecarga de operadores que convier tan un tipo en otro. El método de sobrecarga también puede definir si el compilador
251
de C # debe t rat ar la conv ersión como implícita o explícita. Si quiere definir un nuev o oper ado r de conversión en una clase o estructura, debe definir un método con las siguientes características. La palabra clave implicit si la conversión va a considerarse una co n versión implícita o la p al ab ra clave explicit si la conversión va a co n siderarse una conv ersión explícita La palabra clave operator •
Un tipo que especifica el tipo al que se va a hacer la conversión Una lista de par ámet ros que especifica el tipo original de la conversión
L1 listado IO S define una conversión implícita de un objeto de la clase Point a doubJe. El tipo doble especifica la distancia desde el origen hasta el punto, usando el teorema de Pitágoras. Listado 10.8. Cómo definir una conversión implícita cla s s Point
{ public publie
int X; int Y ;
public
static
implicit
operator
double(Point
RValue)
I double double
Distance; Sum;
Sum = (RValue.X * RValue.X) + (RValue.Y * R V a lu e. Y) ; Distance = Sy st em .M at h.Sq rt (S um ); re t u rn D is t.a n c e ; l p u b lie s t a t.ie v oíd Main () i doub le í)is t.anee ; Poi nt 14yPo int = ne w Poir.t í) ; MyPoint. .X = lUÜ; M y P o m t .Y = 2 0 U ; Di st anee =■ M y P o m t ; 3 y st. em. C o 1is o le.Wri t e L m e ÍDistance) ;
I i
NOTA: .NET Framework define el método System. M a t h .Sqrt () que calcula la raíz cuadrada del parámetro proporcionado. El método es estáti co, por lo que se le puede llamar aunque no se tenga un objeto del tipo System.Math para llamarlo.
252
El método Main ( ) declara un objeto del tipo Point y asigna a sus coor de nadas los valores (100. 200). A continuación asigna el objeto a una variable de tipo double. lo que está permitido porque la clase Point define un oper ador de conversión que convierte un objeto Point a doble. Como el oper ad or de c onve r sión esta definido c omo una conversión implícita, no es necesaria una conversión explícita. A continuación el método Ma i n ( ) escribe el valor de double convert i do en la consola. La figura 10.8 muestra el resultado del listado IOS. -iP l
CA C:\WINDOWS\System3Z\cmd.exe C:\>Listincfl0-8 .exe 223,606797749979 C:\>_
^
i
d
Figura 10.8. Definición de una conversión implícita
Operadores que no pueden sobrecargarse C# no permite redefinir el compo rt ami en to de los operadores de la siguiente lista. Esto se hace principalmente en beneficio de la simplicidad. Los pro gr amadores de C# quieren que estos oper adores no dejen de ser sencillos y que s iem pre realicen la misma función: por tanto, no esta permitido sobrecargarlos. Asignación A ND condicional OR condicional Condicional •
Las palabras clave new. typeof. sir.eof e is
Resumen CU permite per sonal izar el comport ami ent o de varios de los operadores inte grados. Las clases y las est ructur as pueden incluir métodos llamados métodos de
253
sobreca rga de op era dor que definen el c omport ami ent o de un o per ado r c u a n do aparece en una expresión con la clase o estructura. Para so bre carga r los operadores unario más. unario menos, de negación o de complemento bit a bit en una clase o estructura, hay que definir un método con un tipo devuelto deseado, el oper ador que se está sobrecargando y un solo par ámet ro del tipo de la clase o est ructura que contiene el método de operador sobrecargado. Para s obr ecar ga r los operadores de incremento o decrcmento prefijo en una clase o estructura, hay que definir un método con un tipo de devolución que especifica el tipo de clase o es tructur a que contiene el método del operador s ob re cargado. Tambi én es necesario definir el operador que se está s obrecargando y un solo p a rám et ro del tipo de clase o es tru ctur a que contiene el método del operador sobrecargado. Para sobrecargar los operadores t r u e o f a l s e en una clase o estructura. ha\ que definir un método con un tipo de devolución booleano y un solo parámetro del tipo de la clase o est ructura que contiene el método de operador sobrecargado. Para sobre carga r cualquiera de los operadores binarios en una clase o es t ruc tura. hay que definir un método con un tipo de devolución, el operador que se esta s obrecargando y dos parámetros. Al menos uno de los dos parámet ros debe ser del tipo de clase o es truc tu ra que contiene el método del oper ador sobrecargado. Tambi én se pueden definir nuevas conversiones par a las clases o estructuras. Se especifica si la conversión se considerara un o per ador implícito o explícito. El método del operador de conversión especifica el tipo de la variable que se convier te v el tipo al que debe ser convertida.
254
11 Herencia de clase
Los p ro gr amas mas simples de C# pueden u sar una o dos clases. Sin embargo, probablemente se necesiten varias clases en los p ro gr amas más grandes. Muchas de estas clases pueden tener ca mpos o métodos similares y seria logico compar tir el codigo común entre el conjunto de clases. C# inclu\ e el concepto orientado a objetos de herencia, que permite que una clase adopte código de otras clases. Las clases de C# pueden derivarse de las clases primarias y las instrucciones heredadas pueden ser usadas en otras clases. La herencia se usa en el desarrollo de s oftware orientado a objetos par a reutilizar los codigos mas comunes. Observ e, por ejemplo, los cuadros de lista de selección múltiple y de selección simple de Wi ndows. Estos cuadros de lista tie nen diferentes funcionalidades: uno permite que se seleccionen varios elementos y el otro lo impide, pero también tienen m uchas similitudes. Tienen el mismo aspecto, se comport an de la misma forma cuando el usuario se desplaza por la lista y usan el mismo color p ar a m ar c ar un elemento seleccionado. Si hubiera que escribir estos dos cuadros de lista como clases de C'#. se podrian escribir por separado, sin que uno conozca la existencia del otro. Sin embargo, eso sería un desperdicio. La may or parte del código segurament e sea idéntico. Sería más lo gico escribir una clase que cont uvi era el código común y disponer de clases deri v a d a s de la e l a s e de c ó d i g o c o m ú n y qu e i m p l e m e n t a s e n las d i f e r e n t e s funcionalidades. Se puede escribir una clase llamada List Box par a que. por
257
ejemplo, contenga el código común y. a continuación, escribir una clase de C# l lamada SingleSelectionListBox que herede de ListBox y pro po rci o ne el código único al c ua dro de lista de selección simple. Tambi én se puede escribir una clase de C # llamada MultipleSelectionListBox que t am bién herede de ListBox pero que proporcione el código único al cuadro de lista de selección simple. Otra de las ventajas consiste en que. si encuentra un error en el cuadro de lista, se le puede seguir fácilmente la pista hasta el error en el código común. Si se puede reparar el error en el código común, al volver a compilar el programa se repararán esos errores en las clases de cuadros de lista de selección múltiple y selección simple. Basta reparar un error p ar a solucionar el problema en las dos clases. En terminología orientada a objetos, se habla de herencia en térmi nos de clase base y clase derivada. La clase de la que se hereda recibe el nombre de clase base y la clase que hereda de la clase base recibe el nombre de clase derivada. En el ejemplo de los cuadros de lista, la clase ListBox es la clase base v las clases SingleSelect ionListBox y MultipleSelectionListBox son las clases derivadas.
Cómo compilar con clases múltiples T r a ba ja r con herencias en C# significa que se va a t raba jar con más de una clase de C#. C# no es muy estricto respecto a cómo se relacionar! estas clases con los archivos fuente. Se pueden poner todas las clases en un solo archivo fuente o se puede poner c ada clase en un archivo fuente diferente. Obviamente, excepto en los pr ogr a ma s más pequeños, implementar todas las clases en un solo archivo no es un buen modo de o rganizar el código. Ha y que tener en cuenta que todas las clases se r ecompilan cada vez que se hace un cambio en al guna parte del programa. Para compilar un p ro g ra ma que usa archivos fuen te separados desde una línea de comandos, tan sólo hay que escribir cada archivo después del nombre del compilador, como se ve en el ejemplo: ese
filel.es
file2.es
flle3.es
Por defecto, el compilador de C# da al ejecutable resultante el nombre del pri mer archivo fuente. La anterior línea de comandos produce un ejecutable llamado filel .exe. Se puede usar el argumento /out para cambi ar el nombre del archivo: esc / o u t :m y a p p .exe filel.es flle2.es flle3.es
E s t a lí nea de c o m a n d o s del c o m p i l a d o r g e n e r a un e j e c u t a b l e l l am ad o myapp. exe.
NOTA: Uha, y sólo una, de sus clases debe especificar un método estáticóMáinO.
258
Cómo especificar una clase base en C# Vol vamos al ejemplo de la clase Point par a ver cómo funciona la herencia en C //. Supongamos C[ue Hemos designado una clase llamada Point2D. que describe un punt o en un espacio bidimensional con las coor denadas X e Y: class P o in 12 D
{ public int X; public int Y; // mas codigo
} A h o r a s u p o n g a m o s qu e q u e r e m o s t r a b a j a r con p u n t o s en un e s p a c i o tridimensional, pero mant eni endo la clase Point2D. La herencia nos permite crear una nueva clase que mantiene todo el código de la clase Point2D y le añade una coordenada Z. Para n om br ar la clase base de C# se escribe el nombre de la clase derivada seguido por dos puntos y del nombre de la clase base. A continuación, se incluye un ejemplo de cómo se derivaría la clase Point3Da partir de la clase Point2D: class
Point3D
: Point2D
{ public int Z ; // codigo para
la clase
Point3D
} Dependiendo de las reglas de ámbi to de la clase base, todos los campos y propiedades de la clase base (Point2D) pueden ser empl eadas en la clase der i vada (Point3D). Por ejemplo, cuando una clase se deriva de una clase base, el código de la clase derivada puede acceder a los ca mpos y propi edades de la clase base, si el ámbi to lo permite. Sólo se puede escribir una clase cuando una clase se hereda de otra. Algunos lenguajes orientados a objetos, como C++. permiten especificar más de una clase base p ar a una clase derivada. Este concepto recibe el nombre de herencia m ú lti ple. CU admite la herencia simple, pero no la múltiple. En el a pa r ta do dedicado a la contención se explica una t éxmca p a r a simular herencias múltiples en CU. El listado 11.1 enseña a usar las clases Point3D y Point 2D juntas. Listado 11.1. Cómo derivar Point3D a partir de Point2D class Point2D
{ public public
int X; int Y;
} class
P o in 1 3 D
: P o i n 12 D
259
public
int
Z;
; class
MyMainClass
{ public
static void M a i n ()
{ Pomt2D PomtBD
My2DPomt My3DPoint
My2DPoint.X My2DPomt.Y
= new P o m t 2 D ( ) ; = new P o m t 3 D ( ) ;
= 100; = 200 ;
M y 3 D P o 1 n t .X - 150; M y 3 D P oln t .Y = 2 5 0 ; My3DPoint.Z = 350;
El método Main ( ) crea un objeto Point2D y otro Point3D. El objeto Poi nt3D tiene c ampos par a las coordenadas X. Y y Z. aunque la declaración de PointBD sólo declare un ca mpo llamado Z Los campos X e Y se heredan de la clase base Point2D y pueden ser usados exact ament e igual que si se hubieran declarado di rectamente en la clase Point3D.
Ám bito Al diseñar la estructura de la herencia de clase, puede decidir qué miembros de la clase base no deben ser visibles para las clases derivadas o para los demás progr amadores . Por ejemplo, se puede escribir un método en una clase base que ayude a calcul ar un valor. Si ese cálculo no es de utilidad en una clase derivada, se puede ev itar que la clase deriv ada llame al método. En terminología de la programación, la v isibilidad de una variable o método se conoce como su ámbito. Algunas variables o métodos pueden ser declaradas corno de ámbi to publico, otras pueden ser declaradas como de ámbito privado y otras pueden estar entre estos dos casos. C# define cinco pal ab ras clave que permiten definir el ámbi to de cualquier mi embro (variable o método) de una clase. El ámbi to de un miembro afecta a su visibilidad par a las clases derivadas y el código que crea las instancias de la clase. Estas pal abr as clave, resaltadas en la siguiente lista, se colocan antes de cualquier otra p al abr a clav e en una declaración de miembro. •
260
Los mi embros marc ado s como public son visibles par a las clases deri vadas y par a el código que crea los objetos de la clase. Hast a ahora hemos usado public.
•
Los mi embros mar cad os como prívate sólo son visibles p ar a la clase en la que se definen. Los mi embros privados no son accesibles desde las clases derivadas ni desde el código que crea los objetos de la clase.
•
Los mi embros m ar cad os como protected sólo son visibles p ar a la c la se en la que se definen o desde las clases derivadas de esa clase. No se puede acceder a los mi embros protegidos desde el código que crea los objetos de su clase. Los mi embros marcados como interna 1 son visibles par a cualquier código en el mi smo archivo binario, pero no son visibles p ar a otros a r c h i vos binarios. Recuerde que N E T Framework acepta el concepto de e ns am blados. que son bibliotecas de código va compiladas que pueden ser usadas por aplicaciones externas. Si se escribe una clase en C# y se compila la clase par a obtener un ensambl ado, cualqui er fragment o de código del e n s ambl ad o podrá acceder a los mi embros de clase interna. Sin embargo, si otro fragment o de código usa ese ensambl ado, no tendrá acceso al mi em bro. aunque derive una clase de la clase del ensamblado. Los miembros marcados como protected internal son visibles par a todo el código incluido en el mismo archiv o binario y par a las clases externas que se deriven de su clase. Si se escribe una clase en C# y se compila la clase para obtener un ensamblado, cualquier fragmento de codigo del ens amb la do puede acceder a los mi embros de clase interna. Si otro fragment o de codigo externo usa el ens amb la do y deriva una clase de la clase del ensamblado, el mi embro interno protegido sera accesible p ar a la clase derivada. Sin embargo, el código que t rabaja con los objetos de la clase base no tendrá acceso al miembro.
C# permite especificar un mi embro de clase sin especificar ninguna pal abr a clave de ámbito. Si se declara un mi embro de clase sin especificar ninguna p al ab ra clave de ámbito, al mi embro se le asigna por defecto accesibilidad privada. Los miembros que se han declarado sin usar ninguna pal ab ra clave de ámbito pueden usarse en otras partes de la clase, pero no pueden ser usados por clases derivadas ni por codigo que use objetos de la clase.
Cómo reutilizar identificadores de miembros en las clases derivadas C# permite reutilizar identificadores de clase base en las clases derivadas, pero el compi lador de C# emite un aviso cuando lo detecta. Preste atención al código del listado 1 1.2.
261
Listado 11.2. C óm o reutilizar identificad ores de clase base eiass P o in 12 D
{ public m t pubi i c m t
X; Y;
} class
Pomt3D
: Point2D
{ public m t public m t pubi íc m t
X; Y; Z;
} class
MyMamClass
{ public
static void M a i n ()
{ Pomt2D Pomt3D
My2DPomt My3DPomt
My2DPoint.X My2DPomt.Y
= new P o m t 2 D = new P o m t 3 D
= 100; = 200 ;
My3DPoint .X = 150 M y 3 D P o m t .Y - 250 M y 3 D P o m t .Z = 350
La clase derivada Point3D define los c ampos X e Y que coinciden con los identificadores usados en la clase base Point2D. El compi lador de C # emite las siguientes advertencias cuando se compi la este código: warning CS0108: La 'Pomt3D.X' porque warning CS0108: La 'Pomt3D.Y' porque
palabra clave new oculta el miembro palabra clave new oculta el miembro
es necesaria en heredado 'Point2D.X' es necesaria en heredado 'Pomt2D.Y'
El compilador de C# emite avisos porque los identificadores de la clase deriva da ocultan las definiciones que usan el mi smo identificador en la clase base. Si se quieren reutilizar los nombres, pero no que el compi lador emita avisos, se puede usar el operador new al reutilizar los identificadores en la clase derivada. El código del listado 1 1.3 compila sin emitir avisos. Listado 11.3. Có m o usar new para reutilizar identificadores de clase class P o m t 2 D
{ public public
262
mt mt
X; Y;
class
Point 3 D
: Point 2 D
{ new public new public public int
int X; int Y; Z;
} class
MyMainClass
{ public
static void M a i n ()
{ Pomt2D Pomt3D
My2DPomt My3DPomt
= new P o m t 2 D ( ) ; = new P o m t 3 D ( ) ;
My2 D P o m t .X = 100; M y 2 D P o i n t .Y = 200; My3DPoint.X = 150; My3DPoint.Y = 250; M y 3 D P o i n t .Z = 350;
} }
Cómo trabajar con métodos heredados C# permite que los métodos de la clase base y de las clases derivadas se relacionen de varios modos. C# permite los siguientes métodos: Métodos virtuales y de reemplazo Métodos abstractos
Métodos virtuales y de reemplazo Qui zás quiera que una elase derivada cambie la i mplementación de un método en una clase base, pero mant eni endo el nombre del método. Suponga, por ej em plo. que la clase Point2D implementa un método llamado PrintToConsole (). que escribe las coordenadas X e Y del punto en la consola. Qui zás también quiera que la clase derivada Point3D p r o p or c io n e su p r op i a i m pl eme nt aci ón de PrintToConsole (). Sin embargo, no puede us ar el método PrintToCon sole () de la clase Point2D. porque esa implementación sólo funciona con las coordenadas X e Y y la clase Point3D también tiene una coor denada Z. La clase Point3D debe facilitar su pro pi a implement aci ón del mi smo método PrintToConsole (). Los nombres de métodos pueden reutilizarse en clases derivadas si el método de clase base permite que el método pueda volver a ser
263
implcmcntado. La operación de reimpl ement ar un método de clase base en una clase der ivada recibe el nombre de reem pla za r el método de clase base. Al re em pl azar un método de clase base en C# hay que tener en cuenta los dos requisitos: El método de clase base debe declararse con la pal ab ra clave v i r t u a l . El método de la clase d er iva da debe dec la rar se con la p a l ab r a clave override. Los métodos de clase base que usan la pal abr a clave v i r t u a l reciben el nombre de m étodos virtuales y los de clase base que usan la p al abr a clave o v e r r i d e reciben el n ombre de métodos de r e e m p la z o . El listado 1 1.4 d emues tra cómo puede i mplement arse el método P r i n t T o C o n s o l e ( ) para las clase P oint2D y Point3D. Listado 11.4. Cómo reemplazar métodos virtuales class
Pomt2D
{ publie int X ; p u b 1 1 e int Y ; public
virtual
void
P r m t T o C o n s o l e ()
{ System.Console.WriteLine (" ({0} ,
{1} ) " , X , Y) ;
} } e 1a s s P o in t 3 D
: P o in t 2 D
{ public
int
Z;
public
override
void
Pr in tT o C o n s o l e ()
{ S ystem. Con solé. W r i t e L m e (" ((0} , {1}/
} } class
MyMamClass
{ public
static
void M a i n ()
{ Pomt2D My2DPomt PointBD M y B D P o m t
- new P o m t 2 D ( ) ; -• new P o m t 3 D ( ) ;
M y 2 D P o m t . X - 100; M y 2 D P o m t .Y = 2 00 ; M y J D P o m t . X = 150; My 3 D P o m t .Y = 2 50 ; M y i D P o m t . Z = 350 ;
264
{2})",
X,
Y,
Z);
My2DPoint.PrintToConsole(); M y 3 D P o m t .PrintToConsole () ;
} }
NOTA: La sintaxis de las llamadas W r it e L in e () hechas en el listado 11.4 es diferente de la sintaxis usada con ant&iioridad. LoSnúmeros entre llaves de la cadena son comodines. Los valoréis de losotros parámetros escoben en la consola en lugar del comodín. El comodín {0 J es reemplazado por el valor del primer parámetro, el comodín {1} es reemplazado por el valor del segundo parámetro y así sucesivamente. El listado 1 1.4 escribe esto en la consola: (10 0
,
(150,
200)
250,
350)
No se puede reempl azar un método de clase base a menos que use la pal ab ra clave virtual. Si intenta hacerlo sin usar la pal abr a clave, el compilador de C# emite el siguiente error: error CSQ506: 'Point3D.PrintToConsole() ' : no se puede reemplazar el miembro heredado 'Point2D.PrintToConsole() ' porque no esta marcado como virtual, abstract u override
Sin embargo, está permitido r eempl azar un método override. Si. por al gu na extraña razón, se quiere implementar una clase Point4D y derivarla de Point3D. es posible re empl aza r el método de PointSD PrintToConsole ().
Polimorfismo El concepto de reemplazo de métodos lleva al concepto dc polimorfismo. Cuando r eempl azamos un método, queremos llamar al método ap rop ia do desde cualquier método que llame a este método reemplazado. El listado 11.5 presenta este concepto en acción. Se ha añadido a Point2D un m é t o d o U s e P r i n t T o C o n s o 1 e ( ) q u e l l a m a al m é t o d o v i r t u a l PrintToConsole (). Point3D hereda este método de Point2D. Cuando se llama a PrintToConsole ( ) en esta función, se quiere llamar a la versión q u e p e r t e n e c e a la c l a s e a p r o p i a d a . En o t r a s p a l a b r a s , en el m é t o d o UsePrintToConsole ( ) que pertenece a la clase Point2D, se pretende lla m ar al método PrintToConsole () que pertenece a la clase Poi nt2 D. En el mét odo UsePr intToConsole ( ) que pertenece a la clase Point3D. se pre tende llamar al método r eempl azado PrintToConsole ( ) que pertenece a la clase Point3D. C omo el método PrintToConsole ( ) fue declarado como
265
un método virtual, la detección de la versión que debe ejecutarse tiene lugar aut omát icamente. El listado 1 1.5 escribe lo siguiente en la consola: (1 0 0 ,
200)
(150,
25 0,
350) Listado 11.5. Polimorfismo
class
Point2D
{ public public
int X; int Y;
public
virtual
void
PrintToConsole ()
{ S ystem. Console.W r i t e L i n e (" ({0 ) ,
{1})",
X,
Y) ;
} public
void
U s ePrintToConsole ()
{ P r i n t T o C o n s o l e ();
} } class
Point 3D
: Poin12D
{ pu b i lc int public
Z;
override
void
PrintToConsole ()
{ System.C o n s o l e . WriteLine("({0 ( , {1},
} } dass
MyMainClass
{ public
static
void M a i n ()
{ Point2D M y 2 D P o m t Point3D M y 3 D P o m t
= new P o m t 2 D ( ) ; = new P o m t 3 D ( ) ;
My2DPomt.X My2DPomt.Y
= 100; = 200 ;
My3DPomt.X My3DPomt.Y My3DPomt.Z
= 150 = 250 = 350
M y 2 D P o m t . U s e P n n t T o C o n s o l e () My3DPoint . U s e P n n t T o C o n s o l e ()
266
{2})",
X,
Y,
Z)
Métodos abstractos Algunas clases base pueden no ser capaces de propor ci onar la implementación de un método, pero puede que queremos que las clases derivadas proporcionen una implementación. Supon gamos , por ejemplo, que est amos escribiendo en C# una aplicación de geomet ría y escribimos clases llamadas Square y Circle. Decidiremos las funciones comunes que u sar á ca da for ma de la aplicación y por lo tanto implementamos una clase base llamada Shape y derivamos las clases Square y Circle de Shape: Class
Shape
{ } class
Circle
: Shape
Square
: Shape
{ } class
( } Ahora supo ng amos que decidimos que todas las formas deben ser capaces de calcular su arca, de modo que escribimos un método llamado GetArea (). El probl ema de escribir ese código en la clase base es que la clase base no tiene suficiente información par a calcular un área. Cad a forma calcula su área usando una fórmula diferente. Lo que podemos hacer es definir un método abs tra ct o en la clase base Shape. Los métodos abs tra ct os no propor ci onan una implementación propia sino que p ropor ci onan una firma de método que las clases derivadas deben implementar. Los métodos a bs tra ct os dicen "Yo no se implement ar este método, pero mi clase derivada lo hara. de modo que asegúrese de que la implementen con los parámetros y el codigo devuelto que yo especifico." Los siguientes fragment os demuest ran cómo declarar un método abs tra ct o en la clase Shape. abstract
class
Shape
{ public
abstract
double
G e t A r e a ();
}
NOTA: Las clases abstractas usan la palabra clave a b s t r a c t . No tienen cuerpo de método; en su lugar hay un punto y coma después de la lista de parámetros. Las clases abs tra ct as también son. por definición, métodos virtuales y debe usarse la palabra clave overriele par a reemplazarlos por clases derivadas:
267
class Square : Shape
{ public
override
double
G e t A r e a ()
{ // implemente
el calculo del
área
} } Las clases que contienen al menos un método abs tra ct o reciben el nombre de clases abs tra ct as y deben incluir la p al ab ra clave abstract antes de la pal abra clave de la clase. Si no se incluye la p a la br a clave abstract al definir la clase se o bt endrá un error del compi lador de C#: error CS0513: 'S h a p e .GetArea ()' en la clase nonabstract 'Shape'
es abstract
pero está
incluida
El compilador de CU no permite crear objetos a partir de clases abstractas. Si se intenta el compi lador de CU emite un error: error CS0144: No se puede crear una interfaz abstracta 'Shape'
instancia de la clase
o
Las clases abst ract as suelen usarse p ar a crear una elase base común a un conjunto de clases. Esto permite usar el pol imorfismo al a lmac enar clases d er iva das en algún tipo de colección, como se vio en un capítulo anterior.
Clases base: Cómo trabajar con propiedades e indizadores heredados En CU. se pueden m ar car como virtual, override o abstract. p ro piedades e indizadores de clases base y derivadas, igual que si fueran métodos.
Las propiedades virtual y override y los i ndizadores funcionan como las p ropi edades virtual y override. Las propiedades y los indizadores p u e den m ar car se como virtuales en una clase base y como reemplazados en una clase derivada. Las clases base pueden definir propiedades e indizadores, que no tienen implementación propia. Las clases base que contienen al menos una propiedad ab st ra ct a o un indizador deben ser m arc adas como si fueran una clase abstracta. Las propiedades abst ract as y los indizadores deben ser reemplazados en una clase base.
268
Cómo usar la palabra clave base C# proporciona la p al ab ra clave base par a que las clases derivadas puedan acceder a las funciones de su clase base. Se puede usar la p al ab ra clave base par a llamar a un const ructor de clase base cuando se crea un objeto de una clase derivada. Para llamar a un const ructor de clase base hay que colocar después del constructor de la clase derivada dos puntos, la p al abr a clave base y a cont inua ción los par ámet ros que se van a p a s ar a la clase base. El listado 1 1.6 demuestra el funcionamiento de todo esto. Añade const ructores p ar a las clases Point2D y Point3D y el const ructor Point3D llama al const ructor de su clase base. Listado 11.6. Cómo llamar a constructores de clase base class
Point2D
{ public public
int X; int Y;
public
Point2D(mt
X,
int
Y)
{ t h i s .X = X ; this.Y = Y;
} public
virtual
void
PrintToConsole ()
{ System. Consolé. W r i t e L m e ( " ( { 0 } ,
{1})",
X,
Y);
} } class
Point3D
: Point2D
{ public
int
Z;
public
Point3D(int
X,
int Y,
int
Z)
: base(X,
Y)
{ t h i s .Z = Z ;
} public
override
void
PrintToConsole ()
{ Sy st em .Consolé.WriteLine("((0},
{1}/
{2})",
X,
Y,
Z);
} } class
MyMamClass
{ public
static void M a i n ()
{ Point2D My2DPoint Point3D My3DPoint
= new P o m t 2 D ( 1 0 0 , 200) ; - new Point3D(150/ 250, 350);
269
M y 2 D P o m t .Pn nt To Co ns ol e () ; M y 3 D P o m t .P r m t T o C o n s o l e () ;
) } El constructor de la clase P o i n t 2 D establece los campos X e Y de la clase mediante los dos enteros que se pasan al constructor. El const ructor de la clase P o i n t 3 D admite tres parámetros. Los primeros dos parámetros se pasan al cons tructor de la clase base usando la pal abr a clave base y el tercero se usa para establecer el valor del campo Z de la clase derivada.
Cómo acceder a campos de clase base con la palabra clave base Tambi én se puede usar la pal abr a clave base par a acceder a miembros de la clase base. Para t rabaj ar con un miembro de clase base en la clase derivada. ha\ que anteponer al nombre del miembro la p al abr a clave base v un punto. Se puede acceder a los campos de la clase base mediante la siguiente sintaxis: base.Z = 10 0;
Tambi én se puede invocar a métodos de clase base con esta otra sintaxis: base .Prmt.ToConsolé () ;
Clases selladas Si no quiere que se derive codigo de una determinada clase, puede m ar c a r la clase con la pal abr a clave sealed. No se puede derivar una clase de una clase sellada. Se puede especificar una clase sellada escribiendo la palabra clav e sealed antes de la pal ab ra clav e class. como en este ejemplo: sealed
class
MySealedClass
Si se intenta derivar una clase de una clase derivada, el compi lador de C# emite un error: error CS05Ü9: 'Pomt. 3D' sealed ' P o m t 2 D'
: no se puede heredar
de la clase
Contención y delegación Si la herencia es una relación ES-UN. la contención es una relación T I EN E UN. Un gat o de Bi rmani a ES UN gat o (por lo que puede her edar la clase
270
DcBi rmani a de la clase genérica Gato); pero un Coche T I E N E 4 ruedas (por lo que la clase Coche puede tener cuatro objetos Rueda). El aspecto más interesante de la contención es que se puede empl ear como sustituto de la herencia. El pri nci pal inconveniente de u sar la contención en lugar de la herencia es que se pierden las ventajas del polimorfismo. Sin embargo, se obtienen los beneficios de la reutilización del código. En C#. hay dos casos comunes en los que prácticamente sólo se puede emplear la contención y no la herencia: cuando se t r abaj a con herencias múltiples y c u a n do se t rabaj a con clases selladas. A continuación se incluye un ejemplo que m u es tra cómo funciona esta técnica. Además, verá t raba jar al polimorfismo. S u po ng amo s que t enemos una clase AlarmClock y una clase Radio como las que a p a r e c e n en el s i g u i e n t e f r a g m e n t o y q u e r e m o s c r e a r u n a c las e ClockRadio que combine estas dos clases. Si C# admitiese herencias múl ti ples. se podría hacer que ClockRadio heredase de las clases AlarmClock y Radio. A continuación se podría añadir una variable booleana radioAlarm que determine si se activa la al ar ma o la radio v que reemplace SoundAlarm ( ) par a usar esta variable. Por desgracia. C# no admite herencias múltiples. Por suerte, se puede empl ear la contención en lugar de la herencia y obtener todas las ventajas de la reutilización de código. Observe cómo funciona, paso a paso: class
Radio
{ protected public
bool
on off;
void O n ()
{ i f ( !on off) C ons ol e . W n t e L m e ("Radio on_off = true;
ís now on !" ) ;
} public void O f f ()
{ if (on_of f ) Consolé.WriteLine ("Radio on off = false;
ís now
off!") ;
} } class
AlarmClock
{ prívate prívate
int int
prívate
void
currentTime; alarmTime; SoundAlarm()
{ C o n s o l e . W r i t e L m e ("Buzz !") ;
} public
void R u n ()
{
271
for
(int currTime
= 0;
currTime < 43200;
currTime++)
{ Se tC ur re n t T i m e ( c u r r T i m e ) ; íf (GetCurrentTime () == GetAlarmTime () )
{ Con solé. W r i t e L m e ("Current S o u n d A l a r m () ; break;
Time
=
{0 } !" , cur rentTime ) ;
} } } public
int
G et C u r r e n t T i m e ()
{ return
curren tT im e;
} public
void
Se t C u r r e n t T i m e (int
aTime)
{ currentTime
=
aTime;
} public
int
G e t A l a r m T i m e ()
{ return
alar m T i m e ;
) public
void
SetAlarmTiroe ( m t
aTime)
{ alarmTime
=
aTime;
} ) C om o queremos re empl aza r el método SoundAlarm( ) de AlarmClock. es recomendable hacer que ClockRadio herede de AlarmClock. Esto re quiere un pequeño c ambi o en la i mplementación de AlarmClock. Sin embargo, a cambi o se consiguen todos los beneficios del polimorfismo. Una vez que se ha seleccionado una clase base, no podemos heredar de Radio. En lugar de heredar, c r e a r e m o s u n a v a r i a b l e de m i e m b r o p r i v a d a R a d i o d e n t r o de la c las e ClockRadio. Crea mos el mi embro privado en el cons tructor ClockRadio y delegamos el trabajo de los métodos RadioOn ( ) y RadioOf f ( ) en este miembro privado. Ca da vez que la i mplementación de la clase Radio cambi a (por e)emplo. para reparar algún error), la clase AlarmClock incorporará automáticamente estos cambios. Un inconveniente del enfoque cont ención/delegación es que para añadir una nueva f uncionalidad de la clase contenida (por ejemplo, añadir nuevos métodos p ar a aj us tar el volumen) es necesario hacer cambi os en la clase conteni da par a delegar esta nueva funcionalidad en el mi embro priv ado. class
(
272
ClockRadio
: AlarmClock
prívate Radio radio; // Declarar otras variables public
de m i e m b r o . ..
C l o c k R a d i o ()
{ radio = new R a d i o (); // Establecer el valor de otras
variables
de miembro. . .
} //---------public void
Delegar en Radro R a d i o O n ()
-----------
{ r a d r o .On () ;
} public
void
R a d i o O f f ()
{ r a d i o .O f f () ;
} } Ya hemos implementado compl et ament e la funcionalidad de la radio mediante el patrón contención/delegación. Es hora de añadir la funcionalidad A l a r m C l o c k . En primer lugar, se añade una variable pri vada r a d i o A . 1 arrri que determina si debe sonar la radio o si debe sonar el timbre cuando se dispare la alarma: class
ClockRadio
: AlarmClock
{ prívate bool radioAlarm; // Declarar otras variables m i e m b r o . .. publrc C l o c k R a d i o ()
{ radioAlarm = false; // Establecer el valor de otras variables miembro. . .
} //---------public void
Nueva funcionalidad C lo ckRadio---------SetRadioAlarm (bool useRadio)
I radioAlarm = useRadio;
} } Como queremos reempl azar la función S o u n d A l a r m ( ) de A l a r m C l o c k . . necesitamos c amb ia r la declaración del método S o u n d A l a r m ( ) para que sea protegida. Además, como queremos que la función P u n ( ) tenga un c o mp or t a miento polimórfico. tendremos que hacer este método v irtual: class
AlarmClock
í prívate private
int int
currentTime; alarmTime;
273
protected
virtual
void
SoundAlarm()
{ C o n s o l é . W n t e L i n e ("Buzz !") ;
} // Otros
m é t o d o s . ..
} R e em p la z ar SoundAlarm ( ) en AlarmClock es sencillo. De pendi endo de los valores de radioAlarm. se enciende la radio o se l lama al mét odo SoundAlarm ( ) de la clase base que hace sonar el timbre, como sigue: ClockRadio
: AlarmClock
{ prívate prívate
Radio radio; bool radioAlarm;
//---------AlarmClock Reemplazado ----------protected override void SoundAlarm))
{ íf
(radioAlarm)
{ R a d i o O n ();
} e 1se
{ b a s e .S o u n d A l a r m ();
} } / / Ot ros met o d o s . . .
} ¡Y en esto consiste básicamente! Algo muy interesante está ocurriendo dentro del mét odo Run ( ) de la clase AlarmClock (que aparece en el siguiente fr a g mento de código): el compo rt ami en to polimórfico al que aludíamos. La clase ClockRadio hereda este método de su clase base y no lo reemplaza. Por tanto, este mét odo Run ( ) puede ser ejecutado desde un objeto de AlarmClock o un objeto de RadioClock. Como dec la ramos SoundAlarm ( ) de modo que f u e se v i r t u a l . C # es lo s u f i c i e n t e m e n t e i n t e l i g e n t e c o m o p a r a l l a m a r al SoundAlarm ( ) ap ro p ia d o dependiendo de qué clase está i nvocando al método
Run () . class
AlarmClock
{ prívate prívate public
int int void
currentTime; alarmTime; Run ()
( for
274
(int currTime
= 0;
currTime
< 43200;
currTime++)
Se tC ur re n t T i m e ( c u r r T i m e ) ; íf (GetCurrentTime () == G e t A l a r m T i m e () )
{ Consolé . W r i t e L m e ("Current Time = S oundAla r m () ; break;
{0 } \ " ,
curren t.Ti me ) ;
}
//
Otros
m é t o d o s ...
} Este ejemplo resalta uno de los puntos fuertes de la herencia: el polimorfismo. Ademas, cuando se añaden nuevos métodos públicos (o protegidos) a la clase base, están automát icamente disponibles en la clase derivada. El listado 1 1.7 es el listado compl et o con un método m a i n ( ) de ejemplo par a que pueda e xpe r im en tar con él. Listado 11.7. La herencia múltiple puede ser simulada usando la contención u s i ng
System;
namespace
Containment
{ class
Radio
{ protected public
bool
void
on_off;
O n ()
{ íf (!on off) Consolé . W r i t e L m e ("Radio on off - true;
ís now
011 !" ; ;
} p ub l ic
v oid
O f f ()
{ íf (on off) C o n s o l e . W n t e L i n e ("Radio on off = false;
ís
n ow
of f !" 1 ;
} } class
AlarmClock
{ prívate prívate
int int
protected
currentTime; alarmTrme;
virtual
voi d
SoundAlarmu
{ C o n s o l é . W r i t e L m e ("Buzz !" ) ;
}
275
public
void
R u n ()
{ for
(int currTime
= 0; currTime
< 43200;
c urrTime++)
{ if
S et Cu r r e n t T i m e ( c u r r T i m e ) ; (GetCurrentTime () == GetAlarmTime () )
{ C o n s o l e .WriteLine ("Current cur r e n t T i m e ) ; S o u n d A l a r m (); break;
}
public
int
GetCurrentTime ()
{ return
curre nt Ti me ;
} public
void
Se t C u r r e n t T i m e (int
aTime)
{ currentTime
= aTime;
} public
int
G e t A l a r m T i m e ()
{ return
alarmTime;
} public
void
SetAlarmTime ( m t
{ alarmTime
= aTime;
}
class
ClockRadio
: AlarmClock
{ private private public
Radio radio; bool radioAlarm; C l o c k R a d i o ()
{ radio = new R a d i o (); radioAlarm = false;
} //---------public void
Delegar en Radio R a d i o O n ()
{ r a d i o .O n () ;
}
276
aTime)
Time
=
{0 } ! " ,
public
void
RadioOff()
{ radio.Off () ;
} AlarmClock Reemplazado ---------//---------protected override void S o u n d A l a r m ()
{ if
(radioAlarm)
{ RadioOn () ;
} else
( b a s e .S o u n d A l a r m ();
} } //---------public void
Nueva funcionalidad de ClockRadio SetRadioAlarm(bool useRadio)
{ radioAlarm = useRadio;
class
Contlnh
{ static
int M a i n ( s t r i n g [] args)
{ ClockRadio
clockRadio;
clockRadio
= new C l o c k R a d i o () ;
c l o c k R a d i o . S e t R a d i o A l a r m (true ) ; clockRadio.SetAlarmTime (10 0) ; c l o c k R a d i o .R u n () ; // esperar a que el usuario reconozca los resultados Consolé . W r i t e L m e ("Hit Enter to termínate..."); C o n s o l é .Read () ; return 0;
} } }
La clase de objeto .NET Todas las clases de C# derivan en última instancia de una clase construida en N E T F ramework l lamada o b j e c t . Si se escribe una clase en C# y no se define
277
una clase base par a ella, el compi lador de C # la deriva de o b j e c t sin ningún aviso. S upongamos que escribimos una declaración de clase de C# sin una d ecla ración de clase, como en este ejemplo: class
P o in 12 D
Esto es equivalente a derivar la clase de la clase base . NET S y s t e m . Ob j e c t : class
Point2D
: System.Object
La pal abra clave obj ect puede usarse como si fuera un alias del identificador S y s t e m . Obj e c t : class
Point2D
: object
Si se deriva desde una clase base, hay que tener en cuenta que la clase base se deriva desde obj ect o desde otra clase base que herede de obj ect. Al final, la j er ar qu ía de las clases base siempre i ncl i ne la clase N E T object. Graci as a las reglas de herencia de C#. la funcionalidad de la clase NE T object esta disponible para todas las clases de C#. La clase N ET object contiene los siguientes métodos:
•
public virtual bool Equal s (ob j ect obj) :Compara dos objetos y devuelve true si son iguales y false en caso contrario. Este método esta m ar cad o c omo virtual, lo que significa que se puede re empl a zar en las clases de C#. Quizás quiera reemplazar este método para c o m p a rar el estado de dos objetos de la clase. Si los objetos tienen los mismos valores para los campos, puede dev olv er true: si los valores son diferen tes puede devolver false. public virtual int GetHashCode ( ) : Calcula un código hash para el objeto. Este método esta marcado como virtual, lo que significa que se puede reemplazar en las clases de C'#. Las colecciones de clase de NET pueden llamar a este método para generar un codigo hash que ayude en las consultas y clasificaciones y las clases pueden reempl azar a este método par a que genere un codigo hash que tenga sentido para la clase.
N O T A : El código hash es una clave única para el objeto especificado.
Type GetType ( ) : Devuelve un objeto de una clase NET llamado Type que proporci ona información sobre la clase actual. Este método no esta m ar cado como virtual, lo que significa que no se puede reempl azar en las clases de C#. public
public virtual s t n n g ToString ( ) : Devuelve una re pres en tación de cadena del objeto. Este método está marcado como virtual, lo que
278
si gni fi ca que se p ued e r e e m p l a z a r en las cl as es de C#. Un mét od o T o S t r m g ( ) es i n v o c a d o c u a n d o a l g ú n m é t o d o N E T c o m o S y s t e m .Consolé .WriteLine ( ) necesita convertir una variable en una cadena. Se puede reemplazar este método p ar a que devuelva una c a de na más apr opi ad a par a representar el estado de una clase determinada. Qui zás quiera, por ejemplo, añadir el signo adecuado a cada divisa junto a la representación de cadena de la clase Money.
•
protected
virtual
void
Finalize(): Puede ser llamado (o
puede no serlo) cuando el recolector de objetos no utilizados del entorno de ejecución común destruye el objeto. Este método está m a rc a do como vi r tual. lo que significa que se puede r eemplazar en las clases de C#. También está m a r c ad o como protegido, lo que significa que sólo puede ser l lamado desde dentro de la clase o desde una clase deriv ada y no puede ser llamado desde fuera de una j erarquí a de clase. La i mplement aci ón del objeto Finalize ( ) de N E T no hace nada, pero puede implementarlo si quiere. Tambi én puede escribir un dest ructor p ar a su clase, lo que produce el mi smo efecto (pero tenga cui dado al usarlo). De hecho, el compi lador de C# convierte el código destructor en un método reemplazado Finalize ().
protected object MemberwiseClone ( ) : Crea una copia idéntica del objeto, asi gna al clon el mismo estado que el objeto original y devuelve el objeto copiado. Este método no está m ar cado como v irtual, lo que signi fica que no se puede reempl azar en las clases de C#. Tambi én está m a r c a do como protegido, lo que significa que sólo puede ser llamado desde dentro de la clase o desde una clase derivada y no puede ser llamado desde fuera de una j erarquía de clase. Las est ructur as de C# no pueden tener clases bases definidas explícitamente pero se derivan implícitamente de la clase base ob j ec t . To do el c o mp or t a mi e n to de la clase object está disponible p ar a las estructuras y las clases de C#.
Cómo usar boxing y unboxing para convertir a tipo object y desde el tipo object Como todas las clases y estructuras derivan en ultima instancia del tipo obj ect de NET. éste suele usarse a menudo en listas de par ámet ro s cuando el método necesita ser flexible respecto a los datos que recibe. Observe, por ejemplo, el método S y s t e m .Consolé .WriteLine ( ) u s a do en este libro. Este mismo método ha sido usado p a r a escribir cadenas, enteros y tipos dobles en la consola sin usar ningún oper ador de conversión explícita. En el listado 1 1.4. se escribe una cadena con comodines y los comodines son s us ti tuidos por los valores de los p ar ámet ro s que se le proporcionan. ¿Có mo funciona
279
en re al idad 9 ¿Cómo sabe S y s t e m . C o n s o l é . W r i t e L i n e ( ) qué tipos le va a pasar'.' La r e s p u e s t a es que no p ue de s aberl o. M i c r o s o f t c o n s t r u y ó el mét odo S y s t e m .Consolé .Wr iteLine ( ) mucho antes de que t raba jás emos con el listado 1 1.4. por lo que no podía saber qué tipos de datos le pasaría. Microsoft implemento un método System .Consolé .Wr i teLine ( ) con la siguiente forma: public static void WriteLine (string format, params obj ect [] arg) ;
El primer p ar amet ro es la cadena que se va a generar y el segundo parámet ro es una matriz de parámet ros que contiene una cantidad de elementos que se c a lc u la cuando se compila el codigo. ¿Pero cuál es el tipo de la matriz de p a r á m e t r o s '} La m a t r i z de p a r á m e t r o s es del t ipo ob j ect. O b s e r v e e st a l l a m a d a a
WrrteLine (): System.Consolp.WriteLine (" ((0) ,
{ 1} ) ",
X , Y );
El compilador de C# convierte los parámetros X e Y en una matriz de parámetros y llama a WriteLine (). Los parámetros X e Y son de tipo entero, lo que. como ya ha visto, es un alias de una est ructura llamada System .Int32 . Como las estructuras de C# heredan del tipo de obj ect de NET. estas variables heredan del tipo obj ect y pueden ser us adas en una matriz de parámetros. Los literales, que se han estudiado con anterioridad, son algo más co mp l ic a dos. En lugar de usar objetos, puede igualmente escribir el siguiente código: S ys t e m .C ons ole.WriteLine (" ({0} ,
{1})",
100,
2 0 0);
Este codigo también funciona correctamente. ¿Có mo sabe C# cómo conv ertir un valor literal en un objeto par a que pueda ser us ado en una llamada de método que necesite un obj et o9 La respuesta esta en una técnica llamada b oxm g. La técnica de boxing permite que cualquier tipo de valor, incluso un literal, se pueda convertir en un objeto. Cuand o el compi lador de C# encuent ra un tipo de valor par a el que se necesita un tipo de referencia, crea una variable de objeto temporal y la asigna el valor del tipo de valor. Esta técnica "encierra" el valor en un objeto. Observe nuevament e la anterior llamada WriteLine (): System.Console.WriteLine (" ({0} , {1})", 100, 200);
El compi lador de C# encuent ra los literales y los encierra en objetos. Los objetos se envían a la llamada del método en la matriz de parámet ros y a cont inua ción se eliminan los objetos temporales. Observe las siguientes instrucciones: int MyVa lúe = J2 3; object MyOb]ect = MyValue;
C# encierra el valor de MyValue en el objeto MyObject.
280
C# también permite la técnica de unboxing. que es simplemente el proceso opuesto al boxing. El unboxi ng reconvierte los tipos de referencia en tipos de valor. Por último, cada tipo es un objeto. El boxing y el unboxing nos ayudan a visualizar esta idea. Como todo es un objeto, todo (incluidos los literales) puede ser tratado como tal y los métodos de la clase object pueden llamarlos. El siguiente codigo funciona graci as a la técnica de boxing del compi lador de C#.: strinq M y S t r m g ; M y S t n n g = 12 3 .ToS t r ing () ;
El c o m p i l a d o r de C# a p l i c a la o p e r a c i ó n b o x i n g al v a l o r literal 123. t ras formánd ol a en un objeto v llama al método ToString () sobre ese objeto.
Resum en En la terminología de la pro gr amaci ón de software orientado a objetos, la herencia se usa par a describir una clase que hereda miembros de una clase base. C# admite la herencia simple, en la que una clase puede derivarse de una sola clase base. C# no admite la herencia múltiple, que si es admitida por algunos lenguajes orientados a objetos par a permitir que una clase pueda deriv arse de mas de una clase base. Las clases base de C# se especifican al declarar una clase. El identificado!' de clase base sigue al nombre de la clase deriv ada cuando se declara la clase C# permite que los miembros de clase pertenezcan a un at ri but o de ámbito. El ámbito de un miembro determina su accesibilidad para las clases derivadas y los fragmentos de código que t rabaj an con los objetos de la clase. Los miembros de clase marcados como public son visibles par a las clases derivadas y para el codigo que crea los objetos de la clase. Los mi embros de clase marc ados como private solo son visibles para la clase en la que están definidos o desde clases derivadas de la clase. Los miembros de clase marcados como internet 1 son visibles para todo el codigo en su mismo archivo binario, pero no son visibles fuera de los archivos binarios. Los miembros de clase marcados como prot ected internal son visibles par a cualquier codigo en su mismo archivo binario y para las clases externas que se deriven de la clase. Los mi embros de clase que no tienen ni nguna pal abr a clave de ámbito son. por defecto, privados. Los métodos y las propiedades de clases base pueden implementarse de nuevo en clases derivadas para prop or ci on ar nuevas implementaciones. Los métodos y propiedades virtuales, que están mareados con la pal abr a clave de C# v i r t . u a 1. pueden i mplementarse nuevament e en clases derivadas, siempre que la nueva i mpl eme nt ac ion m a n t e n g a el mi s mo i dent ifi cador . tipo d ev ue lt o y lista de par ámet ros. Las nuevas implementaciones de métodos virtuales reciben el n o m bre de r e e m p la z a d a s x deben e st ar m a r c a d a s con la p a l a b r a clave de C# o v errid e.
281
Los m éto d o s a b s tr a c to s son mét odo s de clases bas e que no pueden ser implementados. Los métodos abs tr act os no suelen i mplement arse en clases base porque la clase base no tiene suficiente información como par a ofrecer una implementación completa. Las clases que contienen al menos un método abstracto reciben el nombre de clases abs tra ct as y deben usar la p al ab ra clave abstract en la declaración de la clase. C# dispone de la pal abr a clave base que permite que las clases derivadas accedan a los miembros de una clase base. Se puede anteponer a los idcntificadores de miembros la pal abr a clave base y se puede usar esta p al abr a clave para llamar a un const ructor de clase base desde un const ructor de clase derivada. Por defecto, se puede us ar cualquier clase de C# como clase base y cualquier clase puede derivarse de cualqui er otra clase. Puede evitar este comport ami ent o marcando una clase de C # con la p al abr a clave sealed. Las clases selladas no pueden us ar se como clase base y el compi lador de C# no permite que se deriven clases a partir de clases selladas. Todas las clases de C# y. de hecho, cualqui er clase i mplement ada en un len guaje NET. deriva en última instancia de la clase N E T S y s t e m .Ob j e c t . La pal abra clave de C# object es otro nombre p ar a el identificador de clase System .Object. La clase Sys t e m .Ob j ect contiene algunos métodos que pueden usar las clases derivadas y muchos de estos métodos pueden ser r eempl a zados. La clase System. Object proporciona funcionalidad par a definir igualdad de objetos, cálculo de código hash. representaciones de cadenas, finalización de código y clonación de objetos. El tipo ob j ect puede ser us ado como un método o variable de nombre de tipo y cualquier variable de C# puede usarse como un objeto object. C# convierte a ut omát icame nte al gunos tipos de valor, como tipos de valor y literales numéricos y objetos de tipo ob j ect mediante las t écni cas de boxing y unboxing. La técnica de Boxing encierra un valor en un objeto v la t écnica de unboxi ng devuelve el valor del objeto a su tipo de valor original.
282
Parte III
C# avanzado
283
Cómo trabajar con espacios de nombre h vl
Las clases que diseñe las us ar a en su código y p robablement e en el codigo de otras personas. Las clases de CU pueden ser usadas por una aplicación V B .N E T o desde dentro de una pagina A S P. NE T. Ademas, las clases pueden ser usadas en combinación con otras clascs di señadas por otros progr amadores de N E T El codigo escrito en un lenguaje N E T hace referencia a las clases por sus nombres y todas estas clases usadas en combinación suscitan un dilema eviden te: (,Que ocurre si un p ro g ra ma do r quiere usar dos clases al mismo tiempo.' Supongamos que escribe una clase de CU que lee archivos de una base de datos y llama a esa clase Recordset. El codigo que quiera usar la clase puede crear objetos como el siguiente: Recordset MyRecordset - new Recordset í, ¡;
Ahora s upongamos que empaquet a las clases en un ens ambl ado . NET y di s tribuye ese ensambl ado par a que sea usado por otras aplicaciones. Ademas, s u ponga que alguien consigue su ens ambl ado y lo íntegra en su aplicación. <,Que ocurri rá si la misma aplicación también hace uso de otro ens ambl ado escrito por otra persona y que también contiene una clase llamada Recordset ? Cuando el código de la aplicación crea un nuevo objeto Recordset. ¿qué clase se usa para crear el objeto‘) ¿La nuestra o la clase del otro cns ambl ado‘., Este problema puede resolverse mediante el concepto de CU de los espacios de nom bres. Los espacios
285
de nombres organizan las clases mediante un g ru po designado y el nombre del espacio de nombre puede ser us ado p ar a diferenciar dos clases con el mismo nombre. El código C# debe us ar espacios de nombre p ar a posteriormente ayudar a identificar nuestras clases mediante un grupo común, especialmente si está p l a neando construir un ensambl ado p ar a que sea usado por otros programadores. Los espacios de nombre pueden incluso ser útiles en las aplicaciones de C # que cons truyamos , porque de este modo pueden u sar e nsambl ados externos que usen nombres de clase iguales a los nuestros.
Cómo declarar un espacio de nombre Un espacio de nombre se declara con la pal abr a clave de C# namespace seguida por un identificador de espacio de nombre y llaves. Las clases que se incluirán en el espacio de nombre deben declararse dentro de las llaves del espacio de nombre, como se puede ver en el siguiente código: namespace
MyClasses
{ class
MyFirstClass
Este fragmento de código declara una clase llamada MyFirstClass en un espaci o de nombre llamado MyClasses. Otro pr og ra ma dor también podría es cribir otra clase llamada MyFirstClass, pero mientras el otro p ro gr amad or use un espacio de nombre diferente, el compi lador de C# encontr ará la clase ade cuada que debe usar par a una instrucción particular. Es posible declarar espacios de nombre dentro de otros espacios de nombre. Basta con encerr ar la declaración del segundo espacio de nombre en el interior de la pri mera declaración. namespace
MyClasses
{ namespace
MylnnerNamespace
{ class
■’
286
MyFirstClass
w :pfoáii%' <&?oiapiwm yaiganas
rutinas de emulación de terminales. Estos espacios de nombre serían W i d g e t .Compression y W i d g e t .Emulation, que agrupan los
productos de la compañía pero también los mantiene separados mediante el espacio de nombre Widget, Si no quiere ani dar los espacios de n ombre de esta manera, puede conseguir el mismo efecto declarando las dos declaraciones de espacio de nombre en la misma instrucción y separándol os con un punto, como se indica a continuación: namespace
MyClasses.MylnnerNamespace
{ class
MyFirstClass
{ } } Los siguientes tipos de declaraciones pueden aparecer en un espacio de nombre: Clases Estructuras •
Interfaces
•
Enumeraciones Delegados
Cual qui er declaración de un tipo que no esté en esta lista produce errores del compi lador c uando se intenta compi lar la aplicación.
Cómo declarar un espacio de nombre en varios archivos fuente El compi lador de C# permite el uso del mi smo nombre de espaci o de nombre en varios archivos fuente. A continuación crca un archivo binario que combina todas las clases en el mi smo espacio de nombre. Supongamos, por ejemplo, que quiere construir un ens ambl ado cuyas clases residan en un espacio de nombre llamado M y A s s e m b l y . que quiere escribir dos clases p ar a incluirlas en ese ensambl ado y que quiere definir las clases en a r ch i vos separados. Puede simplemente reutilizar el nombre del espacio de nombre en los dos archivos fuente. El primer archiv o fuente puede contener la declaración de la pri mera clase, como en el siguiente ejemplo: namespace
MyAssembly
{
287
class MyFirstClass
{ } } El segundo archivo fuente puede contener la declaración de la segunda clase y puede us ar el mismo nombre de espacio de nombre: namespace
MyAssembly
{ class
MySeconde 1ass
{ } } C uand o los dos archivos fuente se construyen en un solo ensambl ado, el c o m p i l a d o r de C# c r e a un e n s a m b l a d o con un sol o e s p a c i o de n o m br e. M y A s s e m b l y . con dos clases en el espacio de nombre. Esto tiene una ventaja par a el pr ogr amad or en caso de que quiera separar a lgunas funcionalidades en distintos archiv os o simplemente, si quiere reducir al mínimo la longitud de cada archiv o fuente.
Cómo usar clases en un espacio de nombre Si quiere hacer referencia a una clase en un espacio de nombre específico, ant eponga al nombre de la clase el nombre de su espacio de nombre: M y C 1 a s s e s .My F i r s t C 1 ass
MyObj ect
- new MyClasses.MyFirstClass (•;
Esta sintaxis ayuda a distinguir entre las clases de diferentes codigos base con el mismo nombre. El compi lador de C# ya tiene suficiente información par a en cont rar la clase correcta, porque también sabe a qué espacio de nombre debe dirigirse par a encontrar las clases que estamos buscando. C uand o se t rabaja con clases declaradas en espacios de nombre anidados, deben a pa rec er todos los nombres de espacios de nombre cuando se hace referen cia a esa clase: Name^space 1 .Namespace! .MyClass MyObj ect N a m e s p a c e 1.N a m e s p a c e 2 . M y C l a s s (i;
= new
El listado 12.1 ilustra el concepto de espacio de nombre Listado 12.1. Clases en espacios de nombre diferentes namespace
N am e s p a c e 1
{ class
{
288
TestClass
public TestClass()
{ System. Consolé . W r i t e L m e ("Helio Ñ a m e s p a c e l . T e s t C l a s s !");
from
} } } namespace
Namespace2
{ class
TestClass
{ public
T e s t C l a s s ()
{ System. Consolé.WriteLine("Helio N a m e s p a c e 2 . T e s t C l a s s !");
from
} } } class
MainClass
{ public
static
void M a i n ()
{ N a m e s pa ce 1.TestClass Ñamespace2.TestClass
Objectl 0bject2
= new N am e sp ac e1.TestClass () ; = new Namespace2.TestClass () ;
} } El código del listado 12.1 declara dos clases llamadas T e s t C l a s s . Cada una de las declaraciones de clase está en un espacio de nombre diferente y el const ructor de cada clase escribe un mensaje en la consola. Los mensajes son ligeramente diferentes de modo que se puede saber cual es el mensaje que emite cada clase. El m é t o d o M a i n ( ) del l i s t a d o 12.1 c r e a d os o b j e t o s , u n o de t i p o N a m e s p a c e l . T e s t C l a s s v otro de tipo N a m e s p a c e . T e s t C l a s s . Como los constructores de las clases escriben mensajes en la consola, si se ejecuta el código del listado 12.1 obtendremos como resultado la figura 12.1. Observe que la clase M a i n C l a s s del listado 12.1 no está encerrada en una declaración de espacio de nombre. Esto es per fectamente valido en C#. No es necesario encerr ar las clases en declaraciones de espacio de nombre. Sin e m b a r go. las clases que no están encerradas en espacios de nombre no pueden usar el mi smo nombre en ot ra clase definida sin un espacio de nombre Si necesita usar una clase que está declarada en un espacio de nombre, debe usar el nombre de su espacio de nombre al usar el nombre de la clase. Si no hace esto, el compilador de C# emitirá un mensaje de error. Suponga, por ejemplo, que el método M a i n ( ) del listado 12.1 intenta crear un objeto de la clase T e s t C l a s s : TestClass
Objectl
= new TestClass
();
289
C :\W IN D 0W S\System 32\cm d.exe C : \ > 1. i r; *. i n y 1 A.
D
1 .e x o
■
He l io f rom N
C: \ >
Figura 12.1. C ómo hacer referencia a clases dentro de espacios de nombre
El compi lador de C# no puede encontrar una clase llamada TestClass definida fuera de un espacio de nombre y emite el siguiente error: error CS0234: El tipo o el nombre del espacio de nombres 'TestClass' no existe en la clase o el espacio de nombres 'MamClass' (¿falta una referencia de ensamblado?)
Si revisa los ejemplos de los capítulos anteriores, co mp ro b ar á que esta es la sintaxis que hemos estado usando en todas nuestras llamadas a WriteLine (). como m ues tra el siguiente ejemplo: System .C on so lé .W r i t e L i n e ("Helio
from
C#!");
El método WriteLine ( ) está en una clase llamada Consolé y la clase Consolé está definida en un espacio de nombre de N E T llamado System.
Cómo ayudar a los espacios de nombre mediante la palabra clave using Hay varias maneras de usar la pal ab ra clave de C# using par a facilitar el t rabajo con los espacios de nombre y a hor rar u na buena cantidad de código. A primera vista, la pal ab ra clave using parece la típica directiva #include de C/ C++. No se deje engañar; sus ventajas son mu ch o más potentes. Las siguientes secciones describen algunas de estas ventajas.
Cómo crear alias de nombres de clase con la palabra clave using Escribi r nombres de clase per fectamente válidos y que incluyan nombres de espacio puede ser un poco tedioso, especialmente si los nombres son largos. Pue-
290
de usar la p al abr a clave u s i n g p ar a crear un alias p ar a el identificador de clase completo y, c ua ndo se h a ya est abl eci do el alias, puede us ar lo en l ugar del identificador de clase completo. Puede cr ear un alias mediante una instrucción que tenga la siguiente estructura: La p al ab ra clave using El nombre del alias Un signo igual El nombre de la clase c ompl et a con el identificador de espaci o de nombre Un punt o y c oma de fin de instrucción El listado 12.2 se añade al listado 12.1 creando alias p a r a los nombres de clase y acort ando así sus equivalentes. El método M a i n ( ) utiliza los nombres a c o r t a dos p ar a t rab aj ar con los objetos de las clases. Listado 12.2. C óm o crear alias de los nombres de clase using Classl using Class2 namespace
= N a m e s p a c e l .T e s t C la ss ; = N a m e s p a c e 2 .TestClass ;
Namespacel
{ class
TestClass
{ public
T e s t C l a s s ()
{ System. Consolé.WriteLine("Helio N a m e s p a c e 1.T e s t C l a s s !");
from
} } } namespace
Namespace2
{ class
TestClass
{ public
T e s t C l a s s ()
{ System. Consolé.WriteLine("Helio N a m e sp ac e2 . T e s tC la ss !");
from
} } } class
MainClass
{ public
static void M a i n ()
{
291
Cla ssl Object 1 = new Cíassi () ; C lass2 Object2 = new Class2()
} El listado 12.2 escribe los mismos mensajes que el anterior ejemplo. Puede ver estos resultados en la figura 12 . 2 . CV
C:\WIND0WS\System32\cmd.ene
JJQJxJ
C:\>Listinyl2 2.exe Hello fron Nanccpacel .Ies fcClass * Hello fron Name-,; pace2 .Tes t C 1as s ?
Figura 12.2. Cómo crear alias de los nombres de clase
Las instrucciones u s i n g deben incluirse en el código fuente antes de que se declaren los espacios de nombres. Si aparecen después de las declaraciones de espaci o de nombre, recibirá el siguiente mensaje de error del compi lador de C'#: error CS152 9: Una clausula using debe ir delante elementos restantes del espacio de nombres
de todos
los
En capítulos anteriores ya vimos que las p al ab ras clave de C# que definen los tipos de variable son en realidad est ructuras definidas por N E T Framework. Observe de nuevo la tabla 7 . 1 y preste atención a lo siguiente: Las es tructur as de tipo de valor residen en el espacio de nombre de NET S ystem . La pal abra clave u s i n g se usa par a crear alias de los nombres de estruc t uras de N E T con las pal ab ras clave de C # equivalentes. Puede imaginar como se implementa la tabla 7 . 1 en N E T Framework mediante instruccio nes de C# como las siguientes: using sbyte = System.SByte; using byte = System.Byte; using short = Sy stem.In116 ; // ... mas declaraciones . . .
292
Puede crear alias de nombres de espacio de nombres así como de clases, como demuest ra el listado 12.3. Listado 12.3. Creación de alias de espacios de nombre using NI - Namespacel; using N2 - Namespace2; namespace
Namespacel
{ c1a s s Téstelass
{ p u b 1 ic
Te s tClass ()
{ S ys t.em. Consol e .WriteLine ("Helio Ñame space 1 .Tes t C 1ass !"! ;
f
r om
} } ) namespace
Namespace2
{ c 1a s s Tes t C 1ass
{ public
TestClass ()
1 S ystem. Con solé. W r i t e L m e ("Helio N a m e s p a c e l . T e s t C l a s s !");
trom
} ) } class
MainClas s
{ public
static void M a m ( )
{ NI .TestClass N2.TestClass
Objectl Object2
= new N I .TestClass () ; = new N2.TestClass () ;
} }
Cómo declarar directivas de espacio de nombre con la palabra clave using Si usa una clase declarada en un espacio de nombre, debe anteponer al nombre de clase el nombre del espacio de nombre, aunque no este t r ab aj and o con ningún otro espacio de nombre que pueda tener una clase con el mismo nombre. Esta es la razón por la que los ejemplos que hemos usado hast a este moment o siempre han llamado a WriteLine ( ) con el calificador del espacio de nombre System: Syst em.Consolé.WriteLine ("Helio f rom C# !" ) ;
293
Por defecto, si no se usa el nombre de espacio de nombre, el compi lador de C# emite un error: error CS0234: El tipo o el nombre del espacio de nombres 'TestClass' no existe en la clase o el espacio de nombres 'MainClass' (¿falta una referencia de ensamblado?)
An teponer ca da n ombre de clase con nombres de espacios de n ombre como
System es tedioso, especialmente si hay que hacerlo muchas veces. A f o r t un ad a mente, se puede u s ar la p a l ab r a clave using p a r a reducir el t iempo de codi fi ca ción. Al usar la palabra clave using con un n ombr e de espacio de n om br e se advierte al compi lador de C# que se quiere hacer referencia a clases en el espacio de nombres desi gnado sin a nt epone r a los n ombres de clase el nombre de espacio de nombre. Observe, p or ejemplo, la siguiente instrucción: using
System;
Esto recibe el nombre de directiva de espacio de nombre. Las directivas de espacio de n ombre avisan al c ompi lador de C# de que el código u s a r á clases del espacio de nombres y que las clases no llevarán ant epuest o el nombre del e s p a cio de nombre. El c ompi lador de C# se en car ga de encontr ar la definición de c ada clase en ca da espacio de n ombre al que se hace referencia en u na directiva de espacio de nombre. El listado 12.4 es un a modificación del listado 12.2; incluye un a instrucción using que hace referencia al espacio de nombre de . NET System. Listado 12.4. Cómo usar una directiva de espacio de nombre using System; using using
Classl Class2
namespace
= N a m es pa ce 1.T e s t C la ss ; = Ñamespace2.T e s tC la ss ;
Namespacel
{ class
TestClass
{ public
T e s t C l a s s ()
( C o n s o l é .W r i t e L i n e ("Helio
} } } namespace
Namespace2
{ class
TestClass
{ public
294
T e s t C l a s s ()
from
N a m e s p a c e l .T e s t C l a s s !");
Console. W r i t e L m e ("Hello
class
from
Namespace2.TestClass !" )
MainClass
{ public
static void M a i n ()
{ Class i O b j e c t 1 = new C l a s s 1 () ; Class2 0bject2 = new C l a s s 2 ();
} } La directiva de espacio de nombre System del listado 12.4 permite que el código haga referencia a la clase Consolé sin que se le ant ep on ga el espacio de nombre System.
Un rápido recorrido por los espacios de nombre de .NET . NET F ramework tiene clases en multitud de espacios de nombre predefinidos que pueden usarse en otros códigos de C#. La siguiente lista describe al gunos de ellos: •
El e s p a c i o de n o m b r e S y s t e m c o n t i e n e c l a s e s q ue i m p l e m e n t a n funcionalidades básicas, como conversiones de tipos de datos, operaciones matemáticas, invocación a p rogr amas y gestión del entorno de procesos. El espaci o de n ombre System es el m ayo r de los propor ci onados por NET. N E T Fr amewo rk tambi én contiene el espacio de nombre Microsoft que b ri nda compat ibi lidad con versiones anteriores, además de otros ele mentos generalmente útiles. El espacio de n ombr e S y s t e m .CodeDOM contiene clases que r epr es en tan los elementos de un do cument o de código fuente. El espacio de nombre System. Collections contiene clases que i mplementan colecciones de objetos, como listas, colas, matrices, tablas hash y diccionarios.
•
El espacio de nombre S y s t e m .ComponentMode 1 contiene clases que se usan p a r a crear componentes v controles durante el t iempo de diseño y ejecución. Este espacio de nombre pro po rci ona interfaces y clases par a crear atributos, establecer enlaces a varias fuentes de datos, conceder li cencias de componentes, ade má s de p a r a convertidores de tipos.
295
El espacio de nombre System. Data contiene clases que c omponen la arquitectura de acceso a datos de A DO . N ET . La arquitectura A D O . N E T permite construir componentes que pueden gestionar datos de varias fuen tes de datos en modo desconect ado o conectado. El espacio de nombre System. Diagnostics contiene clases que a y u dan a detectar errores en aplicaciones de . NET y supervisar la ejecución del código. El espacio de nombre S y s t e m .Diagnostics también c on tiene clases que permiten supervisar la actuación de la aplicación mediante contadores de rendimiento y registros de eventos. Aunque la funcionalidad no se considera realmente un diagnóstico, este espacio de nombre también permite iniciar y detener procesos. El espacio de nombre System. Drawing contiene clases que implementan funcionalidad de dibujo del Dispositivo de mt erfaz gráfica (GDI). Este espacio de nombre no está disponible por defecto; hay que crear una refe rencia a él desde el menú Proyecto. El espacio de nombre System. 10 contiene clases que pueden leer y es cribir flujos de datos y archivos de disco. Las clases contenidas en este espacio de nombre pueden gestionar la entrada y salida de archivos sincró nica y asincrónica. El espacio de nombre Sys t e m .Messaging contiene clases que t r a b a jan con colas de mensajes. Este espacio de nombre no está disponible por defecto; hay que crear una referencia a él desde el menú Proyecto. El espaci o de n ombre System. Net contiene clases que p ropor ci onan un contenedor de clase p ar a los muchos protocolos que se utilizan a c tu al me n te en las redes. Este espacio de nombre consta de clases p ar a gestionar peticiones de DNS. H T T P y peticiones de FTP. Además de las clases gene rales de acceso a redes, también hay muchas clases de seguridad de redes que tratan los diferentes aspectos de la seguridad, desde accesos a sitios We b hast a accesos de nivel de socket. El espacio de nombre System. Reflection contiene clases que p ro porcionan una vista de tipos, métodos y campos disponibles par a una apli cación de NET. Incluso es posible crear e invocar tipos dinámicamente en el t i e m p o de e j e c u c i ó n u s a n d o las c l a s e s del e s p a c i o de n o m b r e
System.Reflection. El espaci o de nombre S y s t e m .Resources proporciona clases que per miten a los progr amadores crear, al macenar y admi ni strar recursos especí ficos de las referencias culturales que se utilizan en las aplicaciones El espacio de nombre System. Runtime no es muy útil por sí mismo. Sin embargo, dispone de docenas de clases que p ropor ci onan una enorme
fu nc io na l id a d. Po r ej empl o. System. R u n t i m e . I n t e r o p S e r vices permite el ac ceso a objetos C O M y a los API nativos desde NET. •
El espacio de nombre System. Security contiene clases que pe r mi ten el acceso a la est ructur a subyacente de seguridad de N E T Framework. El espacio de nombre de seguridad es el punto de par ti da p ar a otros e s p a cios de nombre más avanzados de muchos servicios de cifrado. Estos ser vicios incluyen el cifrado y descifrado de datos, generación de hash y generación de números aleatorios. El espacio de nombre System. Text contiene clases que permiten t r a baj ar con codificaciones de caracteres ASCII. Unicode. UTF-7 y UTF-8.
•
El espacio de nombre S y s t e m .Threading contiene clases que p e r m i ten i mplcmentar varios subprocesos del sistema operativo en las apl icaci o nes N ET , creando así una auténtica aplicación multiproceso. El espacio de nombre S y s t e m .Timers contiene clases que permiten des encade nar un evento en un intervalo de tiempo det er minado o en unos plazos más complejos. Estos t empori zadores se basan en el servidor. Un t empori za dor b as a do en un servidor tiene la capaci dad de moverse entre los s ubprocesos p a r a iniciar el evento, lo que propor ci ona una flexibilidad m ay or que el t emp ori za dor típico de Wi ndows. El espacio de n ombre System. Web contiene clases que i mplementan el protocolo de transmisión del hipertexto ( H T T P ) que utilizan los clientes We b p ar a acceder a páginas de Internet. Este espacio de nombre no está disponible por defecto; hay que crear una referencia a él desde el menú Proyecto.
•
El espacio de nombre S y s t e m .Windows .Forms contiene clases par a crear aplicaciones completas par a Windows. Las clases del espacio de nombre S y s t e m .Windows .Forms propor ci onan un entorno de clase NE T con los controles típicos de Windows como cuadros de diálogo, menús y botones. Este espacio de nombre no está disponible por defecto; hay que crear una referencia a él desde el menú Proyecto. El espaci o de nombre S y s t e m .Xml contiene clases que pueden procesar datos XML. Este espacio de nombre i n cl i n e compatibilidad con espacios de nombre X M L 1.0. XML. esquemas XML. XPath. X SL y XSLT. DOM Level 2. y S OAP 1.1.
Aunque no es una lista completa, debería darle una idea de la inmensa cantidad de espacios de nombre ya i mplcmentados por N E T F ramework Consulte la d o cumentación del S D K de N E T F ramework p ar a conseguir una lista completa de espacios de nombre y clases.
297
Resumen Las clases y est ructuras que desarrolle se pueden enc ap su lar en los l lama dos espacios de nombre. Los espacios de nombre ayudan a diferenciar unas clases y est ructuras de otras que t engan el mismo nombre. Una clase o es t ru ct ur a compl et a incluye el nombre del espacio de nombre que alberga a la clase o estructura. Cuand o se hace referencia a una clase o es tr uc tu ra en un espacio de nombre, hay que cualificar el nombre ant eponi en do el n ombre del espacio de nombre y un punto. Se puede usar la pal abra clave u s i n g p ar a facilitar el trabajo con los nombres de espacios de nombre en el código de C#. La p al ab r a clave u s i n g puede usarse par a propor ci onar un alias par a una clase particular en un e spa cio de nombre concreto. Tamb ién puede us ar se c omo una directiva de espacio de nombre, que avisa al compi lador de C# de que nuestro código va a hacer referencia a clases en un espacio de nombre específico y de que el código no ant epondrá el identificador de espacio de nombre a las clases de ese espacio de nombre. Puede crear sus propios espacios de nombre y puede u sar el código incluido en espacios de nombre desarrollados por otras personas mediante las técnicas reseñadas en este capítulo. . NET F ramework incluye una gran cantidad de espacios de nombres llenos de clases, lo que facilita la labor de codificar cualquier cosa, desde aplicaciones Wi ndo ws has ta procesadores de X M L y p rog ra ma s de seguridad. Los espacios de nombre de N E T F ramework t a m bién proporci onan clases que pueden usarse p a r a cr ear código C# mediante técnicas avanzadas, como el s oftware de multi proceso y la reflexión.
298
13 Interfaces
Una interfaz de C# es un conjunto de firmas de métodos, propiedades, exen tos o indizadores agr up ad os con un nombre común. Las interfaces funcionan como conjuntos de funcionalidades definidas que pueden i mplement arse en una clase o est ructura de C#. Las clases o est ructuras que implementa una interfaz propor ci onan implementaciones p ar a todos los métodos definidos en la interfaz. Supongamos , por ejemplo, que queremos que las clases de nuestro provecto puedan g uar dar los valores de sus campos en una base de datos y recuperarlos más tarde. Al i mplementar este requisito, podrí amos decidir que todas las clases deban i mplement ar un método ll amado Load ( ) y otro llamado Save ( ) . T a m bién p odrí amos definir los métodos en una interfaz llamada I Per s is tToDis k (los nombres de una interfaz c omienzan normal ment e con la letra I. aunque no es obligatorio en C#) y exigir que nuestras clases implementen esta interfaz. El código de C# puede consul tar un objeto p ar a determinar si admite una interfaz. Consult ar a un objeto acerca de una interfaz es básicamente hacer la p regunta "¿Admites esta i nt erfa z7" El objeto responde "Sí" o "No". Si el objeto responde "Sí", se puede llamar al método en la interfaz. Los métodos llamados en una interfaz siempre tienen la mi sma lista de par ámet ros y valores devueltos, aunque sus implementaciones pueden ser diferentes. Imagi ne u n a i nt erfaz como un c ont ra to, una p r o m e s a de que una clase implement ará un conjunto específico de funcionalidades. Si un objeto responde
301
"Sí. vo admito la mtcrfaz por la que está preguntando", está asegurando que p r o porciona una implementación par a cada uno de los métodos definidos en la interfaz. Las interfaces no propor ci onan sus propi as implementaciones de métodos. Sólo p ropor ci onan identificadorcs de métodos, listas de p ar ámet ro s y códigos devuel tos. Las clases que implementan la mt crfaz son las responsables de proporci onar una implementación. Dos clases que implement an la mi sma interfaz pueden i mplementar los métodos de la i nterfaz de modos muy distintos. Esto es correcto, siempre que las clases sigan definiendo las firmas de los métodos en la definición de la interfaz. Vamos a usar la interfaz IPersistToDisk como ejemplo. Puede tener objetos en su aplicación que necesiten abrir su estado desde un disco y g ua r da r su e s t a d o de n u e v o en un d i s c o . P o d r í a d e c i d i r i m p l e m e n t a r la i n t e r f a z IPersistToDisk en estos objetos. Para cada uno de los objetos que ímplcmenten IPersistToDisk. necesitará escribir código p ar a los métodos Load ( ) y Save ( ) de la interfaz. Algunos de los objetos pueden tener necesidades de a l m a cenami ent o básicas, de modo que esos objetos pueden implement ar los métodos Save ( ) y Load ( ) mediante un simple código de E/S de disco. Otros objetos pueden ser más complicados y necesitan compatibilidad con la E/S transaccional. en la que toda la operación de persistencia debe tener éxito o f r aca sa r como un todo. Para esos objetos, quizás prefiera implementar los métodos Load ( ) y Save ( ) usando código transaccional. más robusto. La clave está en que el códi go que usan estos objetos no necesita saber si el objeto us a código simple o transacci onal en su implementación. Sólo p re gu nt a a cada objeto "¿Admites el m ét o do IPersistToDisk ?" Para los objetos que responden "sí", el código que usa los objetos puede llamar a Load ( ) o Save ( ) sin necesidad de saber cómo están implementados realmente esos métodos. Conceptualmente, las interfaces son muy parecidas a clases base abstractas: las dos proporcionan una lista de métodos que deben ser implcmentadas por otros fragmentos de código. Sin embargo, hay una diferencia importante: las interfaces pueden ser implcmentadas sin importar la posición de la clase de implementación en una jerarquía de clases. Si se usan clases base abstractas, todas las clases que quieran i mplementar la funcionalidad deben derivarse directa o indirectamente de la clase base abstracta. Esto no ocurre con las interfaces: las interfaces pueden implementarse en cualquier clase, sin importar su clase base. Las clases no nece sitan derivarse de una clase base específica antes de poder implementar una interfaz. El concept o de interfaz como modelo de diseño de soft ware no es nueva; sin embargo, el modelo de objetos de componentes ( C O M ) de Mi crosoft popul ari zó el concepto. C O M t rajo la idea de las i nterfaces (conjunt os especí fi cos de funcionalidad implementados por un objeto) a la vang ua rd ia del desarrollo de soft ware b as ado en Wi ndows. C # llevó más allá el concepto, promovi endo el concepto como una ca rac te rí st ic a de lenguaje en el nivel de código. Aunque las primeras versiones de C ++ o Visual Basic ya tenían interfaces C O M integradas, estos lenguajes no eran compatibles con el concepto como rasgo de lenguaje. La
302
palabra clave de C # interface hace que el concepto de p rog ra ma ci ón de interfaz sea compat ibl e con el código fuente y esté disponible p a r a el código, aunque el código no use C OM . Este capítulo le enseña a t raba jar con interfaces usando C#. Aprenderá a defi nir una interfaz usando la pal abr a clave interface. Tambi én aprenderá a definir e implcmentar métodos, propiedades, indizadores y eventos en una interfaz y a acceder a una interfaz i mplementada por un objeto.
Cómo definir una interfaz El primer paso al t ra ba j ar con interfaces en C# consiste en definir los métodos que componen la interfaz. Las interfaces se definen en C# con la siguiente sin taxis: •
La pal abra clave interface
•
Un identificador de interfaz Interfaces base opcionales Una llave de apert ura Una o más declaraciones de mi embro de interfaz Una llave de cierre
Las interfaces pueden definir métodos, propiedades, indizadores y eventos. Estos constructores de lenguaje trabaj an sobre las interfaces del mismo modo que traba jarí an con las clases de C#. Los métodos de interfaz definen bloques de codigo con nombre; las propi edades definen variables que pueden ser validadas mediante código descri ptor de acceso y los ev entos definen acciones que pueden ocurrir en el código.
Cómo definir métodos de interfaz La agregación de un método a una interfaz significa que cualqui er objeto que desee i mplcmentar la interfaz debe p rop o rc io na r una implementación del método de interfaz. Esto gar ant iza al código que los objetos que i mplementan la interfaz. i ncluyendo una implementación del método, pueden ser llamados por el código. Para definir un método en una interfaz. realice una declaración de método que proporcione el tipo devuelto por el método, el identificador y la lista de parámetros. La definición en C# de la interfaz I Per s is tToDis k que aparece al principio del capítulo sería: interface IPersistToDisk
{ bool Load(string FileName);
303
bool Save ( s t n n g FileName);
} La intcrfaz define dos métodos, pero no prop or ci on a ni nguna i mplementación p a r a los métodos. Las declaraciones de método t erminan con un punt o y coma. Las clases de C# que implementa la interfaz IPersistToDisk prometen que pr opo rci onar án una implementación de los métodos Load () y Save ( ) tal y como se definen en la interfaz.
definir propiedades de interfaz piedades definen variables que pueden ser definidas por una interfaz. Al igual que las propiedades de clase, las propiedades de interfaz están asociadas con funciones de descriptores de acceso que definen el código que debe ejecutarse cuando se lea o escriba el valor de la propiedad. Para definir una propiedad en una interfaz hay que indicar el tipo de propiedad, el identificador y las pal abr as clave descriptoras de acceso seguidas de puntos y comas. Las pal abr as clave descriptoras de acceso aparecen entre llaves. La clase o estructura que implementa la interfaz es la responsable de proporcionar la implementación de los descriptores de acceso de la propiedad. Para definir una propiedad de lectura/escritura en una interfaz hay que usar las pal abr as clave descriptoras de acceso get y set: interface
Interfacel
{ int RecordCount
{ get;
set;
}
} Para definir una propiedad de sólo lectura en una interfaz. bast a con incluir la p a l a br a clave descriptora de acceso get: interface
Interfacel
{ int RecordCount
{ get;
}
} P ar a definir una propiedad de sólo escritura en una interfaz. b as ta con incluir la p al ab ra clave descriptora de acceso set: interface
Interfacel
{ int RecordCount
{ set;
}
}
Cómo definir indizadores de interfaz Los indizadores son propi edades especiales que permiten al código acceder a datos como si estuvieran al macenados en una matriz. Los indizadores pueden definirse en una interfaz del mismo modo que se definen en una clase. Para definir
304
un indizador en una interfaz hay que indicar el tipo del indizador. la pal abr a clave this. la lista de par ámet ros del indizador entre corchetes y las pal ab ras clave descriptoras de acceso seguidas de puntos y comas. Las palabras clave descriptoras de acceso aparecen entre llaves. La clase o est ructur a que implementa la interfaz es la responsable de proporci onar la implementación de los descriptores de acceso del indizador. P a ra definir un indizador de l ectura/escritura en una interfaz. use las pal abras clave descriptoras de acceso get y s e t : m t e r f a c e Interfacel
{ in t this
[mt
Index]
{ get;
set;
}
} Para definir un indizador de sólo lectura en una interfaz. basta con incluir la p al ab r a clave descri ptora de acceso get: mterface
Interfacel
{ int this
[mt
Index]
{ get;
}
} Para definir un indizador de de sólo escritura en una interfaz. bas ta con incluir la pal abr a clave descriptora de acceso s e t : mterface
Interfacel
{ int this
[mt
Index]
{ set;
}
}
Cómo definir eventos de interfaz Los eventos pueden definirse en una interfaz del mismo modo que se definen en una clase. Quizás quiera añadir un evento a la interfaz IPersistToDisk m e n ci onada al principio del capítulo p ar a que. por ejemplo, se desencadene cuando las implement aci ones de Save ( ) y Load ( ) empiecen a t rab aj ar realmente con el disco par a abrir o g ua r d a r los datos del objeto. Para definir un evento en una interfaz hay que us ar la pal abr a clave event. el tipo del evento y un identificador de evento: mterface
Interfacel
{ event
EventHandler
ClickEvent;
}
Cómo derivar a partir de interfaces base Las interfaces pueden derivarse de interfaces base, igual que las clases deri van de clases base. Las interfaces base se escriben tras los dos puntos que
305
siguen al nombre de la interfaz derivada. A diferencia de las clases base, las interfaces pueden derivarse de más de una interfaz base. Los diferentes nombres de interfaces base se separan por comas, como se puede apreciar en el siguiente ejemplo: interface Interface!
{ void
Method 1 () ;
} interface
Interiace2
{ void
M e t h o d 2 () ;
} interface
Interiace3
: Interfacel,
Interface2
Es útil derivar de una interfaz base cuando una interfaz contiene un conjunto de firmas de método, propiedad y evento que deben ser añadidas a una interfaz ya progr amada. Por ejemplo. N E T F ramework define varias mterfaces que p u e den i mplementarse y usarse en C#. Como las interfaces ya son una parte de N E T Framework, la lista de métodos, propiedades, indizadores y eventos que admiten se ha consolidado y no puede cambiar. Si quiere u sar una interfaz defini da y necesita añadir más firmas a la interfaz p ar a uso propio, debería considerar la posibilidad de derivar desde la interfaz ya definida y añadir sus nuevas firmas en la interfaz derivada. Las cl ases que i m pl e m e n t a n u n a i n t e rf a z d e r i v a d a d eben p r o p o r c i o n a r implementacioncs para todos los métodos definidos por las interfaces base. C u a n do una clase implementa una interfaz. debe pr op or ci on ar el código par a cada uno de los métodos definidos en la interfaz. Si una clase i mplementa Inter face 3. por u sar el ejemplo anterior, la clase debe pr op or ci on ar las implementacioncs de método p ar a Methodl () y Method2 (). No se puede derivar una interfaz de ella misma. Obser ve la siguiente definición de una interfaz: mterface
Interfacel
: Interfacel
{ void M e t h o d 1();
} Este error hace que el c ompi lador de C# p rod uz ca el siguiente mensaje de error: error
CS0529:
en
jerarquía
la
La interfaz de
la
heredada
inter faz
'Interfacel'
crea un ciclo
'Interfacel'
Este mensaje de error indica que el código está intentando deriv ar una interfaz de sí misma, lo que no se permite en C#. Las interfaces sólo pueden derivarse de otras interfaces.
306
Cómo usar la palabra clave new para reutilizar identificadores Se puede usar la pal abr a clave new p a r a redefinir un identificador usado en una clase base. S uponga que está t raba jando con una interfaz que define una propi edad l lamada ID: interface
Baselnterface
{ int ID
{ get;
}
} Ahora s uponga que quiere derivar de esa interfaz. pero le g us t ar ía usar el identificador ID como nombre de un método: interface
Derivedlnterface
: Baselnterface
{ int
ID() ;
} Esta const rucci ón hace que el compi lador de C # emita un aviso sobre la reutilización del identificador ID: warning CS0108: La palabra clave new es necesaria en 'Derivedlnterface .I D () ' porgue oculta el miembro heredado 'Baselnterface.ID'
El compi lador está avi sando de que el identificador ID se está usando dos veces: una vez en la interfaz base y otra en la interfaz derivada como un nombre de método. La reutilización de este nombre puede confundir fácilmente a los usuarios de la interfaz. El aviso del compi lador significa que el uso de la pa la br a clave ID en la interfaz derivada tiene prioridad sobre el uso de la p al ab ra clave I D en la interfaz base. Si un fr a gm en t o de códi go recibe una i mpl eme nt aci ón de la interfaz Derivedlnterface. el código será i ncapaz de llamar al método ID ( ) pero no puede acceder a la p ropiedad I D de la interfaz base. La r c ut il i zac ió n del i d en t if ic ad o r ID en la i nt er fa z d e r i v a d a o c u l t a el identificador ID en la clase base por lo que los clientes no pueden acceder a la propiedad de la interfaz base. Para resolver este probl ema se puede us ar la p al ab ra clave new al reutilizar el identificador. El uso de la p al abr a clave new. mos trad o en el siguiente ejemplo, indica al compi lador de C# que se quiere dar un nuevo uso al símbolo reutilizado: interface
Derivedlnterface
: Baselnterface
{ new int
ID () ;
}
307
Cómo implementar interfaces en clases y estructuras Tras definir una interfaz. se puede i mplementar esa interfaz en clases v es tru c turas. Esto indica a los usuarios de la clase o est ructur a que ésta proporciona i mplementaciones a los const ructores definidos en la interfaz. Por eiemplo. si i mplementamos en una clase la interfaz IPersistToDisk que aparecía en la introducción, i nformar emos a los usuar ios de la clase que ésta propor ci ona implementaciones de los métodos Save ( ) y Load ( ) y que se puede llamar a los métodos. Las interfaces que se están i mplementando se identifican de la mi sma forma que las clases base, con una lista de nombres tras dos puntos que siguen al identificador de clase o de estructura: mterface
Interfacel
{ v o i el M e t h o d 1 () ;
} class MyClas.s
: Interfacel
{ void
M e t h o d 1()
{ } } Este código define una interfaz llamada Interfacel. El método Interface 1 declara un método: un método llamado Method 1 (). El codigo también declara una clase l l a m a d a MyClass. que implementa la interfaz Interfacel. La clase MyClass incluye una i mplementación par a el método Method 1 ( ) definido por la interfaz Interfacel. Aunque la clase sólo puede derivarse de una clase base, puede implementar tantas interfaces como se deseen. Solamente hay que escribir las interfaces tras el identificador de clase y s epa rar cada interfaz con dos puntos: class MyClass
: Interfacel,
Interface2,
Interface3
La implementación de interfaz múltiple se usa en todo N E T Framework. Por ejemplo, la clase System. String implementa cuatro interfaces definidas por N E T Framework: IComparabl e. que c o mp ar a los valores de dos objetos del mi smo tipo. •
ICloneable. que crea un nuevo objeto que tiene el mi smo estado que otro objeto. IConvertible. que convierte el valor de un tipo a un valor de otro tipo.
•
308
IEnumerable, que permite que el código itere a través de una colección
Como la clase S ystem. S t r i n g i mplementa estas cua tr o interfaces. la funcionalidad que cada una de las interfaces propor ci ona es compatible con la clase System. String. Esto significa que las cadenas pueden ser comp ar ad as con otras cadenas, pueden ser clonadas, pueden convertirse en otros tipos y se puede iterar a través de sus caracteres como si fuera una colección. El concepto de i mplementación de interfaz múltiple también está disponible p ar a cualquier p ro gr a ma do r de C#. C# también permite derivar una clase de una clase base e implement ar interfaces al mismo tiempo. class MyDerivedClass : CM yB aseClass, Interfacel,
Interface2
Las clases deben implement ar cualqui er declaración de evento, método, p r o piedad o indizador encontrado en una interfaz que implcmenten. En caso c o nt r a rio. el compi lador de C# emitirá un error. El siguiente codigo no funciona porque la clase M y C l a s s i m p l e me n t a I n t e r f a c e l per o no p r o p o r c i o n a una imp le me nt aci ón del m é t o d o M e t ho d 1 ( ) definido en Interfacel: interface
Interfacel
{ void
M e t h o d 1();
} class MyClass
: Interfacel
{ public
static
void M a i n ()
{ } } El compilador de C # emite el siguiente mensaje de error al compi lar el codigo: error CS0535: 'MyClass' no implementa el miembro de interfaz 'Interfacel.Met ho d1()'
La clase debe propor ci onar una implementación p ar a la interfaz Me thod 1 ( ) definida por Interfacel. dado que la clase implementa Interfacel. El siguiente ejemplo corrige el error: mterface
Interfacel
{ void M e t h o d 1();
} class MyClass
: Interfacel
{ public
static void M a i n ()
{ } public
void M e t h o d l ()
{ } }
309
Cómo implementar métodos de interfaz con el mismo nombre Debido a que es posible que el nombre de un método a p a re z ca en más de una interfaz v como es posible que un a clase de C# implemente más de una interfaz. puede o c u r ri r que se le pi da a u na clase de C# que p ro po r ci o ne múltiples i mplementaciones de diferentes interfaces que t engan el mi smo nombre. Observe el mét o do D o Wo r k ( ) en el siguiente código: interface
Interfacel
{ v o i d D oW o r k () ;
} interface
Interface2
{ void
D o W o r k () ;
} class MyClass
: Interfacel,
Interface2
{ v o id D o W o r k ()
{ ) } Este código no se puede compilar. El c ompi lador de C# emite el siguiente mensaje que indica el error que produce: error CS0536: 'MyClass' no implementa el miembro de interfaz 'Interfacel.D o W o r k () ' . M y C 1as s .D o W o r k ()' es estático, no publico, o tiene un tipo de valor devuelto incorrecto, error CS0536: 'MyClass' no implementa el miembro de interfaz 'Interface2.DoWor k () ' . M y C 1as s .D o W o r k ()' es estático, no publico, o tiene un tipo de valor devuelto incorrecto.
En los mensajes de error se muest ra la sintaxis i nterfaz/nombre p ar a recordar la sintaxis correcta de las implementaciones de clase. El p ro bl em a es que la clase M y C l a s s necesita p r o p o r c i o n a r código de implementación p ar a el método D o Wo r k ( ) definido por I n t e r f a c e l y del m ét od o Do Wo r k ( ) definido por I n t e r f a c e 2 y las dos interfaccs reutilizan el nombre de método D o Wo r k ( ) . Una clase de C# no puede incluir dos métodos con el mi smo nombre, de modo que ¿cómo se pueden definir los dos métodos de interfaz7 La solución es ant eponer el nombre de la interfaz a la implementación del método v escribir un punto que separe el nombre de la interfaz del nombre de la implementación. como se mu es tr a a continuación:
310
class MyClass : Interfacel,
Interface2
{ void
Inter fa ce l.D o W o r k ()
{ } void
Interface2.D o W o r k ()
{ } } Esta clase se compi la cor rectament e ya que contiene dos implementaciones
DoWork (). una por cada interfaz definida. C omo los nombres de método están calificados con los nombres de interfaz. el compilador de C# puede distinguir uno de otro y puede verificar que las dos interfaces han sido implement adas en la clase.
Cóm o acceder a m iem bros de interfaz T r a b a j a r con clases que implementan interfaces es sencillo en C#. N o r m a l mente se realizan estas operaciones cuando se t rab aj a con objetos cuy as clases implementan interfaces: Consult ar un objeto p ar a verificar que es compatible con una interfaz es pecifica. Acceder a una interfaz en un objeto. Acceder a un mi embro de la clase de un objeto definido inicialmente en una interfaz. Las próxi mas secciones estudian estas operaciones.
Consultar a un objeto por una interfaz Dado que diseña e implementa su propio código, ya sabe qué clases se usan en su aplicación y qué interfaces admiten. Sin embargo, cuando se escribe código que puede ser empleado por otras aplicaciones N E T y se reciben objetos de otros códigos, nunca se puede estar realmente seguro de qué interfaces admiten esos objetos. Por ejemplo, si está escribiendo un ens amb la do y escribe código que a cept a un tipo de objeto genérico, no puede saber si el objeto admite una interfaz dada. Puede us ar la pal ab ra clave i s par a co mp ro ba r si un objeto admite o no una interfaz. La pal abra clave i s se emplea como una parte de una expresión booleana que se c onst ruye como se indica a continuación:
311
Un identificador de objeto.
La palabra clave i s . Un identificador de interfaz. La expresión devuelve T r u e si el objeto admite la interfaz indicada y F a l s e en caso contrario El listado 13.1 muest ra el funcionamiento de la pa la br a clave is : L istado 13.1. C ómo usar la palabra clave is para trabajar con una interfaz u sing System; public
í n te r f a c e
I P n n t M e s s age
( v o id
P n n t () ;
}; class
Class1
{ p ub l i c
void
P n n t ¡)
{ C ons o l e . W r i t e L m e ("Helio
f r ora C 1 a s s 1 !" ) ;
} } class
: I P r m t M e s sage
Class2
{ p ub l ic
void
P n n t ()
( C o n s o l é . W n t e L m e ("Helio
fr om
C 1 a s s 2 !" ) ;
} } class
MainClass
{ public
stat i c
v oid
M a i n ()
( PrintClassPrintObject
= new
PrintClass () ;
P r i n t O b j e c t . P r i n t M e s s a ge s () ;
1 ( class
Pr i n t C l a s s
{ pu b l i c
void
P r m t M e s s a g e s ()
{ Class 1 Class2
Object'l 0 bj e ct 2
= new = new
Classl () ; C l a s s 2();
PrintMessageFromObject(Objectl);
312
PrintMessageFromObject(Object2) ;
} prívate
void
PrintMessageFromObject (object
obj )
{ if(obj
is
IPrintMessage)
{ IPrintMessage
PrintMessage;
P r m t M e s s a g e = (IPrintMessage) ob] ; PrintMessage.Print () ;
} } } El listado 13.1 define una interfaz llamada IPrintMessage. La interfaz IPrintMessage define un método llamado Print. Al igual que todos los miembros de interfaz de C#. la interfaz IPrintMessage define mi embros pero no los implementa. A continuación el listado implementa dos clases de control l lamadas C l a s s l y C l a s s 2 . La c l a s e C l a s s l implementa un método llamado calied Print (). C om o Classl no hereda de la interfaz IPrintMessage. el método Print ( ) implementado por la clase no tiene ninguna relación con el método Print ( ) definido por la interfaz IPrintMessage. La clase Classl implement a un mét odo l lamado Print () . C o m o Classl hereda de la interfaz IPrintMessage. el compi lador de C # c o n s i d e r a q ue el m é t o d o P r i n t ( ) i m p l e m e n t a d o p o r la c l a s e es una implementación del método Print ( ) definido por la interfaz IPrintMessage. A continuación el listado 1.^.1 define una clase llamada Ma^nClass. que i m p l e m e n t a el m é t o d o M a i n ( ) de la a p l i c a c i ó n y o t r a c l a s e l l a m a d a PrintClass. El método Main ( ) del listado 13.1 crea un objeto de la clase PrintClass y llama a su método público para que haga el trabajo de verdad. El listado 13.1 termina declarando una clase llamada PrintClass. L a c l a s e PrintClass implementa un método publ ico llamado PrintMessages ( ) y un método de ayuda privado llamado PrintMessageFromObj ect (). El método PrintMessages ( ) es el método al que llama el método Main ( ) . Como PrintMessageFromObj ect ( ) esta marc ad o como privado, solo se le puede l lamar desde otros fragment os de codigo del objeto PrintClass y no puede ser llamado desde el código en otras clases. El método PrintMessages ( ) crea un objeto de clase Classl y un objeto a partir de Classl y pasa cada objeto al método privado PrintMessageFromObj ect ( ) . El método p r iv a do PrintMessageFromObj ect ( ) acepta un parámet ro de tipo object como parametro.
NOTA: Es posible usar un parámetro de tipo obj ect gracias a que todos los tipos de variable que el CLR admite derivan en última instancia de
313
System.Object y la palabra clave de C# object es un alias para el tipo System.Object. Cualquier tipo de variable que pueda ser repre
sentada en C# puede ser usada como parámetro para un método que espere un tipo de objeto porque todos los tipos son, en última instancia, objetos de System.O b j ect. En la siguiente línea del listado 13.1. el método PrintMessageFromObject ( ) comienza exa mi na ndo el objeto p ar a c o mp r ob a r si implementa la interfaz IPrintMessage: if(obj
ís
IPrintMessage)
Si el o b j et o i m p l e m e n t a la i nt er fa z. la e x p r e s i ó n b o o l e a n a obj ís IPrintMessage devuelve True y el codigo situado por debajo de la condi ción i f se ejecuta. Si el objeto no implementa la interfaz. la expresión booleana obj is IPrintMessage devuelve False y el código situado por debajo de la condición i f no se ejecuta. Si el objeto admite la interfaz. se puede acceder a la i mplemcntación del objeto de la interfaz. Se puede acceder a la i mplemcntación de la interfaz de un objeto declarando una variable del tipo de la interfaz y luego convirticndo expl íci tamen te el objeto al tipo de la interfaz. como se indica a continuación: IPrintMessage P r m t M e s s a g e ; P n n t M e s s a g e = (IPrintMessage) ob] ;
T ra s imcializar una variable del tipo de la interfaz. se puede acceder a los mi embros de la interfaz usando la habitual notación con punto: P n n t M e s s a g e . Print () ;
En el listado 13-2. se pa sa Objectl al método PrintMessageFromOb j ect () y no se escribe nada en la consola porque el objeto Obj ect 1 es de la c l a s e C l a s s l y Classi no implementa la interfaz IPrintMessage. Cuando se pasa Objectl al método PrintMessageFromOb j ect (). se escribe en la consol a el siguiente texto: Helio
from Class2!
Este mensaje aparece porque el objeto Ob ject2 es de clase Class2 y Class2 i mplementa la interfaz IPrintMessage. Si se llama a la i mplemcntación del objeto del método Print de la interfaz se escribe el siguiente mensaje en la consola.
Cómo acceder a una interfaz en un objeto Usar el operador i s p ar a t ra ba j ar con una interfaz requiere que el código ac ceda a un objeto dos veces:
314
Una vez p a r a consul tar al objeto y c o m p r ob a r si el objeto implementa una interfaz. •
Una vez p a r a acceder a la i mplementación de la interfaz del objeto us ando el op er ad or de conversión explícita.
Se pueden c om bi nar estos dos accesos mediante el op er ad or as. El operador as realiza dos tarcas en una sola instrucción. El listado 13.2 es una versión modi fi cada del listado 13.1 que usa la instrucción as en lugar de la instrucción is: L istado 13.2. C ó m o u s a r la p a l a b r a c l a v e as p a r a t r a b a j a r c o n u n a i n t e r f a z us m g
Sys t em;
public
interface
IPrintMessage
{ void
P r i n t ();
}; class
Classl
{ public
void
Print()
{ Console.WriteLine("Helio
class
Class 2
from
Classl!");
from
C l a s s 2 !");
: IPrintMessage
{ public
v o id
P r i n t ()
{ C o n s o l e . W r i t e L i n e (" Hello
} } class
MainClass
{ public
sta tic
v o i d M a i n ()
{ PrintClassPrintObject
= new
P r i n t C l a s s ();
Pr i n t O b j e c t . P r i n t M e s s a g e s () ;
} } class
Pri ntC l a s s
{ pu b l i c
v oi d
P r i n t M e s s a g e s ()
{ Classl Class2
Objectl O bje ct2
= n e w C l a s s l (); = n e w Class2();
315
PrintMessa geFromObject (Objectl) ; P r m t M e s s a g e FromOb] e ct (Ob j e c 1 2 ) ;
} prívate
void
Pr in tM es sageFromObject(object
obj)
{ IPrmtMessage
PrmtMessage;
P r m t M e s s a g e = obj as TP rm t M e s s a g e ; jf (P r m t M e s s a g e != nuil) Pr intMc-ssaq e .P r in t í) ;
t i oper ado r a s se usa como parte de una expresión que se construye como se indica a continuación: Un identificado!' de objeto. La palabra clave a s . Un identificado!'de mterfaz. Si el ob| eto designado en la expresión implementa la interfaz designada en la expresión, la implementacion del objeto de la mt erfaz se devuelve como el resul tado de la expresión. Si el objeto designado en la expresión no implementa la mterfaz designada en la expresión, se asigna al resultado de la expresión un valor vacío representado por la pal abra clave de C# nuil. La nueva implementación del método pri vado Pr intMes sage FromOb j ect () usa el operador a s . De clara una variable local del tipo de la interfaz I Pr intMes sage v usa el op er a dor as para acceder a la implementación del objeto del método. Una vez que se ha compl et ado la operacion as. se c omprue ba la variable de implementación de la interfaz par a d escubri r si tiene el valor nuil. Si la variable no es nuil, se sabe que el objeto propor ci onado implementa la interfaz v se puede llamar al método de la interfaz. El listado 13 .2 es funcionalmente equivalente al listado 13.1 v escribe el si guiente texto en la consola: Helio
f rom C ]a s s 2 !
Declaraciones de interfaz y palabras clave de ámbito Al d e s i g n a r una int erfaz. se puede m a r c a r la i nt erfaz como public. protected. internal o private. Si decide usar una de estas palabras claves para pr opor ci onar un nivel de ámbito par a la interfaz. debe colocarse i nmediatamente antes de la pal ab ra clave interface.
316
Las interfaces marc adas como p ub li c son visibles par a cualquier fr ag mento de código que tenga acceso al código en el que la definición de la interfaz pueda resolverse en tiempo de ejecución. Si se desarrolla un e n s ambl ado y se implementa una interfaz pública en ese ensamblado, c u a l quier aplicación de . NET que acceda al ensambl ado podra t raba jar con la interfaz. Las interfaces marc adas c o m o p r i vate solo son visibles para la clase en la que se definen. Solo las interfaces cuyas definiciones están anidadas en clases pueden m ar car se como private. Las interfaces mareadas como protector] solo son visibles para las clases en la que son definidas o desde clases derivadas de la clase. Solo las interfaces cuyas definiciones están ani dadas en clases pueden marcarse com o p r o t o c t e d . Las interfaces marcadas como i n t e r n a l son visibles para cualquier eodigo en el mismo archivo binario, pero no son visibles para el codigo que se encuentre en otros archiv os binarios Si se define una interfaz en ( ’# > se compila la clase for mando un ensamblado, cualqui er fr agmento de codigo del ens ambl ado puede acceder a las interfaces internas. No obstante, si otro fragmento de codigo usa ese ensamblado, no tendrá acceso a la interfaz. C# permite especificar una interfaz sin especificar ninguna pal abr a clave de ámbito. Si se declara una interfaz sin especificar ni nguna pal ab ra clav e de á m b i to. por defecto se le concede a la interfaz accesibilidad publica.
Cómo implementar interfaces definidas por .NET Framework N E T F ramework define v a n a s interfaces que se pueden implementar en otras clases. En este ca pí tul o ya se ha menci ona do que N E T F r am ew o rk define interfaces, como las interfaces ICloneable. TEnumerable. ICompareab]e e IConvert ible que implementa la clase S y s t e m .String. Implementar interfaces definidas por N E T Framework puede a yu da r a las clases a integrarse en N E T Framework v en el entorno común de ejecución (el CLR. para a b r e viar). Observe este ejemplo.
Cómo implementar foreach mediante lEnumerable lEnumerator El listado 9.3 del capítulo 9. implementa una clase llamada Rainbow, que incluye un indizador que permite utilizar los contenidos de la clase (cadenas que
317
n om br an los colores del arco iris) como elementos de u na matriz, como se m u e s t ra en el siguiente ejemplo: Rainbow MyRainbow = new Ra in bo w( ); for (Color Index
= 0;
Colorlndex
< M y R a i n b o w .C o u n t ; ColorIndex + + )
{ string
ColorName;
ColorName = M yR ai nb ow [Colorlndex]; S y s t e m . C on so lé .W ri te Li ne (C ol or Na m e);
Se puede reducir aún más este código usando la p a la br a clave f oreach con la clase, como se mues tra en el siguiente fragment o de código: Rambow
MyRainbow = new R a m b o w ( ) ;
foreach ( s t n n g Color in MyRainbow) C o n s o l é . W r i t e L i n e (C o l o r N a m e );
Por defecto, las clases no admiten la pal ab ra clave foreach y usarla para a cceder a los elementos de la clase hace que el c ompi lador de C# genere el si guiente mensaje de error: error CS1579: La instrucción foreach no funciona en variables del tipo 'Rainbow' porgue 'GetEnumerator' no contiene una definición para 'miembro' o es inaccesible
Sin embargo, se puede u s ar foreach en las clases si la clase implementa una interfaz de N E T Framework llamada IEnumerable. La interfaz IEnumerable contiene métodos que N E T F ramework us a p ar a extraer elementos de los objetos. Si la clase contiene u na colección de elementos y se quiere que otras partes del código usen la p a la br a clave foreach pa r a iterar c ada uno de los elementos de la colección, hay que implement ar la interfaz IEnumerable en la clase. La interfaz IEnumerable contiene una sola definición de método: IEnumerator
GetEnumerator () ;
El mét odo GetEnumerator ( ) debe implement arse en la clase y debe de v ol ve r un o bj et o que i m p l e m e n t a o t r a i n t e rf a z N E T F r a m e w o r k l l am ad a IEnumerator. La interfaz IEnumerator es la responsabl e de implementar el código que devuelven los elementos de clase individuales. La interfaz IEnumerator define u na p ropi eda d y dos métodos como sigue:
318
•
object Current
{get;}
•
bool
M o v e N e x t () ;
void
R e s e t ();
La propiedad Current devuelve una referencia al elemento actual de la colección. El método MoveNext ( ) se mueve al siguiente elemento de la col ec ción y devuelve True si hay otro elemento a continuación o False si se ha llegado al final de la colección y no hay otro elemento a continuación. El método Reset ( ) devuelve el integrador al principio de la coleccion. C uando se accede a los datos de una clase con el const ructor foreach. NET Framework accede a las interfaces de las clases IEnumerable e IEnumerator con código c omo el del siguiente pseudo-codigo: IEnumerable IEnumerableImplementation; IEnume rat or IEnume rat orImplement at ion; IEnumerablelmplementation = YourClass if (IEnumerabie Imp1ementat i on !- null)
as
¡Enumerable;
{ IEnumerator Implementation IEnume r a b 1e Im p 1ement at ion.Get Enume r at o r () ; I f (IEnumeratorImplementat ion != null)
{ w h i l e (IEnumeratorlmplementation.G e t N e x t () == true) CurrentValue = IE nu meratorImplementation.C u r r e n t ;
} } Las interfaces ¡Enumerable e ¡Enumerator están definidas en un e s p a cio de nombre de N E T F ramework llamado System. Collections y hay que hacer referencia a ese espacio de nombre cuando se trabaja con estas interfaces Se puede hacer referencia a los nombres de espacio explícitamente: class MyClass : S ystem.C o l l e c t i o n s .IEnume rabie, S ystem.C o l l e c t i o n s .IEnume r at o r
Si se quiere, se puede us ar en su lugar la pal abr a clave using para hacer referencia a los espacios de nombre. using
System. Collections ;
class MyClass : ¡Enumerable, ¡Enumerator
El listado 13.3 remodela el listado 9.3 y usa la clase Rainbow para implementar las interfaces ¡Enumerable e ¡Enumerator; también usa el constructor foreach del método Main ( ) par a recorrer los elementos de la clase. Listado 13.3. Cómo admitir foreach mediante ¡Enumerable lEnum erato r using System; using System. Collections ;
319
ass Rainbow : IEnumerable, private public
short
IEnumerator
Iterator Index
IEnumerator
= -1;
GetEnumerator ()
{ return
this;
} public
object
Current
{ get
{ switch(Iteratorlndex)
{ case 0 : return
"R e d ";
return
"Orange";
return case 3 : return
"Yellow"; "Green";
4 :
case
return
"Blu e" ;
ca s e 5: return
case
return default : return
public
bool
" Indigo";
r: "Violet"; ERROR
* * *" ;
M o v e N e x t Í)
{ 11 e r a t o r I n d e + + ; if (Itera to r ln d ex -r eturn false; r e turn t r u e ;
public
void
7)
Reset)>
{ Iteratorlndex
= -1;
} public
static
void M a i n ()
( Rainbow MyRainbow = new R a i n b o w () foreach(string
ColorName
in MyRainbow)
Consolé . W n t e L m e (ColorÑame )
Si se ejecuta el código del listado 13.3 se escribe el siguiente texto en la consola: Red Orange Ye 11ow
Creen Blue Indigo Vi o 1 e t
La c las e R a i n b o w i m p l e m e n t a las i n t c r f a c e s ¡ E n u m e r a b l e e IEnumerator. La clase mantiene un campo p r n a d o llamado IteratorIndex que se usa p ar a seguir el rastro del siguiente elemento que debe devolverse en el bucle foreach. Se inicial iza a -1: veremos por que cuando exami nemos la implement aci ón deMoveNext ( ) en las siguientes paginas. La implementación de clase de ¡Enumerable .GetEnumerator () d e vuelve una referencia al objeto que se llama con la siguiente instrucción: return
this;
Recuerde que el método debe devolver una referencia a una clase que implementc el mterfaz IEnumerator. Como el objeto usado para llamar a ¡Enumerable. Get Enumerator ( ) tambié'n implementa la clase IEnumerator. se puede devolver el objeto al que se esta llamando. Se puede usar la p al ab ra clave this como v alor dev uelto. En este contexto, la pal ab ra clave this hace referencia al objeto cuyo codigo se este e jecutando en ese momento. Como el compi lador de C# puede det er minar en tiempo real que el objeto que se ejecuta en ese moment o implementa ¡Enumerable, el codigo no necesita c o n v e r t i r e x p l í c i t a m e n t e la p a l a b r a cl ave this a u na v a r i a b l e de tipo IEnumerator. El codigo resultante podría ser como el siguiente: return
this
as
IEnumerator;
Sin embargo, esto es redundante porque el compi lador de C# ya puede c o m probar que el objeto this (el objeto que se esta ej ecutando en ese momento) implementa la interfaz IEnumerator. Si se usa este codigo par a devolver una referencia IEnumerator. recibirá el siguiente aviso del compi lador de C#: warning CS0183: La expresión dada es siempre del tipo proporcionado ('System.Collections.I En umerator')
El resto de la c las e R a i n b o w i m p l e m e n t a m i e m b r o s de la i n t e r f a z ¡Enumerator. El primer mi embro propor ci ona la implementación par a la p r o piedad ¡Enumerator .Current . Exami na el valor de la propiedad privada de
321
la clase Iteratorlndex y devuelve una cadena que representa el color del a r c o iris en el í n d i c e al q u e h a c e r e f e r e n c i a el v a l o r de la p r o p i e d a d Iteratorlndex. La propiedad Current devuelve u na var ia bl e de tipo object. pero como las cadenas son objetos igual que todos los otros tipos de datos, el C L R acepta los valores devueltos basados en cadenas. La i mplementación del mét odo IEnumerator .MoveNext () i ncrementa el valor de la propiedad pri vada Iteratorlndex. C omo el arco iris tiene siete colores. la implement aci ón MoveNext ( ) da por sentado que los valores válidos de Iteratorlndex van de 0 a 6. Si el valor llega a 7. la implementación de MoveNext ( ) as ume que el iterador ha llegado a su límite y devolverá False. En c a so con tra ri o, la i mp le me nt ac ió n devue lve True. La i ns trucci ón que i n c r e m e n t a el v a lo r de I t e r a t o r l n d e x e xi ge que el v a l o r ini ci al de Iteratorlndex sea - I . C uando se llama a MoveNext ( ) por vez primera, la i nstrucción de incremento aument a el valor de Iteratorlndex de -1 a 0. dándole a Iteratorlndex un valor válido en la pri mera iteración del bucle. La i mplement aci ón del mét odo IEnumerator .Reset ( ) s implemente res tablece el valor de Iteratorlndex a -1. A este método se le llama si se llama a más de un const ructor f oreach y N E T F r amew or k necesita devolver el est a do de la enumeraci ón a su valor inicial. To d a esta implementación hace que el método Main ( ) sea muy claro. El método puede crear un objeto de la clase Rainbow y us ar f oreach para iterar c a da nombre de color de la clase.
Cómo implementar limpieza mediante IDisposable El C L R contiene un mecani smo p a r a la eliminación aut omát i ca de objetos l lamada recolección de objetos no utilizados. Es import ant e co mprender cómo funciona este mecani smo, en qué se diferencia de otros sistemas y cómo hacer el código C# lo más compatible posible con este algoritmo p ar a eliminar objetos creados. En C ++, un objeto se crea con la pal ab ra clave new y la operaci ón devuelve al objeto un puntero ya que se crea en el montón de memori a de la aplicación. El p ro g ra ma do r en C + + debe liberar esta m emori a invocando al oper ado r delete sobre ese mi smo puntero cuando el objeto ya no sea necesario. Al invocar al o per ado r delete se libera la memori a u sad a por el objeto y se llama al destruc tor de la clase par a que la clase pueda realizar cualquier operación de limpieza específica de clase. Si no se invoca al operador delete sobre un puntero de objeto devuelto por new. se produce pérdida de memoria. En al gunos entornos de ejecución, como Visual Basic y C O M. se lleva la cuenta de las referencias de los objetos. Los entornos de ejecución llevan la cuen ta de los s ubprocesos asociados a un objeto y libera aut omát icamente el objeto c uando su cont ador de referencias llega a cero. Esto permite al programador
322
olvidarse de tener que llamar a una instrucción de destrucción como delete y ayuda a eliminar toda un tipo de errores relacionados con la pérdida de memoria. C L R usa un es quema de recuperaci ón de memori a llamado recolección de objetos no utilizados. Los objetos no se destruyen cuando se libera su última referencia, como ocurre con los sistemas que cuentan las referencias, como C O M y COM+. En su lugar, los objetos se destruyen algo más tarde, cuando el recolector de objetos no utilizados de C L R se ejecuta y destruye los objetos preparados para ser borrados. Los destructores de objetos de C# se ejecutan, no cuando se libera la última referencia del objeto, sino cuando el recolector de objetos no utilizados libera las estructuras de datos internas de C L R usadas para seguir la pista al objeto. Es i mportante tener en cuenta este diseño de recolección de objetos no utilizados al p ro gr a ma r las clases de C#. Las clases que gestionan recursos y necesitan ser explicitamente cerradas cuando se destruye el objeto, como los controladores de conexiones de bases de datos, deben cerrarse tan pronto como el objeto deje de usarse. Insertar código de limpieza en el dest ructor de la clase significa que los recursos no serán liberados hast a que el recolector de objetos no utilizados dest ruya el objeto, que puede ser mucho después de que se libere la última referencia al objeto. .NET F ramework admite una interfaz llamada IDisposable que las clases pueden implementar p ar a admitir recursos de limpieza de clases. La interfaz IDisposable se incluve en el espacio de nombre System de N E T Framework. Admite un solo método llamado Dispose (). que no toma par ámet ros y no devuelve nada, como se mu es tra en el siguiente ejemplo: using public
System; class MyClass
: IDisposable
{ public
M y C l a s s ()
i } ' M y C l a s s ()
{ } public
void D i s p o s e ()
{ } } Esta clase admite un constructor, al que se llama cuando se crean los objetos de la clase; un destructor, al que se llama cuando el recolector de objetos no utilizados destruye los objetos de la clase; y Dispose (). al que se puede l lamar cuando el código cliente se deshace del objeto. El código cliente puede consul tar los objetos p ar a ver si son compatibles con la interfaz IDisposable y puede llamar a su método Dispose ( ) par a libe
323
rar recursos de clase antes de que el recolector de objetos no utilizados destruya el objeto. El lenguaje C# realiza esta consulta de for ma sencilla mediante una sintaxis especial que incluye la p a l ab r a clave using. La pal abra clave using puede usarse en una expresión con paréntesis que incluye la creación de un n u e vo objeto: using(MyClass MyObj ect = new MyClass () )
{ // use
aguí
"MyObject"
} En este ejemplo, la p al ab ra clave using se usa par a crear un nuevo objeto ll amado MyObj ect. El nuevo objeto pertenece a la clase MyObj ect. El objeto puede usarse en cualquier instrucción que esté incluida entre las llaves que si guen a la pal abr a clave using. El objeto es a ut omát icame nte destruido cuando la ruta de acceso del código llega a la llave de cierre del bloque using. Si la clase del objeto creado en la instrucción using admite IDisposable. ent on ces el mét odo Dispose ( ) de la clase es i nvocado aut omát i came nt e sin que el cliente deba hacer nada. El listado 13.4 mues tra una clase llamada MyClass que i mplementa la interfaz IDisposable. Listado 13.4. IDisposable y la palabra clave using using
System;
public
class MyClass
: IDisposable
{ public
M y C l a s s ()
i Consolé. W r i t e L m e ("constructor") ;
) ~ M y C 1 a s s ()
{ Consolé.WriteLine("destructor");
} public
void
D i s p o s e ()
{ Consolé.WriteLine ("impiement a1 1 on I D i s p o s a b l e .D i s p o s e ()");
of
} ) public
class
MamClass
{ static
void M a i n ()
{ using (MyClass MyObject
{
324
= new MyClass ())
Esta aplicación de consola i mplementa la clase MyClass mo s tr ad a en el lis tado 13.4 v contiene instrucciones en su constructor, destructor e implementación Dispose ( ) que escriben mensajes en la consola. El listado 13.4 también inclu ye una instrucción using en su método Main ( ) que crea un objeto de tipo MyClass. Si se ejecuta el listado 13.4 se escribe el siguiente mensaje en la consola: constructor implementation dest ructor
of
IDispo sa bl e.D i s p o s e ()
Obs er ve que la i mplementación Dispose ( ) de la mterfaz IDisposable es invocada a ut om át i ca me n te sin que el método Main ( ) intervenga. Ten ga en cuenta que sólo debe implement ar la interfaz IDisposable para las clases que tienen recursos que deben ser liberados explicitamente. como c o nexiones de bases de datos o indicadores de ventana. Si la clase sólo contiene referencias a objetos gestionados por el CLR. entonces no es necesario implementar IDisposable. Implementar IDisposable significa que el C L R necesita realizar más trabajo p ar a eliminar los objetos y este t rabajo adicional puede ralentizar el p roces o de recolección de elementos no utilizados. Implemente IDi sposable cuando sea necesario pero no lo haga a menos que sea necesario.
Resumen Piense en una interfaz como en una promesa de que una clase implementará los métodos, propiedades, indizadores y eventos definidos en la interfaz. Las interfaces p r o p o r c i o n a n d e f i n i c i o n e s de m i e m b r o s , p e r o no p r o p o r c i o n a n n i n g u n a implementación. Se necesita una clase que implemente una interfaz p ar a p r o p o r cionar una i mplementación de cada uno de los miembros de la interfaz. Una clase puede implementar varias interfaces. aunque sólo puede derivarse de una de las clases base. Las interfaces pueden derivarse de otras interfaces. del mismo modo que las clases pueden derivarse de las clases base. Las pal abr as clave de C# is vas pueden usarse par a t raba jar con objetos que implementen interfaces. La p al ab r a clave i s se usa en una expresión booleana que devuelve True si un objeto implementa una interfaz y False en caso con trario. La pal abr a clave as convierte una variable de objeto en una variable de un tipo de interfaz. Las expresiones que usan la pal abr a clave as devuelven nuil si el objeto no implementa la interfaz designada. En este capítulo se ve un ejemplo sobre cómo implementar una interfaz defini da por N E T Framework. N E T Framework implementa muchas interfaces y es
325
aconsejable revisar la documentación y estudiarlas todas. Las interfaces de N E T F ramework comienzan con la l et ra /. Revíselas todas. Puede usar las interfaces de N E T F ramework p ar a implementar cualquier cosa, desde dar formato per sonal i zado a la consola p ar a mecani zar la hast a la s emánt ica de eliminación de la reco lección de elementos no utilizados.
326
14 Enumeraciones
Algunas de las variables definidas en el código pueden usarse par a contener un valor tomado de un conjunto de valores posibles. Por ejemplo, puede necesitar seguir el rastro del estado de un archivo. Podría definir una variable que pudiera describir si un archivo está abierto, cerrado o si no se puedo encontrar. Un modo de lograrlo sería escoger algunas constantes par a definir las distintas opciones y un entero p a r a que contenga el valor actual, como en el siguiente código: const int FileOpen - 1; const int FileClosed = 2; const int FiieNotFound = 3; int
Fi leStatus;
FileStatus
- FileClosed;
Este código es código C# válido y se compi lará perfectamente. Sin embargo, un p rog ra ma do r puede asi gnar a la variable un valor que no esté disponible en el conjunto de constantes definidas. El tipo de datos de F i l e S t a t u s es un n ú m e ro entero y el compi lador de C# ac ept a perfectamente cualquier código que a s ig ne a la variable cualqui er valor entero válido, aunque el objetivo es restringir el conjunto de valores válidos al conjunto definido por las constantes. En teoría, asignar a F i l e S t a t u s un valor no definido por las constantes no debería estar
329
permitido porque lo que se pretendía en un principio era restringir el conjunto de valores posibles al conjunto definido p a r a esa variable. La situación de desarrollo ideal en casos como éste es que debería ser posible definir una variable y as oc ia r el valor a un conjunto de posibles valores v álidos. Además, el compi lador de C# debería ser c ap az de evitar que los progr amadore s asignen a la variable un valor no definido del conjunto de valores posibles. Como resultado, C # admite un cons tructor llamado enumeraci ón que se ocupa de este caso concreto. Las enum era cio nes son un gru p o de constantes definidas con un nombre c o mún. El nombre de la enumeración puede usarse como un tipo de variable una vez que se h ay a definido la enumeración. Cua nd o se us a una variable definida como un tipo de enumeración, el compi lador de C# se a se g ur a de que los valores a si g nados a las variables del tipo de la enumeraci ón concuerden con uno de los val o res del conjunto de constantes definidas en la definición de la enumeración. Las enumeraciones son ideales par a las situaciones en las que una variable debe asociarse a un conjunto de valores específico. S upongamos , por ejemplo, que está escribiendo una clase de C# que controla una puer ta electrónica y decide escribir una propiedad par a la clase llamada DoorState. que abre o cierra la puerta: public int DoorState
{ set { InternalDoorState
= valué;
} } Tamb ién puede definir algunas constantes que se pueden u sar p ar a hacer el código más legible: public public
const const
int DoorStateOpen = 1; int DoorStateClosed — 2;
La propi edad y las constantes permiten que el código que t rabaj a con los objetos de la clase p ued a escri bir código legible como el siguiente: DoorStateOb]ect = new DoorClass () ; D o o r O b j e c t .DoorState D o o r O b ] e c t .DoorState
= =
DoorClass.DoorStateOpen; DoorClass.DoorStateClosed;
El código anterior se compila y ejecuta sin problemas. Sin embargo, la propie dad DoorState se define como un int y no hay n ada que impida a los i nvocadores u s ar valores que no tienen sentido y as ignar los a la propiedad
DoorState: Do or O b j e c t .DoorState
330
=
12 3 45;
Este código t ambi én es válido porque el literal 12 3 4 5 está dentro de los lími tes válidos de un entero de C# y la propi edad DoorState está definida como poseedora de un tipo int. Aun qu e este código es válido desde el punt o de vista de la c ompi laci ón de C#. no tiene sentido en el nivel de clase porque el estado de la puerta en realidad sólo debería ser abierta o cerrada. Podría crear algún código par a la verificación de errores en la propiedad DoorState p ar a que sólo acepte valores válidos, pero sería incluso mejor hacer que el compi lador de C# i mponga la restricción por nosotros cuando se crea el código. Las enumeraciones prop or ci on an el mecani smo en tiempo de compilación que está buscando. Le permiten ag r up ar las constantes relacionadas, como las c ons tantes DoorStateOpen y DoorStateClosed. bajo un nombre de grup o y usar ese nombre de gru po como un tipo de valor. Puede, por ejemplo, a gr u p a r las constantes Door StateOpen v Door Sta teClosed en una enumer aci ón lla mada LegalDoorStates y redefínir la propi edad DoorState p ar a que t ra bajen con un tipo de LegalDoorStates. en lugar de con un int. El compilador de C # puede entonces a se gurar que los valores asignados a la propiedad son miembros de la enumeraci ón y p roducir á un error si el valor no existe en la e nu meración.
Cómo declarar una enumeración Puede declarar una enumeraci ón en C# usando la siguiente sintaxis: La palabra clave enum. Un identificador de enumeración. Un tipo base opcional. Identificadores de valor de enumeraci ón s eparados por comas y entre lla ves. La e numeración LegalDoorStates de la anterior sección se definiría como se indica a continuación: enum
LegalDoorStates
{ DoorStateOpen, DoorStateClosed
} C ad a uno de los mi embros de las enumeraci ones tiene un valor numérico a s o ciado. Por defecto, el valor numérico del pri mer valor es cero y el valor de cada uno de los otros miembros es una unidad mayor que el valor de la anterior en ume ración. Si se usan estas reglas y la enumeración definida anteriormente, el valor
331
por defecto de DoorStateOpen es 0 y el valor de DoorStateClosed es 1. Si lo desea, puede invalidar estos valores asignando valores a los miembros cuan do se definen us ando el op er ad or de asignación: enum
LegalDoorStates
{ DoorStateOpen = 10 0, DoorStateClosed = 150
} Puede asi gnar los miembros a un valor literal o al resultado de una expresión constante: enum
LegalDoorStates
( DoorStateOpen = (75 + 25), DoorStateClosed = 150
} Si no se asi gna un valor a un miembro concreto de la enumeración, se aplican las reglas de asignación de valor por defecto. Observe la siguiente e nu m e r a ción: enum
LegalDoorStates
{ DoorStateOpen = 10 0, DoorStateClosed
} Usando esta enumeraci ón y las reglas de asignación de valor por defecto, el val or de Door StateOpen es 100 y el valor de DoorStateClosed es 101. C# también permite el uso de un identificador de enumeraci ón p ar a as ignar un valor a otro identificador: enum
LegalDoorStates
{ DoorStateOpen = 100, DoorStateClosed, LastState = DoorStateClosed
} En este ejemplo, el valor de la enumer aci ón LastState es igual al valor de la enumeración DoorStateClosed. que en este caso es igual a 101. Esto demu es tra que dos identificadorcs en una enumer aci ón tienen el mi smo valor. Las enumeraciones corresponden a un tipo de valor particular. Este tipo co rrespondiente recibe el nombre de tipo subyacente de la enumeración. Las enume raciones pueden ser convertidas explícitamente a su tipo subyacente. Por defecto, el tipo s ubyacente de todas las enumeraciones es int. Si quiere usar un tipo suby acente diferente, especifique el tipo s ubyacent e después de dos puntos que siguen al identificador de la enumeración:
332
enum LegalDoorStates
: short
{ DoorStateOpen = 100, DoorStateClosed
} Todas las asignaciones explícitas de valor deben usar valores que incluidos dentro de los límites válidos del tipo suby acente de la enumeración. Obs er ve el error en la siguiente enumeración: enum Weather
: uint
{ Sunny = -1, Cloudy = - 2, Rain = - 3, Snow = -4
} Esta declaración de enumeraci ón es un error porque el tipo subyacent e es u i n t y las asignaciones usan valores negativos que están fuera de los valores legales de un u i n t . Si se compila la anterior enumeración, el compi lador de C# emite los siguientes errores: error C S 0 0 3 1 : 'u i n t ' error CS 0 0 3 1 : 'uint' error C S O 031 : 'uint ' error C S 0 0 3 1 : 'uint '
El valor
const ante
'- 1 ' no s e puede
convertir
a
El valor
constante
'- 2 ' no s e puede
convertir
a
El valor
constante
'-3'
no s e puede
convert i r a
El valor
constante
'- 4 ' no s e puede
conve rtir a
Cómo usar una enumeración Tra s haber definido la enumeración, se puede usar el identificador de e nu m e r a ci ón c o mo un t ipo de v ar i a b l e . El l i st ad o 14.1 m u e s t r a c ó mo la cl as e DoorController puede usar una enumeración. Listado 14.1. Cómo usar una enumeración public
enum LegalDoorStates
{ DoorStateOpen, DoorStateClosed
) class
DoorController
{ prívate
LegalDoorStates
Curr en tS ta te ;
333
public
LegalDoorStates
State
{ ge t
{ return
CurrentState;
} set { CurrentState
= valué;
} } } class
MamClass
{ public
static
void M a i n ()
{ DoorContro11er Door
Door;
= new D oo rC o n t r o l l e r () ;
Door.State
=
LegalDoo rS ta te s.DoorStateOpen ;
} } La enumeración LegalDoorStates está definida fuera de una d ec la ra ción de clase Esto está permitido en C# y hace que la enumeración sea visible p ar a todas las clases del archivo fuente. Una alternativa es definir las e nu mer a ciones dentro de una declaración de clase usan do p a l a br a s clave de ámbito (public. protected. internal o private) par a especificar el modo en que la enumeración es visible p a r a las otras clases. Tr as definir la enumeración LegalDoorStates. se puede usar su nombre como un tipo de variable. Se usa como tipo para el campo privado Currentstate de la clase DoorController y también como tipo de la propiedad publica State de la mi sma clase. Para referirse a una enumeraci ón del código se usan el nombre de la e n u me r a ción v uno de los identificadorcs de la enumeración. Estos identificadores están s eparados por un punto, como se muest ra en la siguiente instrucción: Door.State
=
Lega lD oo rS ta te s.DoorStateOpen;
El valor de la expresión LegalDoorStates .Door StateOpen es igual al valor del identificador Door StateOpen de la enumeración LegalDoor States. Este valor se asigna a la propiedad State. La ventaja de este diseño basado en enumeraciones es que el compilador puede identificar los lugares donde el código intenta as ignar a la propiedad State un valor diferente del valor pr o cedente de la enumeración. Observe el error en la siguiente instrucción: Door.State
334
= 1234 5;
El anterior código es un error porque la propiedad State está definida como si tomase un valor de tipo LegalDoorStates y en su lugar se le asigna un v al or entero. El código del an ter i or ej empl o p ro du c e el s iguiente e r ro r del compi lador de C#: error CS0029: No se puede 'in t ' a 'LegalDoorStates'
convertir
implícitamente
el tipo
Cómo usar operadores en valores de enumeración Debido a que los valores enumer ados tienen un tipo s ubyacent e y un valor, lo logico es que escriba un código que trate con valores subyacentes. Puede usar v arios operadores de C# con valores enumerados: igualdad desigualdad menor que m ay or que menor o igual que m ay or o igual que suma resta •
AND
•
OR exclusivo O R inclusivo compl ement o bit a bit incremento decremento
Por ejemplo, observe el listado 14.2. Listad o 14.2. Cómo usar operadores con enumeraciones using public
System; enum
FileAttnbutes
{
335
AttrNone = 0, AttrReadOnly = 1, AttrHidden = 2, AttrReadyForArchive
= 4
} c 1 a s s M a i n C 1ass
{ pubJic
static void M a m ()
{ FileAttributes
FileAttr;
FileAt.tr = Fí 1 eAttributes .AttrReadOnly FileAttribut.es .AttrHidden; Consolé . W n t e L í n e (Fi leAttr) ;
|
} } El código del listado 14.2 define un conjunto de valores enumerados que es pe cifican atributos para un archivo o un disco. Un archivo puede no tener atributos e s p e c i a l e s ( F i l e ñ t t r i b u t e s .AttrNone). a t r i b u t o s de solo l ect ur a (FileAttr ibutes .AttrReadOnly). at ri but os ocultos (FileAttributes .H idden) o atributos listos par a ser archivados ( FileAttr ibutes .
AttrReadyForArchive). El codigo del método Main ( ) especifica una variable local llamada FileAttr. que es de tipo Fi leAttributes. El código asigna el valor a un archivo oculto de solo lectura mediante una operación OR sobre los atributos FileAttributes .AttrReadOnly v FileAt tribu tes.Hidden. El valor de la variable local se escribe a conti nuación en la consola. Si se compila y ejecuta el listado 14.2 se escribe lo siguien te en la consola:
El listado 14.2 p ro du c e el v al or 3 p o r q u e el v a lo r de la e n u m e r a c i ó n FileAttr ibutes .AttrReadOnly. 1. se unió al valor de la enumeración FileAttr ibutes .Hidden. 2. en una operación OR. Realizar una operacion bool eana OR con los v alores 1 y 2 produce un resultado de 3. Tambi én puede convertir un valor enumer ado a un valor con el tipo del tipo s ubyacente de la enumeración: enum IntEnum
{ EnumOne = 1 , EnumTwo, EnumTh r e e
} IntEnum In t E nu mV al úe ; int IntVa1u e ;
336
IntEnumValue = EnumTwo; IntValue = (int) IntEnumValue;
// el valor es 2
El có di g o a n t e r i o r c on v i e r t e el v a l o r de u n a v a r i a b l e de e n u m e r a c i ó n IntEnumValue a su equivalente entero y asigna el entero IntValue a ese valor. Como la variable IntValue es un entero estándar, se le puede asignar cualquier valor válido par a un entero. No esta limitado al conjunto de valores definidos por la enumeración, aunque se le asigna un valor que procede de una variable enumerada.
Cómo usar la clase .NET System.Enum El tipo enum de C# es en realidad un alias de la clase System. Enum defi nida en N E T Framevvork. Podemos us ar cualquiera de los mi embros de la clase NE T System. Enum en las enumer aci ones que definamos.
Cómo recuperar nombres de enumeración El listado 14.3 muest ra cómo el código puede t raba jar con enumeraciones como objetos System. Enum. Se trata de una mejora del listado 14.1 que recu pera el estado actual de la puerta y escribe el nombre del estado en la consola. Listado 14.3. Cómo recuperar una nombre de enumeración mediante GetName() u s m g System; public
enum
LegalDoorStates
{ DoorStateOpen, DoorStateClosed
} class
DoorController
{ prívate public
LegalDoorStates LegalDoorStates
Cur re nt St at e; State
{ ge t
{ return
CurrentState;
} set { CurrentState
= valué;
}
337
class M a m C l a s s
{ public static void Main ()
{ DoorController Door; string E n u m N a m e ; Door
= new D oo rC o n t r o l l e r ();
Door.State = Lega lD oo rS ta te s.DoorStateOpen; EnumName = Lega lD oo rS ta te s.G e t N a m e (typeof (LegalDoorStates), Do or .S ta te ); C o n s o l é . W r i t e L i n e (E n u m N a m e );
} } El método Main ( ) del listado 14.3 usa el método GetName ( ) de la clase System. Enum par a obtener una cadena que represente un valor de e n um er a ción El pri mer p ar amet ro es un objeto Type que especifica la enumeración a la que se consulta. La expresión typeof ( LegalDoor States ) devuelve un objeto N E T T y p e p a r a el t i p o e s p e c i f i c a d o (en e s t e c a s o , la e n u m e r a c i ó n LegalDoorStates). El segundo p ar ám et ro es el valor de la enumeración a c tual. de la que debe devolverse la representación de su cadena. La siguiente ins trucción muest ra cómo puede usarse el método GetName ( ) p ar a obtener el nombre de un valor enumerado: EnumName = LegalDo or St at es .G et Na me (type of(LegalDoorStates) , Door.State) ;
Esta instrucción se interpreta como: "Devuelve una cadena que represente el nombre del valor de la propiedad Door. State. Este valor es una parte de la enumer aci ón LegalDoorStates." Si se ejecuta el listado 14.3 se escribe lo siguiente en la consola: DoorStateOpen
T am bi én se puede u sar el método Format ( ) par a recuper ar el nombre de un valor de enumeración, según su valor numérico. La llamada GetName ( ) del listado 14.3 puede reemplazarse por la siguiente llamada a Format ( ) : EnumName = Lega1DoorStates.F o r m a t (t y p e o f (LegalDoorStates), 0, "g");
El primer p ar ámet ro de Format ( ) es el mismo que el pri mer p ar ám et ro de GetNames (). que es el tipo de enumeración que se usa en la llamada. El s egun do p ar á me tr o de Format ( ) es el valor numéri co que debe devolver la llamada. El ultimo par ámet ro de Format ( ) es la cadena que especifica los contenidos de la cadena que debe devolver la llamada. La cadena de formato puede ser una de las siguientes:
g. que especifica que debe devolverse el valor de enumer aci ón con el valor numéri co que concuerde con el valor del segundo parámetro.
338
x, que especifica que el val or del segundo p ar á me tr o debe devolverse como una cadena que represente el valor en notación hexadecimal d. que especifica que el valor del segundo p ar á me tr o debe devolverse
como una cadena que represente el valor en notación hexadecimal f . que especifica que el valor debe ser trat ado como un conjunto de val o
res enumer ados combi nados y que el método debe devolver una lista de valores delimitada por comas como una cadena El valor de formato f se creó par a ser usado con enumeraciones que repre sentan valores de bit. Observe la siguiente enumeración: public enum BitsToSet
{ BitOSet BitlSet Bit2Set Bit 3 S e t B 1 1 4S e t B i1 5 S e t Bit 6Set B 1 1 7 S et
= = = = = = = =
1, 2, 4, 8, 16, 32, 6 4, 128
} La enumeración anterior representa un conjunto de bits que pueden asignarse a un byte. Se pueden asignar varios bits a una v ariable mediante el operador booleano OR. como en el siguiente ejemplo: BitsToSet Byte; Byte = BitsToSet.BitlSet
I BitsToSet.Bit 3 Set
| Bi t s T o S e t .B it óS et ;
Ll amar al método Format ( ) en la variable Byte con el par amet ro de f or mato f devuelve una cadena que represent a los nombres de los valores e nu m e r a dos cuvos v alores se encuentran en la variable: BitlSet,
B i t 3 S e t , Bit 6Set
Cómo comparar valores de enumeración El método CompareTo ( ) de la clase System. Enum puede c om par ar una enumeración con otra v devolver un entero que describe la relación entre los dos v alores. Observe el listado 14.4. que comp ar a el v alor de una v ariable enumerada con el valor det erminado de la mi sma enumeración. Listado 14.4. Cómo comparar valores de enumeración con CompareTo() us m g public
Sys t em; class MainClass
339
public enum Color
{ Red = 0, Or a n g e , Yellow, Green, Blue , Indigo, Violet
static
void M a i n ()
{ Color
MyColor;
MyColor = Color.Green; C o n s o l e . W r i t e L m e (" {0} ' Console.WriteLine (" {0} ' C o n s o l e . W r i t e L m e (" {0} '
M y C o l o r .Co m p a r e T o ( C o l o r .R e d ) ); M y C o l o r .Compar eTo(Color.Green) ) ; M y C o l o r .C o mp ar eT o( Co lor.Violet))
El listado 14.4 declara una clase con una enumeración pública llamada Co lor. Sus valores varían de 0 a 6. El método Main ( ) declara una variable de tipo Color llamada MyColor y asigna el valor Green a la variable. A conti n uación invoca a CompareTo ( ) par a c om pa r ar el valor de la variable con otros valores de la enumeración. El método CompareTo ( ) dev uelve uno de estos tres v alores: -1 si el valor que se pas a como argument o a CompareTo ( ) tiene un valor superior al valor enumerado usado p ar a invocar al método 1 si el valor que se p a sa como a rgument o a CompareTo ( ) tiene un valor inferior al valor e numer ado usado par a invocar al método 0 si los dos valores son iguales En el listado 14.4. se llama tres veces al método CompareTo (). En la prime ra llamada, la variable MyColor se c omp ar a con el valor Red. Como Green. que tiene el valor 3. tiene un valor super ior a Red. que tiene el valor 0. CompareTo ( ) devuelve 1. En la segunda llamada, la variable MyColor se co mpar a con el valor Green. Como los valores son iguales. CompareTo ( ) dev uelv e 0. En la ultima llamada, la variable MyColor se co mpar a con el valor Violet. C o mo Green. que tiene el valor 3. tiene un valor inferior a Violet. que tiene un valor 6. CompareTo () devuelve -1. El ar gume nt o usado en la llamada a CompareTo ( ) debe ser del mismo tipo que la enumeración usada p ar a llamar al método. Usar cualquier otro tipo, inclu so el tipo subyacente de la enumeración, produce un error en tiempo de ejecución
340
Cómo descubrir el tipo subyacente en tiempo de ejecución De sc ub ri r el tipo subyacent e de una enumer aci ón en tiempo de ejecución es sencillo con el mét odo GetUnder lyingType (). Este método, al que se llama en el tipo de enumeración, en lugar de una variable del tipo, t oma un p ar ámet ro Type que represent a el tipo de la enumer aci ón y devuelve otro objeto Type que representa el tipo s ubyacent e de la enumeración. El método ToString ( ) puede ser llamado en el objeto Type devuelto par a obtener un nombre legible p a r a el tipo, como se mu es tra en el siguiente código: s t n n g FormatSt r i n g ; Type U n d e r l y i n g T y p e ; UnderlyingType = B i t s T o S e t .GetUnderlyingType (typeof (BitsToSet) ) ; Co n s o l é . W r i t e L i n e ( U n d e r l y i n g T y p e .ToString () ) ;
Este código r e c u pe r a el tipo s u b y ac e nt e p a r a una e nu me r a c i ó n l lamada
BitsToSet y escribe el nombre del tipo en la consola, que produce una cadena como la siguiente: System.Int 3 2
Cómo recuperar todos los valores de enumeración El método GetValues ( ) devuelve una mat ri z de todos los valores de e n u meración ordenados en orden ascendente según su valor numérico, como se puede ver en el siguiente código: Array
ValueArray;
ValueArray = C o l o r .GetValues (typeof (Color) ) ; foreach (Color Colorltem m ValueArray) C o n s o l é . W r i t e L i n e ( C o l o r l t e m . T o S t r i n g ());
Este código llama a GetValues ( ) en la enumer aci ón Color definida con anterioridad. El mét odo GetValues ( ) devuelve una matriz y se v isita a los elementos de la matriz de uno en uno mediante la p al abr a clave f oreach. El nombre de cada elemento de la matriz se escribe en la consola, como se puede ver a co nt i nu a ción: Red Orange
341
Ye11ow Gr e en B lúe Indigo Vi o 1et
Análisis de cadenas para recuperar valores de enumeración La clase Enum contiene un método de análisis de cadenas llamado Parse (). que acept a un a cadena como ent rada de datos y devuelve el valor de la e nu m e r a ción cuyo nombre concuerda con la cadena pr opor ci onada, como se puede ver en el siguiente ejemplo: C o l o r .Parse (typeof (Color) ,
"Blue") ;
Esta llamada devuelve un objeto que representa el valor enumer ado llamado
Blue en una enumeración llamada Color C omo muchos otros métodos de enumeraci ón, el método Parse ( ) es llamado en el tipo, en lugar de una variable del tipo. El mét odo Parse ( ) devuelve un objeto, que necesita ser convertido expl íci t ament e en un valor del tipo apropiado. El siguiente ejemplo muest ra cómo el m ét o do Parse ( ) pude ser usado como uno de los muchos modos de representar un v alor enumerado: Color ColorValue; object ParsedObject; ParsedObject - Col o r .Parse (typeof (C o l o r ) , "Blue"); Consolé . W n t e L m e (Pars edOb j e ct .Ge t Type () . T o S t n n g () ) ; ColorValue - (Color)ParsedOb]ect; Consolé. W n t e L m e (ColorValue. ToS t r i ng () ) ; Con s o l é . W n t e L m e (Color. Format (t ype of (Color) , ColorValue,
"d " ) ! ;
En este codigo. se llama a Parse ( ) en el tipo de enu mer aci ón Color y se le otorga una cadena de ent rada Blue. Esta llamada dev uelv e un objeto y el codigo escribe el tipo del objeto en la consola. El objeto se conv ierte entonces explícitamente en una variable del tipo Color y se escribe en la consola el nombre del valor de la enumeraci ón y el valor deci mal: MainClass+Color B Lúe
4 Este resultado d emues tra que el objeto devuelto por el método Parse ( ) es del tipo Color. La variable convertida, que es una variable de Color, tiene un nombre de cadena Blue v un valor decimal 4.
342
Resumen Las enumeraciones se emplean par a a g r u pa r un conjunto de constantes rela cionadas. Al dar a sus enumeraciones un nombre, se puede us ar ese nombre en el código como un tipo de variable una vez que se haya definido la enumeración. Las enumeraciones, por defecto, se basan en un conjunto de constantes i n t . Se puede invalidar este valor por defecto especificando un tipo s ubyacente p ar a la e n um e ración. Se pueden us ar como tipo suby acente de una enumer aci ón muchos de los tipos numéricos de C#. Se deben emplear enumeraciones cuando queramos que el compi lador de C# garant ice que las constantes con las que t rab aj amos en el codigo proceden de un conjunto de valores válidos. Por defecto, el compilador de C # asigna valores numéricos a los identificadores de las enumeraciones. El primer identificador tiene el valor de cero y las otras enumeraciones aument an su valor a partir de ahí. Si lo deseamos, se puede u sar el operador de asignación p ar a as ignar un valor a un identificador de enumeración cuando se define la enumeración. Un valor en una enumeración se especifica escribiendo el nombre de la e num e ración. un p u nt o y el n o m b r e de los i d en ti fi cad or es de e nu m e r a c i ó n . Los identificadores de enumer aci ón pueden convertirse implícitamente al tipo suby a cente de la enumeración. Esta conversión implícita también permite el uso de algunos de los operadores de C# p ar a t ra ba j ar con los valores de enumeración. Todas las enumeración de C# derivan de una clase base de N E T llamada Sy s t e m .Enum. La clase S y s t e m .Enum contiene al gunos métodos útiles que pueden a y u da r a obtener las máxi mas prestaciones de las enumeraciones. Este capítulo ha exa mi na do la may or ía de estos métodos.
343
m 15 Eventos y delegados
En el flujo general del típieo segmento de software orientado a objetos, un fragmento de codigo crea un objeto de una clase y llama a métodos de ese objeto. En este contexto, el mvocador es el codigo activo porque es el codigo el que llama a los métodos. El objeto es pasiv o, en el sentido de que espera y realiza una acción solo cuando se invoca a uno de sus métodos. Sin embargo, también se puede producir el contexto contrario. Un objeto p u e de realizar una tarea y avi sar al inv ocador cuando ocurre algo durante el proceso A este algo se le llama un evento y la publicación de ese ev ento del objeto se le conoce como d esen cadenar un evento. El proceso activado por eventos, en el que fragmentos de codigo informan a otras piezas de codigo c uando se producen eventos relevantes, no es una novedad de N E T La capa de mterfaz de usuario de Windows siempre ha usado una forma de ev entos para i nformar a las aplicaciones Window s cuando los usuarios t r a b a jan con el ratón, presionan una tecla en el teclado o mueven una ventana Los controles ActiveX desencadenan eventos par a los contenedores de control de ActiveX cuando el usuario realiza una acción que afecta al control. El lenguaje C# contiene pal abr as clave especiales que hacen que sea fácil desencadenar, publ icar y proces ar eventos en el código de C'#. Se pueden usar estas pal abr as clave para permitir que las clases de C# desencadenen y procesen eventos con el mínimo esfuerzo.
345
Cómo definir delegados Cua nd o se p rog r am an los eventos que desencadenan las clases de C#. es necesario decidir cómo recibe el evento otros fragment os de código. Otros f r ag mentos de código necesitan escribir un método que reciba y procese los eventos que se publican. Suponga, por ejemplo, que su clase implementa un servidor Web y quiere des encade nar un evento siempre que llega una solicitud de una página desde Internet. Otros fragmentos de código quizás quieran realizar al guna acción cua ndo la clase desencadene este evento new request y ese código debería incluir un método que se ejecute cua ndo se desencadene el evento. El método que implementan los usuarios de la clase p ar a recibir y proces ar los eventos es definido por un concepto de C# llamado delegado. Un delegado es una especie de "función patrón" que describe el aspect o que tiene el cont rol ador de eventos del usuario. Un delegado tambi én es una clase que tiene una firma y contiene referencias a métodos. Es como un a función puntero, pero puede c ont e ner r eferencias a métodos estáticos y de instancia. Para los métodos de instancia, el delegado al mac ena una referencia al objeto y al punto de entrada de la función. Un delegado define lo que debe devolver el controlador de eventos del usuario y lo que debe ser la lista de parámetros. Para definir un delegado en C#, hay que u s ar la siguiente sintaxis: La pal abra clave de C# delegate. El tipo devuelto por el cont rol ador de eventos. El identifícador de delegado. La lista de par ámet ros del controlador de eventos, entre paréntesis. Si se declaran delegados en la clase que des encade na el evento, se les puede anteponer las pal abras clave public. protected, internal o prívate como se p ueden ver en este ejemplo de un a definición delegate. public
delegate
void
E ve nN um b e r H a n d l e r (int
Number);
En este ejemplo se crea un delegado público llamado EvenNumberHandler que no devuelve nada. Este delegado sólo define un par ámet ro p ar a ser pasado, de t ipo int . El identifícador de delegado, EvenNumberHandler. puede ser cual quier nombre que elija mientras no le dé el nombre de una p a l a b ra clave de C#.
Cómo definir eventos Para ac la rar lo que es en realidad un evento, vamos a empezar con un ejemplo. Está conduciendo en su coche y ap arece en el s alpicadero la luz que indica que queda poco combustible. Lo que en realidad ha ocurri do es que un sensor en el
346
depósito de gasolina ha avisado al ordenador de que el nivel de combustible está bajo. El ordenador entonces desencadena un evento que a su vez enciende la luz del salpi cadero para que sepa que tiene que comprar más combustible. En pocas palabras, un evento es un medio que tiene el ordenador de avisarle de una condición. Para definir un evento que desencadene una clase se usa la p al ab ra clave de C# event. En su forma más simple, las declaraciones de eventos de C# usan la siguiente sintaxis: La p al abr a clave de C# event. El tipo de evento. El identificador de evento. El tipo de evento concuerda con un identificador de delegado, como se muestra en el siguiente ejemplo de servidor Web: public
delegate
public
class
void NewRequestHandler ( s t n n g
URL) ;
Webserver
{ public
//
..
event
NewRequestHandler
NewReque st Ev en t;
.
} Este e j em pl o d e c l a r a un d e l e g a d o l l a m a d o N e w R e q u e s t H a n d l e r . NewRequestHandler define un delegado que sirve como una plantilla par a los métodos que proces an el evento new request. Todos los métodos que necesitan p ro ces ar el evento new request deben obedecer las convenciones de llamada del delegado: no deben devolver ningún dato y deben tener una sola cadena como lista de p ar ámet ros . Las implementaciones de control de eventos pueden tener cualqui er n ombre de método mientras su tipo dev uelto y su lista de par ámet ros concuerdcn con el pat rón de delegados. La clase Webserver define un evento llamado NewRequestEvent. El tipo de este evento es NewRequestHandler. Esto significa que sólo los con troles de evento escritos par a que concuerdcn con las conv enciones de llamada del delegado pueden ser usadas p a r a pr oc es ar el evento NewRequestEvent.
Cómo instalar eventos Tras escribir el controlador de ev entos, hay que crear una nueva instancia de él e instalarlo en la clase que desencadena el evento. P ar a cr ear una nueva i nstancia de cont rol ador de eventos debe crear una nueva v ariable del tipo dele gado y p a s a r el n ombre del método cont rol ador de eventos como un argumento. Usando el ejemplo del cliente Web. la creación de una nueva instancia controladora de eventos puede tener este aspecto:
347
public void MyNewRequestHandler ( s t n n g URL)
{ } NewRequestHandler
Handlerlnstance;
H a n d 1erInstanee
new
=
NewRequestHandler(MyNewRequestHandler);
Tras crear la nueva instancia controladora de eventos, se usa el operador += par a añadirla a la variable de evento: NewRequestEvent
+=
Handlerlnstance;
Esta instrucción enlaza la instancia de delegado Handlerlnstance. que admite el m é t o d o MyNewRequestMethod. con el e v e n t o N e w R e q u e s t E v e n t . Mediante el oper ad or + = . se pueden enl azar tantas instancias de delegado como quiera par a un evento. Del mi smo modo, puede u sar el oper ador - = p ar a eliminar una instancia de delegado de un evento: NewRequestEvent
-=
Handlerlnstance;
Esta instrucción desenlaza la instancia de delegado Hand 1er Ins tance del evento NewRequestEvent.
Cómo desencadenar eventos Se puede des encadenar un evento desde una clase usando el identificador del evento (como el nombre del evento) como si fuera un método. La acción de invo c ar un ev ento como un método des encadena el evento. Dese ncad en ar el evento new request en el ejemplo del servidor We b puede t ener el siguiente aspecto. N e w R e q u e s t E v e n t (s tr UR LO f N e w R e q u e s t );
Los pa r ám et ros usados par a desencadenar el evento deben coincidir con la l i s t a de p a r á m e t r o s del d e l e g a d o del e v e n t o . El d e l e g a d o del e v e n t o NewReques tEvent se definió para acept ar un par ámet ro de cadena: por tanto, se debe p r op or ci onar una cadena cuando se desencadene el ev ento desde la clase del cliente Web.
Cómo unirlo todo El listado 15.1 muest ra delegados y eventos en acción. El código implemcnta una clase que cuenta desde 0 hasta 100 y desencadena un ev ento c uando encuen tra un numero impar d urante el proceso de contado.
348
Listad o 15.1. Cómo recuperar eventos de número impar using System; public class
delegate
void
EvenNumberHandler (.int Number) ;
Counter
{ public
event
EvenNumberHandler
public
C o u n t e r ()
O n Ev en Nu mb er ;
{ OnEvenNumber
= null;
} public
void CountTo'100 ()
{ int
CurrentNumber;
for (CurrentNumber Cu rr e n t N u m b e r ++)
= 0;
CurrentNumber
<-
100;
{ lf (CurrentNumber
2
0)
{ lf (OnEvenNumber
!= null)
{ OnEvenNumbe r (Cur r entNumbe r ) ;
} } } class
EvenNumberHand1erC1ass
{ public
void
Ev en Nu m b e r F o u n d (m t
EvenN um be r )
{ Console.WriteLine (EvenNumbe r ) ;
} ) class
MainClass
{ public: static
void Main ()
{ Counter MyCounter = new C o u n t e r (); EvenNumberHandlerClass MyEvenNumberHandlerClass = new E v e n N u m b e r H a n d l e r C l a s s (); M y C o u n t e r .OnEvenNumber += new EvenNumbe rHa nd 1e r (MyEvenNumbe r H a n d l e r C l a s s .EvenNumbe r F o u n d ) ; M y C o u n t e r .C o u n t T o l O O ();
}
349
Para compilar esta aplicación hay que crear una nueva aplicación de consola en Visual Studio y copiar en ella el código fuente o simplemente u sar el bloc de notas par a g ua r da r el archivo y a continuación usar: ese
< filename>
El listado 15.1 implementa tres clases: La clase Counter es la elase que realiza el cálculo. Implementa un méto do públ ico l lamado Co u n t T o l O O ( ) y un evento p ú b l ic o l lamado O n E v e n N u m b e r . El evento O n E v e n N u m b e r es del tipo delegado
EvenNumbe rHand1er. La clase EvenNumberHandlerClass contiene un método público lla mado EvenNumberFound. Este método actúa como el controlador de evento p a r a el evento OnEvenNumber de clase Counter. Imprime en la consola el entero p ropor ci onado como parámetro. •
La clase MainClass contiene el método Main ( ) de la aplicación
El método Main ( ) crea un objeto de clase Counter y da nombre al objeto My C o u n t e r . T a m b i é n c r ea un n ue v o o bj et o de c las e E v e n N u m b e r HandlerClass y llama al objeto MyEvenNumberHandlerClass. El método Main ( ) llama al método CountTolOO ( ) del objeto MyCounter. pero no antes de instalar una instancia de delegado en la clase Counter. El c ó d i g o c r e a u n a n u e v a i n s t a n c i a de d e l e g a d o q u e g e s t i o n a el m é t o d o EvenNumber Found del objeto MyEventNumberHandlerClass y lo añ a de al evento OnEvenNumber del objeto MyCounter us ando el oper ador +=. La i mplementación del método CountTolOO usa una variable local para cont ar desde 0 a 1 0 0 . Ca da vez que pa sa por el bucle contador, el código com pr ue ba si el número es par e xami nando si el número tiene resto al dividirse entre dos. Si el número es par. el código desencadena el evento OnEvenNumber y p ropor ci ona el número p a r como el ar gument o par a hacerlo coincidir con la lista de p ar á me tr os del delegado de eventos. C om o el método EvenNumber Found de MyEvenNumberHandlerClass e st ab a instalado como un cont rol ador de eventos y como ese método escribe el p ar ám et ro p ro por ci onado en la consola, si se compila y ejecuta el código del listado 15.1. se escriben en la consola los números pares entre 0 y 100.
Cómo estandarizar un diseño de evento Aunque C# ac ept a perfectament e cualqui er diseño de delegado que se pueda compilar. N E T F ramework prefiere que se adopte un diseño est ándar par a los delegados. El diseño de delegados preferido usa dos argumentos: por ejemplo, el
SystemEventhandler:
350
Una referencia al objeto que desencadenó el evento. Un objeto que contienen datos relacionados con el evento. El segundo par ámet ro, que contiene todos los datos del evento, debe ser un objeto de un clase que derive de una clase . NET llamada System. EventArgs. El listado 15.2 remodela el listado 15.1 usando este diseño preferido. Listad o 15.2. Cómo recuperar números impares con la convención de delegados NET using
System;
public delegate void EvenNumberHandler(object OnEvenNumberEventArgs Eve nN um berEventArgs); class
Origmator,
Counter
{ public
event
EvenNumberHandler
public
C o u n t e r ()
OnEvenNumber;
{ OnEvenNumber
= nuil;
} public
void
CountTolOO()
{ int
CurrentNumber;
for (CurrentNumber CurrentNumber++)
= 0;
CurrentNumber
<=
100;
{ if (CurrentNumber
2 == 0)
{ if (OnEvenNumber
!= nuil)
{ OnEvenNumberEventArgs
EventArg um en ts ;
EventArguments = new O n E v e n N u m b e r E v e n tA rg s( Cu rr en tN um b er ); OnEvenNumber(this, E ve nt Arguments);
public
class
OnEvenNumberEventArgs
: EventArgs
{ prívate public
int
EvenNumber;
OnE ve nN um be rE ve nt Ar gs (int
EvenNumber)
351
{ t h i s .EvenNumber
= EvenNumber;
} public
int Number
{ get
{ return
EvenNumber;
} } I c lass
EvenNumbe rHancllerClass
{ public void E ve n N u m b e r F o u n d (object Originator, OnRvenMurtibe rEventArgs EvenNumbe r Event Args )
{ Cons ole . W n t e L i n e (EvenNumbe rEventArgs .Numbe r ) ;
c 1a s s M a 1 11C 1a s s
< pu b ! i.c stdtic
voicl Main (!
I f:o i;n t e r MyCounter = new Counter i) ; EvenMumberHand1erClass MyEvenNumbe i Hancll e rClass - new tilve r,Uumbe rHand 1 e rC 1 a s s () ; 14yCount e r .OnEvenNumber + - new E ve nN um be rH an d1er (MyEvenNumbe rHandle r C 1 a s s .EvenNumber Found > ; M yC o u nter .C o u n t T o 1 0 0 ( ') ;
¡ El listado 15.2 añade una nueva clase llamada OnEvenNumberEventArgs que d e n \ a de la clase N E T EventArgs. Implementa un const ructor que toma un numero entero y lo al macena en una variable privada. Tambi én expone una propiedad de solo lectura llamada Number. que devuelve el valor de la variable privada. La firma del delegado también ha cambiado para cumplir con la nueva conven ción. Ahora acepta dos parámetros de entrada: una referencia al objeto que desen cadena el evento v un objeto de tipo OnEvenNumber Event A r g s . Cuando la clase Counter se pre para par a desencadenar el evento, antes crea un nuevo objeto de tipo OnEvenNumberEventArgs y lo inicializa con el numero impar. A continuación pasa este objeto al evento como segundo parametro. La nuev a implementacion del método EvenNumber Found exami na el se gundo parámetro, un objeto de clase OnEvenNumberEventArgs y escribe el valor de la propiedad Number del objeto en la consola.
352
Cómo usar descriptores de acceso de eventos En la implementación general de objetos, hay que tener un c ampo de eventos definido en la clase por cada evento posible que pueda desencadenar la clase. En ejemplos como el listado 15.2. en el que la clase solo desencadena un evento, definir un ca mpo de ev entos p ar a cada evento no es muy costoso. Sin embargo, este sistema es mucho más compl icado cuando la clase puede des encade nar uno de varios eventos. Tome, por ejemplo, una clase de C# que gestiona un componente de la mterfaz de usuario de Windows. Los componentes normales de la interfaz de usuario de Window s pueden recibir uno de los muchos mensajes posibles del sistema o p e r a tivo y podríamos querer diseñar nuestra clase para que se envíen a los usuarios de la clase, mediante un ev ento de C#. los mensajes que el componente del interfaz de usuario recibe del sistema operativo. Definir un ca mpo de evento en una clase para cada posible mensaje de Window s obligaría a la clase a a lmac enar una gran cantidad de campos y haría que tuviera un t amaño enorme C# admite una forma alternativa por la que los ev entos pueden definirse como propiedades, en lugar de como campos. Las propiedades de eventos funcionan exact ament e igual que las propiedades de clase estándar, que se implementan con código, en lugar de c a m pos de datos. A diferencia de una propiedad estándar, una propiedad de evento usa las palabras clave add y remove par a definir bloques de codigo: pub.Lic event EvenNumberHandler OnEvenNumber
{ add
{ } r emove
{ } ) El código del bloque de código add se invoca cuando un usuario añade un nuev o cont rol ador de ev entos al ev ento us ando el op er ad or + = . La ventaja de us ar descriptores de acceso de eventos es que tenemos total libertad en lo que respecta al modo en que almac enamo s los controladores de eventos. En lugar de definir c ampos s eparados par a cada evento, podemos a l m a cenar una sola lista enlazada o una matriz de controladores y podemos implementar el descriptor de acceso de eventos remove par a eliminar un controlador de even tos de la matriz o de la lista. Como en las propiedades estandar. podemos usar la palabra clav e de C# valué para hacer referencia al cont rol ador de evento que se añade o elimina, como se puede v er en la siguiente instrucción:
353
M y C o u n t e r .OnEvenNumber += new E v e n N u m b e r H a n d l e r( My Ev en Nu mb er Ha nd le rC la ss .E v e n N u m b e r F o u n d ) ;
En es t a i n s t r u c c i ó n , se a ñ a d e al eve nt o O n E v e n N u m b e r un nu ev o EvenNumberHandler objeto. Si implemcnt ásemos el evento como una p ro piedad. el bloque de código add podría usar la pal abr a clave add para hacer referencia al nuevo objeto EvenNumberHandler: public
event
EvenNumberHandler
OnEvenNumber
{ add
{ AddToList (valué) ;
} r emove
{ R em ov e F r o m L i s t ( v a l u é ) ;
} } C uand o se usa en descriptores de acceso de eventos, la p al ab ra clave valué es una variable de tipo Delegate.
Cómo usar modificadores de eventos Se puede anteponer a una declaración de un evento uno de los siguientes modi ficadores: Static Virtual Override Abstract
Eventos estáticos Los eventos modificados con la pal ab ra clave static se c omport an de forma parecida a los campos estáticos, en el sentido de que. aunque cada copia de una clase contiene copias s eparadas de todos los campos, sólo puede haber una copia de un mi embro estático en un moment o dado. Todos los objetos de la clase com parten los eventos estáticos. C uan do se les hace referencia, debe ser a través del nombre de la clase y no mediante el nombre del objeto, como se apreci a a conti nuación: public
{
354
class
Counter
public static event EvenNumberHandler OnEvenNumber;
//
. . .
} C o u n t e r .OnEvenNumber += new Ev en Nu mb er Ha nd le r( My E v e n N u m b e r H a n d l e r C l a s s .Ev e n N u m b e r F o u n d );
Como puede ver. debe hacer referencia al evento estático OnEvenNumber especificando el nombre de la clase y el n ombre del objeto.
Eventos virtuales Los eventos modificados con la p al ab ra clave virtual marc an cualquier descriptor de acceso add o remo ve como virtual. Los descriptores de acceso v irtuales en clases derivadas pueden ser r eemplazados.
Eventos de reemplazo Los eventos modificados con la p al ab r a clave override marc an cualquier descriptor de acceso add o remo ve como eventos de reempl azo add o remove con el mismo nombre en una clase base.
Eventos abstractos Los eventos modificados con la p al ab ra clave abstract marcan cualquier descriptor de acceso add o remove como abstracto. Los descriptores de acceso abstractos no proporci onan una implementacion propia: en su lugar, un evento de reemplazo de una clase derivada p ropor ci ona una implementacion.
Resumen Las clases pueden des encadenar eventos cuando un p rog rama pide a sus clien tes que le avisen de las acciones llevadas a cabo por la clase. Sin eventos, los usuarios llaman a un método p ar a realizar una operación, pero en realidad no saben lo av a nz ad a que está la operación. Imagine, por ejemplo, un método que recupera una página Web de un serv idor Web. Esa operación consiste en varios pasos: Conect ar con el servidor Web. Solicitar la pági na Web. Recuperar la pági na We b devuelta. Desconect ar del servidor Web.
355
Es posible diseñar una clase como esta con eventos que se desencadenen cuando comience cada una de estas acciones. Al des encadenar los eventos en los pasos críticos del proceso le da pistas a los usuarios de su clase sobre en qué parte del proceso se encuent ra el código. Los i n vo cado res r e sponden a los eventos re gi st ra n do mét odo s llamados controladores de eventos. Los controladores de eventos se invocan cuando una clase desencadena algún evento. Estos métodos se corresponden con la lista de par ámet ros y devuelven un valor de un método patrón especial llamado delegado. Un delegado describe el diseño de un co nt rol ador de eventos, indicando qué par ámet ro s debe admitir y cómo debe ser su código devuelto. Los controladores de eventos se instalan usando el o per ador +=. Los ex entos se declaran normal ment e como ca mpos públicos en una clase y los invocadores añaden sus controladores de ev entos a la clase creando un nuevo objeto de la clase delegado y as ignando el objeto al evento us ando el o per ador + = . C# permite especificar varios controladores de eventos p ar a un solo evento y también permite usar el oper ador - = p ar a eliminar un cont rol ador de eventos de un evento. C# no obliga a usar un único patrón de diseño p ar a los delegados, pero NET F ramewo rk recomienda un diseño. Usar el diseño recomendado p ropor ci ona un estándar que. cuando se respeta, puede dar a los delegados una lista de parámetros de método: un objeto que especifica los objetos que desencadenan el evento y un objeto de una clase derivada de la clase System. EventArgs. que contiene los ar gument os par a el evento. C # hace que sea muy sencillo i mplement ar eventos en las clases. En el nivel mas básico, cada evento se declara como un ca mpo público y se pueden gestionar tantos eventos como se desee. Si la clase va a gestionar varios eventos y el numero de ca mpos públicos dentro de la clase parece tener una cantidad excesiva de código, se pueden escribir descriptores de acceso de eventos que permitan contro lar el modo en que la clase gestiona los controladores de eventos. En lugar de definir c amp os públicos par a los eventos, los descriptores de acceso de exentos permiten definir los eventos como propi edades con bloques de código add y remove. El bloque de codigo add se invoca cuando se añade un controlador de exentos a un evento y el bl oque de código remove se invoca cuando se elimina un c ont rol ador de eventos de un evento. Estos bloques de código se pueden impicment ar al mac enand o los controladores de eventos en una matriz o en una lista par a ser usados posteriormente. El concepto de C# de ex entos y delegados es un concepto nuevo p ar a la familia de lenguajes C. Los eventos pueden ser desencadenados en C y C ++ u sando otros mecani smos, pero estos lenguajes no definen pal abr as clave par a hacer que fun cionen los ex entos. En C#. los exentos y delegados son elementos completamente definidos por sí mismos y tanto el lenguaje como el compi lador tienen compat ibi lidad integrada par a el control de exentos. Ot ra ventaja de us ar exentos en C# es que son completamente compatibles con el CLR. lo que significa que se puede p r e p a r a r un mecani smo de exento en
356
C# y desencadenar eventos que se controlen por otro lenguaje N E T Como el CLR admite eventos en el nivel de tiempo de ejecución, se puede des encade nar un evento en C# y controlarlo v procesarl o con otro lenguaje, como Visual Basic NE T
357
IQ Control de excepciones
Bus car errores y manejarlos adecuadament e es un principio f undamental par a diseñar software correctamente. En teoría, escribimos el código y cada línea f un ciona como pretendemos y los recursos que e mpl eamos siempre están presentes. Sin embargo, éste no siempre es el caso en el mundo real. Otros progr amadore s (por supuesto, no nosotros) pueden cometer errores, las conexiones de red pueden interrumpirse, los servidores de bases de datos pueden dejar de funcionar y los archivos de disco pueden no tener los contenidos que las aplicaciones creen que contienen. En pocas pal abras, el código que se escribe tiene que ser ca paz de detectar errores como éstos y responder adecuadamente. Los mecani smos p ar a i nformar de errores son tan diversos como los propios errores. Algunos métodos pueden estar diseñados par a devolver un valor booleano que indican el éxito o el fr acaso de un método. Otros métodos pueden escribir errores en un fichero de registro o una base de datos de algún tipo. La variedad de modelos de presentación de errores nos indica que el código que escri bamos par a controlar los errores debe ser bastante consistente. Ca da método puede i nformar de un error de un modo distinto, lo que significa que la aplicación estará repleta de gran cantidad de código necesario p a r a detectar los diferentes tipos de errores de las diferentes l lamadas al método. N E T F ramework propor ci ona un mecani smo estándar, llamado control de excepciones estructurado (SEH). p ar a informar de errores. Este mecanismo de
359
pende de las excepciones par a indicar los fallos. Las excepciones son clases que describen un error. N E T Framework las usa p ar a informar de errores y podemos utilizarlas en nuestro codigo. Puede escribir código que busque excepciones gene radas por cualquier fragmento de código, tanto si procede del C L R como si proce de de nuestro propio código y podemos ocup ar no s de la excepción generada adecuadament e. Usando SEH. sólo necesitamos crear un diseño de control de errores par a nuestro código. Esta metodología unificada del proceso de errores también es crucial para permitir la programaci ón N E T multilingiie. Al diseñar todo nuestro código u s an do SEH. podemos mezclar y co mp ar a r código (por ejemplo C#. C ++ o VB.NET). sin peligro al guno y fácilmente. Como premio por seguir las reglas del SEH. N E T Framework gar ant iza que todos los errores serán expuestos y controlados convenientemente en los diferentes lenguajes. El proceso de detección y gestión de excepciones en el código de C# es senci llo. Se deben identificar tres bloques de código cuando se t rabaj a con excepci o nes: El bloque de código que debe usar el proces ami ent o de excepciones Un bloque de código que se ejecuta si se e ncuentra una excepción mientras se procesa el pri mer bl oque de código Un bloque de código opcional que se ejecuta después de que se procese la excepción En C#. la generación de una excepción recibe el nombre de iniciación de una excepción. El proceso de i nformar de que se ha iniciado una excepción recibe el nombre de capturar una excepción. El fragment o de código que se ejecuta des pués de que se haya procesado la excepción es el bloque f i n a l l y . En este capítulo veremos como se usan estos constructores en C#. Tambi én estudiaremos a los mi embros de la jerarquía de las excepciones
NOTA: Un debate largo y recurrente de la comunidad de usuarios de soft ware orientado a objetos es si las excepciones deberían usarse en todos los errores (incluyendo los errores que uno espera que ocurran frecuentemente) o sólo para los errores graves (los conocidos como errores de excepción, que sólo ocurren cuando un recurso falla inesperadamente). El punto crucial de este debate es el relativamente importante encabezado necesario para iniciar y atrapar excepciones, encabezado que podemos evitar mediante el uso de otro método de control, como los códigos de devolución. La respues ta de .NET Framework a este conflicto es el uso del control de excepciones estructurado para todos los errores porque permite garantizar que todos los recursos se liberan adecuadamente cuando se produce un error. Esto es propio del consenso actual de este reñido debate. La investigación exhaus-
360
tiva (principalmente por parte de la comunidad de usuarios de C++) ha llegado a la conclusión de que evitar la pérdida de recursos sin excepciones es prácticamente imposible. Por supuesto, C# evita la pérdida de memoria con la recogida de elementos no utilizados, pero todavía necesitamos un mecanismo para evitar las pérdidas de recursos de los varios tipos de pun teros de operaciones del sistema. Como en todas las instrucciones de diseño, usar excepciones indiscrimi nadamente para todos los errores supone un uso excesivo de recursos. Cuando el error es local para un bloque de código, puede ser más apropiado usar códigos de devolución de error. Con frecuencia vemos este enfoque cuando se implementa una validación de formularios. Éste es un cambio aceptable porque los errores de validación suelen estar localizados en el formulario que recoge la entrada. En otras palabras, cuando ocurre un error de valida ción, se presenta en pantalla un mensaje y pedimos al usuario que vuelva a introducir correctamente la información requerida. Como el error y el códi go controlador están en el mismo bloque, controlar la pérdida de recursos es sencillo. Otro ejemplo es controlar una condición de fin de archivo cuan do se está leyendo un archivo. Esta condición puede ser controlada fácil mente sin usar el encabezado de excepciones requerido. De nuevo, la condición de error se controla completamente dentro del bloque de código donde ocurre el error. Cuando observe que se realizan llamadas al código fuera del bloque de código donde ocurre el error, debe tender a procesar los errores usando SEH.
Cómo especificar el procesamiento de excepciones La pal abr a clave de C# try especifica que hay un bloque de codigo que debe b us ca r cualquier excepción iniciada mientras se está ejecutando el código T r a b a jar con la pal abr a clave try es sencillo. Use la pal ab ra clave try seguida de una llave de apertura, de las instrucciones en las que se deben bu sc a r excepciones mientras se ejecutan y termine con una llave de cierre: try { // coloque
aquí
las
instrucciones
} Si se inicia una instrucción mientras se esta ejecutando cualquiera de las ins trucciones del bloque try. se puede ca pt ur a r la excepción en el código y o c u p a r se de ella adecuadamente.
361
Cómo capturar excepciones Si se usa la pal abr a clave try p ar a especificar que desea ser i nformado sobre las excepciones iniciadas, es necesario escribir código que at rape la excepción y se ocupe de los errores que envía el código. Par a indicar el código que debe ejecutarse cuando se at rape una excepción se usa la p al abr a clave de C# catch después de un bloque try. La pal abr a clave catch funciona de for ma pareci da a la p al ab ra clave try.
Cómo usar la palabra clave try La for ma más simple del bl oque de código catch at ra pa todas las excepcio nes iniciadas por el código en el anterior bloque try. El bloque catch tiene la mi sma est ructur a que el bloque try, como se puede ap reci ar en el siguiente ejemplo: try { // coloque
aquí
las
instrucciones
aqui
las
instrucciones
} catch
{ // coloque
} Las instrucciones del bloque catch se ejecutan si se inicia una excepción desde el bl oque try. Si ninguna de las instrucciones del bloque try inicia una excepción, entonces no se ejecuta nada del código del bloque catch.
Cómo atrapar clases especificas de excepciones Tambi én se puede escribir un bloque catch que controle una clase específica de excepción iniciada por una de las instrucciones del bloque try. Esta forma del bl oque de código catch usa la siguiente sintaxis: La p al abr a clave catch Un paréntesis de apert ura La clase de excepción que desea controlar Un identificador de variable p ar a la excepción Un paréntesis de cierre Una llave de ap ert ura
362
Las instrucciones que deben ejecutarse cuando se inicia una excepción del tipo especificado desde el anterior bl oque try •
Una llave de cierre
Observe el siguiente código: try { // coloque
aquí
las
instrucciones
} catch(Exception
thrownException)
{ // coloque
aqui
las
instrucciones
} El bloque catch de este ejemplo a t r a pa excepciones de tipo Exception que inician el anterior bloque try. Define una variable del tipo Exception llamada ThrownException. La variable ThrownException puede usarse en el código del bl oque catch par a obtener más información sobre la excepción iniciada. El código del bloque try puede iniciar diferentes clases de excepciones y quere mos controlar cada una de las diferentes clases. C# permite especificar varios blo ques catch. cada uno de los cuales controla una clase de error específica: try
{ // coloque
aquí
las
instrucciones
} catch(Exception
ThrownException)
[ // Bloque
catch
1
} catch(Exception
ThrownException2)
{ / / Bloque
catch 2
} En este ejemplo, se revisa el código del bloque try en b us ca de excepciones iniciadas. Si el C L R descubre que el código del bloque try inicia alguna ex c ep ción. exami na la clase de la excepción y ejecuta el bloque catch adecuado. Si la excepción iniciada es un objeto de clase Exception. se ejecuta el codigo del Bloque catch 1. Si la excepción iniciada es un objeto de alguna otra clase, no se ejecuta ninguno de los bloques. Tambi én se puede añadir un bloque catch genérico a la lista de bloques de códi go catch. como en el siguiente ejemplo: try
{ // coloque
aquí
las
instrucciones
363
} catch(Exception
ThrownException)
{ // Bloque
catch
1
} catch
{ // Bloque
catch 2
) En este caso, se revisa el código del bloque try en b us ca de excepciones. Si la excepción iniciada es un objeto de clase Exception. se ejecuta el código de Bloque catch 1. Si la excepción iniciada es un objeto de al guna otra clase, se ejecuta el código del bloque genérico catch (Bloque catch 2).
Cómo liberar recursos después de una excepción Los bloques catch pueden ir seguidos por bloque de código. Este bloque de código se ejecuta después de que se procese una excepción y cuando no ocurre ninguna excepción. Si se quiere ejecutar este código, se puede escribir un bloque fina 11 y. La palabra clave de C# finally especifica que hay un bloque de codigo que debe ejecutarse después de que se ejecute un bloque de código try. Un bloque de código final ly tiene el mi smo for mato que los bloques try: f i n a 11 y
{ // coloque
aquí
las
instrucciones
} El bloque de código f inally es un buen sitio donde liberar recursos a los que se habí a colocado en el método con anterioridad. Suponga, por ejemplo, que estamos escribiendo un método que abre tres archivos. Si encerramos el código de acceso a archivos en un bloque try. podremos a t r a pa r excepciones relacionadas con la apert ura, lectura o escritura de esos archivos. Sin embargo, al final del código, querremos cerrar los tres archivos, aunque se haya iniciado una excep ción. Probabl emente quer amos colocar las instrucciones de cierre de archivos en un bloque f inally y podremos es tructur ar el código como se indica a continua ción: try ( // abrir archivos // leer archivos
} c atch
{ //
364
atrapar
excepciones
} finally
{ // cerrar
archivos
} El compi lador de C# permite definir un bloque finally sin ningún bloque catch. Se puede escribir un bloque finally inmedi at ament e después de un bl oque try.
La clase exception Todas las excepciones iniciadas por N E T F ramework son clases derivadas de la clase Sys t e m .Except ion. La tabla 16.1 describe algunos mi embros utiles de esta clase. Tabla 16.1. Miembros de la clase S y ste m .Exception Class
M iem b ro
D escrip ción
HelpLink
Un vínculo al archivo de ayuda que proporciona más información sobre la excepción
Message
El texto que se ha proporcionado, normalmente como parte del constructor de la excepción, para describir la condición del error
Source
El nombre de la aplicación u objeto que provocó la excepción
StackTrace
Una lista de las llamadas al método en la pila
TargetSite
El nombre del método que inició la excepción
Introducción a las excepciones definidas por .NET Framework N E T F ramework define varias excepciones que pueden iniciarse cuando se encuent ran ciertos errores en el código de C# o en los métodos que se pueden invocar. Todas estas excepciones son excepciones est ándar de N E T y pueden ser at rapadas usando un bloque catch de C#. Cada una de las excepciones de N E T se define en el espacio de nombre System de NET. Los siguientes apa rt ad os describen algunas de las excepciones mas comunes. Estas excepciones son sólo una pequeña parte de todas las que hay definidas en la biblioteca de clases base de N E T Framework.
365
OutOfMemoryException El C L R inicia la excepción OutOfMemoryException cuando agota su memoria. Si el código intenta crear un objeto us ando el o per ador new y el CLR no d i s p o n e de s u f i ci e nt e m e m o r i a p a r a ello, el C L R ini ci a la e x c ep ci ó n OutOfMemoryException. mos trada en el listado 16.1. Listado 16.1. Excepción O utOfMemoryException using
System;
class
MainClass
{ public
static void M a i n ()
{ int
[] LargeArray;
t ry
{ LargeArray
= new int
[2000000000];
} catch (OutOfMemoryException)
{ C o n s o l é . W r i t e L m e ("The CLR
ís
out
of memo r y ." ) ;
} ) } El codigo del listado 16.1 intenta asi gnar un espacio a una matriz de dos mil millones de números enteros. Dado que un número entero requiere cuatro bytes de memoria, se necesitan ocho mil millones de bytes par a contener una matriz de este tamaño. Es bastante probable que su ordenador no disponga de esta cantidad de memori a y de que la asignación falle. El codigo encierra a la asignación en un bloque try y define un bloque catch p a r a que controle cualquier excepción OutOfMemoryException iniciada por el CLR.
NOTA: El código del listado 16.1 no escribe un identificador para la ex cepción del bloque catch. Esta sintaxis (en la que se especifica la clase de una excepción pero no se le da nombre) es válida. Funciona perfectamente cuando se quiere atrapar una clase de excepciones pero no se necesita infor mación del propio objeto específico de la excepción.
StackOverflowException El C L R inicia la excepción StackOverflowException cuando agota el espacio de la pila. El C L R gestiona una estructura de datos llamada stock, que
366
registra los métodos que han sido llamados y el orden en el que fueron llamados. El C L R tiene una cantidad limitada de espacio de pila disponible y si se llena, se inicia la excepción. El listado 16.2 mues tra la excepción StackOverflow-
Exception. Listado 16.2. Excepción S t a c k O v e r f l o w E x c e p t i o n us m g
S ystem;
class
MamClass
{ public
s tat ic
v o id
M a i n ()
{ tr y
{ Re cur s ive () ;
} catch(StackOverflowException)
{ Consolé.WriteLine("The
CL R
is
out
of
stack
space.");
} } pu b l i c
st atic
v oi d
R e c u r s i v e ()
{ R e c u r s i v e () ;
} } El código del listado 16.2 i mplementa un método llamado Recursive (). que se llama a sí mi smo antes de regresar. Este método es llamado por el método Main ( ) y con el tiempo, hace que el C L R agote su espacio de pila porque el m é t o d o R e c u r s i v e ( ) n u n c a r e g r e s a . El m é t o d o M a i n ( ) l l a m a a Recursive ( ) . que a su vez llama a Recursive ( ) , que a su vez llama a Recursive ( ) y así sucesivamente. A la larga, el C L R se queda rá sin espacio de pila e iniciará la excepción StackOverf lowException.
NullReferenceException En este ejemplo, el compi lador a t ra p a un intento de eliminar la referencia de un objeto nuil. El listado 16.3 m ue s t r a la excepci ón N u l l R e f e r e n c e
Exception. Listado 16.3. Excepción NullReferenceException us i n g
System;
class
MyClass
( pu b l i c
int
Valué;
367
class
MamClass
{ public
static void M a i n !)
{ try { M yObject
= n e w M y C l a s s ();
MyObject
= null;
MyObject.Value
=
12 3;
// espere a que el usuario reconozca los resultados Console.WriteLine ("Hit Enter to terminate..."); C o n s o l e .R ea d () ;
) catch (Null Referenceszception)
{ Console.WriteLine("Cannot / /e s pe r e a que el C o n s o l e .R e a d () ;
reference
usuario
r e co n oz c a
a null los
object.");
r es u ltados
}
El codigo del listado 16.3 declara una variable de objeto de tipo MyC las s \ asigna a la variable el valor de nuil. (Si no se usa la instrucción new. sino que sólo se declara una variable de objeto de t i p o M y C l a s s . el compi lador emitirá el siguiente mensaje de error cuando se compile. "Uso de la variable local no asigna da MyO b j e c t .") A continuación intentará t raba jar con el campo público Valué del objeto, lo que no está permitido porque no se puede hacer referencia a objetos nuil. El C L R a t r a p a este er r or e inicia la excepci ón Nul IRe f e r e n c e Ezception.
TypelnitializationException El C L R inicia la excepción T y p e l n i t i a l i z a t i o n E x c e p t i o n cuando una clase define un const ructor estático y el const ructor inicia una excepción. Si no hay bl oques catch en el constructor par a at ra p ar la excepción, el C LR inicia una excepción T yp e ln i ti a l i z a t i o n E x c e p t i o n .
InvalidCastExpression El C LR inicia la excepción I n v a l i d C a s t E x p r e s s i o n si falla una con versión explicita. Esto puede ocurrir en contextos de interfaz. El listado 16.4 muest ra una excepción Invalid Ca st Ex pr es sio n.
368
Listado 16.4. E x c e p c i ó n
inval idCastExcept.i o¡
u sin g S y s t e m ; class
Main Cías s
i public
static
voicl Ma 1 n ()
{ t ry
{ M a m C l a s s MyObject = nev/ M a m C l a s s !) ; IFormattable Formattable; Formattable
=
(IFormat tablej M y O b j e c f ;
// espere a que el usuario reconozca los resultados Consolé . W n t e L m e ("Hit Enter to termínate ..." i ; C o n s o l é .R e a d ();
} catch (InvalidCast E x c e p t i o n 1
{ Consolé . W r i t e L m e ("MyObject T Fo rmat t able interface ." 1 ;
cloes not
implement
// espere a que C o n s o l é .R e a d ();
reconozca
los
el
usuario
t.hc
resuitados
) } } El codigo del listado 16.4 usa un operador de conversión de tipo explícito para obtener una referencia a una interfaz N E T llamada IFormattable. Como la clase M a m C l a s s no ímplementa la interfaz IFormattabl e. la operaeion de con ve rs ió n expl íci ta falla y el C L R inicia la e xc epci ón Inval idCast-
F c ep t io n .
ArrayTypeMismatchException El C L R inicia la e x c e p c i ó n ArrayTypeMismatchException c u a n d o el c ó d i g o intenta a l m a c e n a r un e l e m e n t o en una mat r i z c u n o t ipo no c o i n c i d e con el t i po del e l eme n t o .
IndexOutOfRangeException El C LR inicia la excepción IndexOutOfRangeException cuando el codigo intenta a l ma c en ar un elemento en una mat ri z empl eando un Índice de elemento que esta fuera del rango de la matriz. El listado 16.5 describe la excepción
IndexOutOfRangeException.
369
Listado 16.5. E x c e p c i ó n using
System;
class
MamClass
IndexOutOf R angeException
{ public
static
void M a i n ()
{ try
{ int
[]
IntegerArray - new int
In te ge rA rr ay [10]
=
[5];
123;
// espere a que el usuario reconozca los resultados Console.WriteLine ("Hit Enter to te r m i n a t e ..."); C o n s o l e .Read () ;
} c a t c h (IndexOutOfRangeException)
{ Console.WriteLine("An a 11 empt e d .") ;
invalid
// espere a que el usuario Cons o 1e .R e a d () ;
element
reconozca
index
los
access
was
resultados
El código del listado 16.5 crea una matriz con cinco elementos y a cont inua ción intenta as ignar un valor al elemento 10 de la matriz. C omo el indicc 10 está fuera del rango de la matriz de números enteros, el C L R inicia la excepción
IndexOutOfRangeException.
DivideByZeroException El C L R inicia la excepción DivideByZeroException cuando el código intenta realizar una operación que da como resultado una división entre cero.
OverflowException El C L R inicia la excepción OverflowException c uando una operación mat emát ica guar dad a por el operador de C# checked da como resultado un desbordamiento. El listado 16.6 mues tra la excepción OverflowException. L istad o 16.6. Excepción OverflowException
370
using
S ys t em;
class
MainClass
public
static void M a i n ()
{ t ry
{ checked
{ int Integerl; i n t Integer2; int Sum; Integerl = 2000000000; Integer2 = 2000000000; Sum = Integerl + Integer2;
} // espere a que el usuario reconozca los resultados C o n s o l e . W n t e L m e ("Hit Ent e r t o t e r m m a t e ..." ) ; Consolé.Read () ;
} catch(OverflowException)
{ C o n s o l e . W n t e L m e ("A mathematical ove r i 1o w .") ; // espere a que el usuario Consolé.Read () ;
operation
reconozca
los
caused
an
resultados
} } } El código del listado 16.6 suma dos números enteros, cada uno con un valor de dos mil millones. El resultado, cuatro mil millones, se asigna a un tercer numero entero. El probl ema es que el resultado de la s uma es mayor que el v alor máxi mo que se puede asignar a un número entero de C# y se inicia una excepción de des bo r da miento matemático.
Cómo trabajar con sus propias excepciones Puede definir sus propias excepciones y usarlas en su codigo del mismo modo que haría con una excepción definida por N E T Framevvork. Esta consistencia en el diseño le permite escribir bloques catch que funcionen con cualqui er e x c e p ción que pueda iniciar cualquier fragmento de codigo. tanto si el codigo pertenece a N E T Framevvork. como si pertenece a una de sus propias clases o a un en s am blado que se ejecuta en tiempo de ejecución.
371
Cómo definir sus propias excepciones N E T Framework declara una clase llamada S y s t e m .Exception. que sir ve como clase base par a todas las excepciones de N E T Framework. Las clases p r e d e f i n i d a s del e n t o r n o c o m ú n de e j e c u c i ó n se d e r i v a n de S y s t e m . Sys temExcept ion. que a su vez deriva de S y s t e m .Except ion. La ex cepci ón a est a regla son las ex ce pc io ne s D i v i d e B y Z e r o E x c e p t i o n . Not Fi ni teNumber Except ion y Over f lowExcept ion. que derivan de una clase l l am ada System. A r i t h m e t i c E x c e p t i o n . que de r iv a de System. SystemException. Cual qui er clase de excepción que defina debe derivar de System. A p p l icationException. que t a mb i én deriva de
System.Ezception. La clase System. Except ion contiene cuatro propiedades de sólo lectura que el codigo de los bloques catch puede u sar par a obtener más información sobre la excepción que se ha iniciado: La propiedad Message contiene una descripción de la c aus a de la excep ción. •
La propiedad InnerException contiene la excepción que ha p r ovo ca do que se inicie la excepción actual. Esta propiedad puede ser nuil, lo que i n d i c a r i a q u e no h a y n i n g u n a e x c e p c i ó n i n t e r i o r d i s p o n i b l e . Si InnerExcept ion no es nuil, hace referencia al objeto de excepción que se ha iniciado y que provocó que se iniciase la excepción actual. Un bloque catch puede a t r a pa r una excepción e iniciar otra diferente. En ese caso, la propiedad I nner Except ion puede contener una referencia al objeto de excepción original a tr a pa do por el bloque catch. La propiedad StackTrace contiene una cadena que muest ra la pila de ll amadas de método que es taba en vías de ejecución cuando se inició la excepción. En última instancia, este rastro de pila puede contener todo el recorrido hasta la llamada al método Main ( ) de la aplicación del C LR La propiedad TargetSite contiene el método que ha iniciado la excep ción.
Algunas de estas propiedades pueden especificarse en uno de los constructores de la clase S y s t e m .Except ion: public public
E x c e p t i o n (string message); Ezception ( s t n n g message, Exception
innerException);
Las excepciones definidas por el usuario pueden llamar al constructor de clase base en su const ructor de modo que se pueden as ignar valores a las propiedades, como mues tra el siguiente codigo:
372
using
System;
class
MyException
: A p p l 1 cat 1 onExcept 1 on
{ public M y E x c e p t i o n () : base ("This
is my exception message.
Este código define una clase l lamada MyException. que deriva de la clase ApplicationException. Su constructor usa la palabra clave base para llamar al constructor de la clase base. La propiedad Message de la clase recibe el valor This is my exception message.
Cómo iniciar sus excepciones Puede iniciar sus propias excepciones mediante la pal abra clave de C# throw. La pal abr a clave throw debe ir seguida por una expresión que evalúa un objeto de clase System. Exception o una clase derivada de .System. Exception. Observe el codigo del listado 16.7. L istado 16.7. C ó m o iniciar s u s p r o p i a s e x c e p c i o n e s using
System;
class
MyException
: A p p 1 1 cat ionExcepti on
{ public M y E x c e p t i o n ()
class
: base("This
is my
exception
message.")
MamClass
{ public
static
void M a i n ()
{ try
{ MamClass
MyObject
= new M a m C l a s s () ;
M y O b ]e c t .T h r o w E x c e p t i o n (); // espere a que el usuario reconozca los resultados C o n s o l é . W r i t e L m e ("Hit Enter to terminate ..."); C o n s o l é .Read () ;
} catch(MyException
Ca ughtException)
{ Consolé . W r i t e L m e (CaughtException.Message) ;
373
// espere a que el usuario C o n s o l é . R e a d ();
reconozca
los
resultados
} } public
void
T h r o w E x c e p t i o n ()
{ throw new M y E x c e p t i o n () ;
} } El código del listado 16-7 declara una clase new llamada MyException. que deriva de la clase base ApplicationException definida por NET Framework. La clase MainClass contiene un método ll amado ThrowException. que inicia un nuevo objeto de tipo MyException. El método es invocado por el mét o do Main ( ) . que encierra la llamada en un bloque tr y. El método Main ( ) también contiene un bloque catch. cuya i mplementación escribe el mensaje de la excepción en la consola. C omo el mensaje se estableció c uando se construyó el objeto de la clase MyException. está disponible y se puede escribir. Si se compila y ejecuta el listado 16.7 se escribe lo siguiente en la consola: This
ís my exception message.
Cómo usar excepciones en constructores y propiedades Algunos constructores de C# contienen código que se puede ejecutar, pero no pueden devolver un valor que indique el éxito o fr aca so del código que se está ejecutando. Los constructores de clase y los descriptores de acceso de p ropi eda des set son un ejemplo claro. Iniciar excepciones es un buen modo de informar de errores de bloques de codigo c omo estos. En un capítulo anterior e xa mi na mos una clase que implement aba un punto en la pantalla. La clase tenia propiedades que representaban las coordenadas v e v del punto y los descriptores de acceso set para las propiedades garant izaban que el valor era valido antes de que se al mac enar a realmente. El probl ema con el codigo del listado 1 es que no hay un informe de errores en caso de que se proporcione un valor que este fuera de los límites permitidos. El listado 16.8 es una versión mejorada del listado 1 porque añade control de excepciones para informar de coor denadas que están fuera de los límites permitidos. L istado 16.8. C ómo iniciar excepciones desde descriptores de acceso de propiedades using
System;
public cías s Coorclinat.eOut.Of RangeExcept. ion A pp li ca ti onException
374
:
{ public CoordinateOutOfRangeException () coordinate is out of range.")
: base ("The
supplied
{ }
public
class
Point
{ private private public
mt mt mt
XCoordmate; YCoordinate; X
{ get
{ return
XCoordinate;
} set { if ( (value >= 0) & & (value < 6 40)) XCoordinate = value;
else throw
new
CoordinateOut Of Ra ng eE xc e pt io n();
} ) public
mt
Y
{ get
{ return
YCoordinate;
} set
{ if ( (value >= 0) && (value < 48 0) ) YCoordinate = value;
else throw
new
CoordinateOutOfRangeExceptlon () ;
) } public
static void M a i n ()
{ Point M y P o m t
= new Point () ;
try { M y P o m t . X = 100; MyPoint.Y = 200; Console .WriteLine (" ((0) , M y P o m t . X = 1500 ; MyPoint.Y = 600;
{1})",
M y P o m t .X , M y P o m t .Y) ;
375
C o n s o 1 e . Wr 1 1 e L i n e ( " ( { 0 } ,
{ 1} ) " ,
M yPom t.X,
M y P o m t . i'l
// espere a que el usuario reconozca los resultados Console.WriteLine("Hit Enter to termínate..."); C o n s o ie .P e a d () ;
} catch ¡Coorci inateOutOfP.angeRxcept ion
Caught.Exception!
{ C onso le .Wr if el.i ne (Ca ught Except ion .Me s s a ge ) ; // espere a que C o n s o 1 e .P e a d t ) ;
e i usuario
reconozca
los
resultados
} C a t r :h
{ Consoie.WrifeTiine ¡"An unexpected caught .") ; // espere a que el C o 11 s o !e .P e1a d í} ;
usuario
except i on wa s
reconozca
los
resultados
1 } } El código del listado 16.8 comp rue ba el valor de los descriptores de acceso de la propiedad set para gar ant izar que el valor proporci onado está dentro de los límites validos. En caso contrario, se inicia una excepción. La asignación del primer punto tiene éxito ya que los dos valores están dentro de los límites validos. Sin embargo, el segundo punto no tiene éxito, ya que la c o o rd en ad ax está fuera de los límites válidos. Este valor fuera de los limites validos hace que se inicie un ob| et o de clase Coorci inateOutOf RangeEi-icept ion. Si se compila y ejecuta el listado 16.8 se escribe lo siguiente en la consola: (1 0 0, 20 o ) The supplied
coordínate
rs out
oí
range.
Resum en N E T Framevvork usa las excepciones p ar a i nformar de diferentes errores a las aplicaciones NET. El lenguaje C# admite perfectamente el tratamiento de las excepciones y permite al usuario definir sus propias excepciones, ademas de t ra baj ar con las excepciones definidas por N E T Framevvork. El codigo de C# puede iniciar y a t r a pa r excepciones. Tambi én puede at ra p ar excepciones iniciadas por N E T Framevvork La ventaja de usar excepciones reside en que no es necesario co mp ro ba r cada llamada de método en busca de un error. Se puede encerrar un grupo de llamadas de método en un bl oque try y se puede escribir código como si cada llamada de
376
método del bloque tuviera éxito. Esto hace que el codigo del bloque t r y sea mucho mas limpio porque no necesita ninguna comp rob aci ón de errores entre líneas. Cual qui er excepción iniciada desde el codigo del bloque t r y es g es ti ona da en un bloque c a t c h .
377
Cómo trabajar con atributos u17 vi
Los capítulos anteriores estaban dedicados a las pal abr as clave que definen el comport ami ent o de una clase y sus miembros. Por ejemplo, las p al abr as clave public. prívate, protected e internal definen la accesibilidad de la declaración para otras clases del código. Estos modificadores están implementados por pal abr as clave predefinidas cuyo significado está integrado en el lenguaje C# y no puede ser cambiado. C# también permite mej orar las declaraciones de clase y de mi embro de clase mediante i nformación que es i nterpretada por otras clases de C# en tiempo real. Esta información se especifica usando un constructor llamado atributo. Los atribu tos permiten incluir directivas en las clases y en sus miembros. El comp or ta mi en to del at ri bu to se define por el código que escri bimos o por el codigo que proporci ona N E T Framework. Los atributos permiten ampliar el lenguaje C# mediante la escri tura de clases de at ri but o que mejoran el c omport ami ent o de o t ra s cl as es c u a n d o se e j e c u t a el códi go , a u n q u e e s c r i b a m o s la cl as e de implementación de atributo antes de que los otros usuarios apliquen el atributo a sus propias clases. Al compi lar aplicaciones, la información del atributo que se añade es enviada a los met adatos del ensamblado, lo que permite que otras aplicaciones o he r ra mientas vean que se está usando el atributo. Mediante el des ens ambla dor IL ( I L D A S M ) o las clases del espacio de nombres S y s t e m .Reflection se pue-
379
de c omp ro ba r fácilmente que atributos se han añadido a las secciones de codigo y se puede determinar si son útiles. En C#. se pueden usar dos tipos de atributo: los que están integrados en el lenguaje y los atributos personalizados, que podemos crear. En la primera parte de este capítulo, aprenderemos a usar atributos y exa mi naremos algunos de los atributos integrados que nos ofrece C#. En la segunda parte, aprenderemos a escribir atributos personalizados y el modo en que las aplicaciones pueden sacar partido de estos atributos.
Atributos C# permite que los atributos se antepongan a los siguientes constructores de CU: •
Clases. Miembros de clase, incluyendo constantes, campos, métodos, propiedades, eventos, indizadores. sobrecargas de operador, constructores y dest ructo res. Estructuras Interfaces. Mi em br os de interfaces. i ncluyendo métodos, p ropi edades , eventos e indizadores.
•
Enumeraciones y mi embros de enumeraciones. Delegados.
Para especificar un atributo en el código se escribe su nombre entre corchetes. La especificación de at ri but o debe apa rec er antes de la declaración en la que se debe aplicar el atributo. Los atributos mas simples pueden tener este aspecto: [MyAt t n bu t e ]
Un buen ejemplo de un atributo simple es el modelo de s ubprocesos que se usa par a crear una aplicación de consola en C#. Observe en el siguiente fragmento de código el a tri but o [ STAThread J aplicado a la función Main ( ) . Este atributo indica al c ompi lador que la función Mai n ( ) debe introducir un ap art ament o de un único s ubproc es o (STA) C O M antes de que se ejecute algún codigo basado en C OM / / -'s urrond r y > // K 1 punto de entrada principal // < /summary> STAThreadj static v o id Main f s t n n g [] a r g s )
380
de
la aplicación.
// // TODO:
Añada
el codigo para
iniciar
la aplicación
aquí
// ) Tambi én se puede ant eponer a un at ri but o un modi fi cador que defina el ele mento de C# al que se aplica el atributo. El modificador de atributo se escribe antes del nombre del atributo y va seguido de dos puntos. Esto recibe el nombre de enl azar un atributo. La tabla 17.1 enumera los tipos de declaraciones y los ele mentos a los que hace referencia par a los atributos específicos. Los elementos a los que las clases de atributo hacen referencia están predefinidos: por ejemplo, si una clase N E T contiene una clase de at ri but o que sólo hace referencia a e nu m e raciones. el atributo sólo puede ser usado par a enumeraciones y no puede a pl i ca r se a otros constructores del código C#. como clases y estructuras. Más adelante aprenderá a especificar los destinos de los atributos para sus clases de atributo personalizadas. Tabla 17.1. Listado de destinos de atributos
D ecla ración
D estin o
Assembly
Assembly
Module
Module
Class
Type
Struct
Type
Interface
Type
Enum
Type
Delegate
Type (por defecto) o Return Value
Method
Method (por defecto) o Return Value
Parameter
Param
Field
Field
Property - Indexer
Property
Property - Get Accessor
Method (por defecto) o Return Value
Property - Set Accessor
Method (por defecto), Param or Return Value
Event - Field
Event (por defecto), Field o Method
Event - Property
Event (por defecto), Property
Event - Add
Method (por defecto) o Param
Event - Remove
Method (por defecto) o Param
381
Por ejemplo, par a enl azar explícitamente un atributo a un método, se debe escribir algo parecido a esto: [me t h o d :MyAt tribute] int M y M e t h o d ()
{ } Los modificadores de atributo son útiles en las situaciones en las que su enlace puede ser ambi guo, como m ues tra el siguiente ejemplo: [MyAttribute ] int MyMethod ()
{ ( En realidad, este ej empl o no es muy ac la rat ori o. ¿Se apl i ca el atributo
MyAt tribu te al método o a su tipo d evuelto9 Si se especifica explícitamente el enlace, como se muest ra en el anterior ejemplo, se indica al compilador de C# que el at ri but o se apl ica a todo el método. En el ejemplo en el que el atributo [ STAThr ead ] se aplica a la función Main ( ) cuando se crea una aplicación de consola, se puede hacer la siguiente modificación p ar a hacer más evidente el enlace: /// <'summary> /// El punto de entrada principal / / / ''/s umma r y > [method: STAThread] static v o id Main Istring[] a rg s )
de
la aplicación.
{ // // TODO:
Añada
el codigo para
iniciar
la aplicación
aquí
// } Algunos atributos están construidos para acept ar parámetros. Los parámetros de at ri but o siguen al nombre del atributo y están entre paréntesis. Los paréntesis están a su vez entre corchetes. Un atributo con un par ámet ro puede tener este aspecto: [MyAttribute ÍParameter) ]
Ahora que tiene un conocimiento básico de la sintaxis de los atributos, pode mos ex ami na r las clases de atributo integradas que ofrece .NET. Observe que las clases de atributos funcionan en todos los lenguajes, de modo que. aunque escriba atributos p ar a los tipos de C#. la información de atributo puede ser usada por Visual Basic NET. JScript N E T y todos los lenguajes orientados al entorno común de ejecución (CLR). El objetivo del uso de at ri but os es aument ar la funcionalidad del lenguaje.
382
Cómo trabajar con atributos de .NET Framework N E T F ramework p ropor ci ona cientos de atributos predefinidos integrados. No son fáciles de encontrar, ya que el S DK no propor ci ona una lista con cada at ri but o en orden alfabético. Dependiendo de las clases que estemos usando, los atributos pueden derivar de la clase S y s t e m .Attribute y estos atributos pueden ser objetos específicos. Por ejemplo, cuando se t rabaj a con la función de N E T Fr amewor k que permite que el código N E T interactúe con el código her e dado C O M (lo que se conoce como mt eroperabi lidad C OM ). se pueden emplear s ob re los m o d i f i c a d o r e s m ás de 20 cl as es de a t r i b u t o s , des de el a t r i b u t o ComAliasName hast a el atributo TypeLibType . El siguiente código muest ra el atributo DllImportAttribute y le da una idea de c ómo l lamar a métodos externos en las DLL de Wi n32 desde C#. ñamespace
S y st em .R un ti me .InteropServices
{ [AttributeUsage (AttributeTargets.Method) ] public class DllImportAttribute: S y s t e m .Attribute
{ public public public public publ íc public public public
DllImportAttribute (string dllName) { . . .} C a l l m g C o n v e n t i o n Cal 1 m g C o n v e n t ion ; CharSet CharSet; string EntryPoint; bool E x a c t S p e l l m g ; bool PreserveSig; bool Se tL astError; string Valué { get {...} }
} } Sin atributos, no sería posible informar al compi lador de C# del modo en el que pretendemos usar un método específico en una DLL externa y si el lenguaje C# inclín ese esta funcionalidad en el lenguaje base, no sería lo suficientemente generica como p ar a poder ejecutarse en otras plataformas. Con la posibilidad de llamar a componentes Wi n32 a través de todas las plataformas, se obtiene control sobre qué propiedades usar, en su caso, cuando se llama a métodos externos. Como N E T Framework cuenta con tal cantidad de clases de atributos, es imposible describir ca da una de ellas en un solo capítulo. Además, como las clases de atributo son específicas de las clases en las que se definen, sólo son útiles en el contexto de esas clases. A medida que codifique aplicaciones y se vaya familiarizando con los espacios de nombres de N E T Framework par a los que esta codificando, las clases de atributo asociadas a los espacios de nombre se harán más transparentes. Algunas clases de atributo r eservadas pueden funcionar por sí mismas y afectar directamente al lenguaje C#. Las clases Syst e m .Obsolete-
383
A t t r ibutc. S y s t e m . S e r i a l i z a b l e A t t r i b u t e y System. Condi 1 1 o na iAt tribute son clascs de atributo que pueden usarse indepen dientemente y que afectan directamente al resultado del código.
NO TA: En NET Framework, las clases de atributo tienen alias, por lo que, cuando se usan clases de atributo, es normal ver el nombre de la clase de atributo sin la palabra "Attribute" a continuación. El sufijo está im plíci to, de m odo que la form a corta no produce un error. Por ejem plo, ObsoleteAttribute puede usarse com o Obsolete, ya que los atri butos están entre llaves, lo que hace evidente que son atributos y no algún otro tipo de m odificador.
Obser vemos algunas de las muchas clases de atributo disponibles en NET Framework. De esta forma, se a cos tumb ra ra al modo de t rabaj ar de estas clases y al modo de aplicar estos atributos en el codigo de CU.
System. Diagnostics. ConditionalAttribute El atributo C o n d i t i o n a l es el alias de System. Diag n o s t i c s . Cono i t io na iAttribute. que solo puede aplicarse a declaraciones de metodo de clase. Especifica que el método solo debe ser incluido como una parte de la clase si el compi lador de CU define el símbolo que ap arec e como p ar ámet ro del atributo. El listado 17.1 muest ra el funcionamiento del atributo Conditional. L istad o 17.1. C ómo trabajar con el atributo Conditional using using
System; System.rnaqnostics;
p u b 1 1 c c 1 a s s TestClass
{ p ubi i c vo ici Method i (i
{ Consol e . W r i t e L m e !"Hello
from Method!!") ;
} [C ondit io nal (" PE BU G " ) ] p u b 1 1 c v o id M e t h o d 2 ()
I Conso l p .W c i t e L i n e ("Hello
from M e t h o d 2 !");
I p u b11c void M e t h o d 3 ()
( Console .Wri t e L m e !"Hello
384
from
Me t h o d 3 !" ) ;
class
MainClass
{ public
static void M a i n ()
{ TestClass
MyTestClass
= new TestClass () ;
M y T e s t C l a s s .Method 1 () ; M y T e s t C l a s s . M e t h o d 2 (); M y T e s t C l a s s . M e t h o d 3 ();
} }
NOTA: Recuerde hacer referencia al espacio de nombre System. Diagnostics en el código de modo que no tenga que usar el espacio de nombre completo al usar la clase de atributo Conditional y el compilador de C# pueda encontrar la implementación de la clase. El listado 17.1 declara dos clases: TestClass y MainClass. La clase TestClass contiene tres métodos: Methodl ().Method2 () y Method3 (). Las clases Methodl ( ) y Method3 ( ) se implementan sin at ri but os, pero Method2 ( ) usa el atributo Condítíonal con un p ar ámet ro llamado DE BUG. Esto significa que el método Method2 ( ) es una parte de la clase solo cuando el compi lador de C# construye la clase con el símbolo DE BUG definido. Si el compi lador de C# construye la clase sin haber definido el símbolo DE BUG. el método no se incluye como una parte de la clase y se pasa por alto cualquier llamada al método. La clase MainClass i mplementa el método Main ( ) de la aplicación, que crea un objeto de tipo Tes tClass y llama a los tres métodos de la clase. El resultado del listado 17.1 ca mb ia dependiendo del modo en que es compilado el código. En primer lugar, intente compilar el listado 17.1 con el símbolo DEBUG definido. Puede u sar el ar gume nt o de línea de c omando del compi lador de C# /D par a definir símbolos par a el compilador: ese
/D:DEBUG
Listingl7-l.cs
C uand o el código del listado 17.1 se compi la mientras el símbolo DEBUG está definido, el método Method2 ( ) de la clase TestClass se incluye en la c o n s t rucción y al ejecutar la aplicación se escribe lo siguiente en la consola: Helio Helio Helio
from Methodl! from M e t h o d 2 ! from M e t h o d 3 !
Ahora intente compi lar el listado 17.1 sin el símbolo DEBUG definido: ese
Listingl7-I.cs
385
Cua nd o se compila el código del listado 17.1 mientras el símbolo DEBUG no esta definido, el método Method2 ( ) de la clase TestClass no se i n cl u \e en la construcción y se p as a por alto la llamada al método Method2 ( ) realizada en el método Main (). Si se crea el código del listado 17.1 sin definir el símbolo DEBUG se g enera código que escribe lo siguiente en la consola al ser ejecutado: Helio Helio
from M e t h o d l ! from M e t h o d 3 !
Como puede ver. el atributo Conditional es eficaz y útil. Antes de empe zar a usar esta clase, preste atención a las siguientes reglas: •
El método marc ad o con el atributo Conditional debe ser un método de una clase. El método marc ad o con el atributo Conditional no debe ser un método
override. El método marcado con el atributo Conditional debe tener un tipo devue lt o void. Aunque el método m ar cad o con el at ri but o Conditional no debe estar m ar c ad o con el modi fi cador override. puede estar m ar cado con el mo dificador virtual. Los reemplazos de estos métodos son implícitamente condicionales y no deben estar mar cados explícitamente con un atributo
Conditional. •
El método m ar cado con el at ri but o Conditional no debe ser una implementación de un método de interfaz; en caso contrario, se producirá un error en tiempo de compilación.
System.SerializableAttribute class El atributo Serializable es el alias de la clase System.Seriali zableAttribute. que puede ser aplicado a clases. Indica a N E T Framework que los mi embros de la clase pueden ser s eñ a li z ad os a y desde un medio de al macenamiento, como un disco duro. El uso de este atributo hace que no resulte necesario agr egar la función de estado en las clases p ar a que su almacenamiento en el disco y su posterior recuperación. C uando se serializan tipos, todos los datos de la clase ma rc a da como Serializable se guar dan en el estado en el que se encuentran cuando el dato es persistente. Si hay tipos dentro de la clase que no quiere que sean persistentes, puede marcarlos con el a tr i b u t o NonSerialized. que es el alias de la clase System. NonSerializableAttribute. En el siguiente fragmento de código, los datos de la cadena password marcados como NonSerialized no son persistentes p ar a el archivo o flujo para el que se escriben los datos de clase:
386
public
class
public public public
Users{
string username; string emailaddress; string phonenumber;
// Añada un campo que no vaya a ser persistente [NonSerialized() ] public public
FillData()
string
password;
{
username = "admin"; password = "password"; emailaddress = "[email protected]"; phonenumber = "555-1212";
Para mo st rar un ejemplo de serialización completo, el listado 17.2 vuelve a la clase Point 2D con la que ya hemos trabajado. La clase está m a rc a da con el atributo Serializable, lo que significa que puede ser g ua r d a d a y leída desde un flujo de datos. L istado 17.2. Cómo trabajar con el atributo Serializable using using using
System; System.10; System.Runt im e .Serialization.Forma11 ers.Binary;
[Se r i a l i z a b l e ] class Point2D
{ public public
int X; int Y;
} class
MyMainClass
{ public
static
void M a i n ()
{ Point2D My2DPoint
= new Point 2D () ;
My2DPoint.X = 100; My2 D P o i n t .Y = 200 ; Stream WriteStream = F i l e .C r e a t e ("Point2D .b i n "); BinaryFormatter BinaryWrite = new B in ar yF o r m a t t e r (); B i n a r y W n t e .Serialize (WriteStream, M y 2 D P o m t ) ; W r i t e S t r e a m . C l o s e (); Point2D ANewPoint
= new P o m t 2 D ( ) ;
387
C o n s o l e . Wr it eL in e("New Point Before Deserialization: ({0}, {1} ) " , A N e w P o m t .X , ANewPoint .Y) ; Stream ReadStream = F i l e . O pe nR ea d("Point2D.bin"); BinaryFormatter B m a r y R e a d = new BinaryFormatter () ; ANewPoint = (Point2D )Bi na r y R e a d .D e s e r i a l i z e (Readstream); R e a d S t r e a m . C l o s e (); C o n s o l e . W ri te Li ne ("New Point After Deserialization: ({0}, {!})", A N e w P o i n t .X , A N e w P o i n t .Y) ;
El código del listado 17.2 crea un nuevo objeto Point2D y le otorga las coordenadas (100, 200) . A continuación serializa la clase en un archivo llamado Point2D.bin. El código crea entonces un nuevo punt o y deserializa los contenidos del archi vo Point 2 D .bin en el nuevo objeto Point2D. El proceso de deserialización lee el archivo Point2D.bin y asigna los valores que se encontraban en el archivo binario a los valores del objeto. Si se ejecuta el código del listado 17.2 se escribe lo siguiente en la consola. New P o m t New P o m t
Before Deserialization: (0, 0) After Deserial iz at io n: (100, 200)
C uand o se crea el nuevo objeto Point2D. sus mi embros se inicializan con sus valores por defecto de 0. El proccso de deserialización. que asigna los valores de acuerdo con los datos al mac enados en el archivo Point2D.bin. cambia los valores. El listado 17.2 emplea dos clases de . NET F ramework en su proceso de señalización La clase Stream se encuentra en el espacio de nombre System.10 y gestiona el acceso a los flujos de datos, incluyendo los archivos de disco. La c l a se B i n a r y F o r m a t t e r se e n c u e n t r a en el e s p a c i o de n o m b r e System .Runtime .Ser ial i zat ion .Formatters .Binary y gestiona la serialización de datos a una representación binaria. N E T Framework incluye otros formateadores que pueden usarse par a representar datos serializados en otros formatos. Por ejemplo, la clase SoapFormatter da a los datos señalizados un formato adecuado par a una llamada X M L SOAP.
NOTA: La cia&e BinaryForfaatter es una patente de .NET Framework. Si tíene pefis&do que su destino sean otros sistemas que pueden no ser compatibles con el formato binario, considere usar la clase Soap Formatter para que persistan los datos de un formato XML que es com patible con otros sistemas.
System.ObsoleteAttribute class El atributo Obsolete puede ser aplicado a cualquier tipo de C# excepto a ensamblados, módulos, p ar ámet ros y valores devueltos. El atributo Obsolete
388
permite definir fragment os de código que se van a re empl aza r o que ya no son válidos. Las propiedades Message e IsError de la clase Obsolete otorgan el control del modo en el que el c ompi lador controla los tipos m ar c ad os con el atributo Obsolete. Al asignar a la propiedad IsError el valor True. el compi lador produce un error y el mensaje de error es la propi edad de cadena asignada a la propiedad Message. El valor por defecto de la propiedad IsError es Fa 1 se. lo que hace que se pro du zca un aviso cuando se compi la el código. En el siguiente código, el método HelloWorld está marc ad o como Obsolete. using System; public class RunThis
{ public
static
void M a i n ()
{ // Esto genera un aviso de tiempo de compilación. Consolé.WriteLine (HelloWo r1d () ) ; C o n s o l é . R e a d L i n e ();
} // Marca HelloWord como Obsolete [Obsolete("Next versión uses Helio public static string HelloWorld()
Unive rs e" )]
{ return
("HelloWorld") ;
} } La figura 17.1 muest ra la lista de tareas de los avisos producidos al compilar el código anterior. lis ta de tareas - 1 tarea(s): Error al generar (filtro) y
m m
Descripción
RunThis,HelloWorld()' esta obsoleto: 'N ext version uses Hello Universe’
Z\ Lista de tareas
0
(■-
Figura 17.1. Aviso producido al emplear el atributo obsolete
Si quiere asegurarse de que se produce un error y no solo un mensaje de aviso, puede modi fi car el código marc ado asi gnando a la propiedad IsError el valor true y la clase no se compilará. Si modifica el atributo Obsolete del código anterior con la siguiente línea, se produce un error: [Obsolete("Next
versión
uses
Helio
Universe",
true)]
Como puede ver. el uso del at ri but o Obsolete permite man tene r el código existente mientras nos a se guramos de que los prog ramad ore s no están usando tipos desfasados.
389
Cómo escribir sus propias clases de atributo N E T F ramework parte con un buen número de clases de atributo que pueden usarse p a r a diferentes propósitos. Sin embargo, podria necesitar un atributo que cumpl iera una función no incluida en N E T Framework. Por ejemplo, podría desear disponer de un a tri but o de revisión de código que etiquetara una clase con la fecha de la última vez que el código de esa clase fue revisado por sus compañe ros. En ese caso, necesitará definir sus propios atributos y hacer que funcionen como cualquier atributo incluido en N E T Framework. Por suerte. NE T Framework admite perfectamente la construcción de nuevas clases de atributos. En esta sec ción apr ender emos cómo el código N E T desarrolla y usa las nuevas clases de atributos. Puede escribir sus propias clases de atributos y usarlas en su código del mismo modo que usaría un atributo procedente de N E T Framework. Las clases de atri buto personales funcionan como clases normales; tienen propi edades y métodos que permiten al usuario del atributo asignar y recuperar datos. Los atributos se implementan con clases de atributo. Las clases de atributo d er i v a n de u n a c las e del e s p a c i o de n o m b r e S y s t e m de N E T l lamada Attribute. Por norma, las clases de atributo llevan antepuesta la palabra
Attribute: public
class
CodeAuthorAttribute
: Attribute
{ } Esta clase define un a tri but o llamado CodeAuthorAttribute. Este nom bre de atributo puede usarse como un atributo una vez que se ha definido la clase. Si el nombre de atributo t ermina con el sufijo Attribute, el nombre de atribu to puede ser usado entre corchetes o sin el sufijo: [CodeAuthorAttribute] [C o d e A u t h o r ]
Estos dos atributos hacen referencia a la clase CodeAuthorAttribute. Tr as definir una clase de atributo, se us a como cualquier otra clase de atributo .NET.
Cómo restringir el uso de atributos Las clases de atributo pueden, a su vez, us ar atributos. El ejemplo más común es un atributo llamado AttributeUsage. El atributo AttributeUsage contiene un p ar ám et ro que especifica dónde puede usarse un atributo. Algunos atributos pueden no tener sentido en todos los constructores de C # válidos. Por ejemplo, el atributo Obsolete tratado con anterioridad sólo tiene sentido en métodos. No es lógico m ar c a r u na sola variable como obsoleta, de modo que el
390
atributo Obsolete solamente debe aplicarse a métodos y no a otros co ns t ru c to res de C#. La clase de atributo AttributeUsage contiene una enumer aci ón pública llamada AttributeTargets. cuyos miembros ap arecen en la tabla 17.1. Estos miembros AttributeTargets pueden apa rec er juntos en una e x presión OR y ser usados como par ámet ros del atributo AtrributeUsage para especificar que la clase de atributo define un atributo que sólo puede usarse en determinados contextos, como mu es tr a el siguiente ejemplo: [ A t t n b u t e U s a g e (AttributeTargets .Class | A t t r i b u t e T a r g e t s .S t r u c t ) ] public class CodeAuthorAtt r íbute : A t t n b u t e
{ } Este constructor declara una clase llamada CodeAuthorAttribute y es pecifica que el atributo sólo puede ser usado con clases y estructuras. El compilador de C# le fuerza a usar el atributo par a asegurarse de que se emplea de ac uerdo con los valores de la e numer aci ón de AttributeTargets especificados en el atributo AttributeUsage. Si usa un atributo en una e x presión que no está permitida en la definición del atributo, el compi lador emitirá un error. Por ejemplo, suponga que escribe un atributo llamado Ñame y sólo usa la enumer aci ón A t t r i b u t e T a r g e t s .Class c omo p a r á m e t r o del a tri but o
AttributeUsage: [At tributeUsage (AttributeTargets.Class) ] public class NameAt t r íbut e : A t t n b u t e
{ } Si a continuación intenta aplicar el atributo Ñame a algo que no sea una clase, el compi lador emitirá un mensaje de error parecido al siguiente: error CS0592: El atributo "Ñame" no es valido en este declaración. Solo es válido en declaraciones "class".
tipo de
Cómo permitir múltiples valores de atributo Tambi én se puede usar el atributo AttributeUsage par a especificar si una clase permite que varias instancias de un atributo sean usadas en un f r agmen to de código C# en particular. Esto se especifica mediante un p arámet ro de at ri bu to llamado AllowMultiple. Si el valor de AllowMultiple es True. se pueden usar varias instancias del atributo en un elemento particular de C#. Si AllowMult iple recibe el valor False. sólo se puede u sar una instancia en cualquier elemento par ti cular de C# (aunque sigue estando permitido aplicar el atributo a más de un constructor de C#):
391
[AttríbuteUsage (A t t r ib ut eT ar ge ts .elass , AllowMultíple public class NameAttr ibute : A t t n b u t e
-
true) ]
{ public
NameAttnbute (stnng
Ñame)
( } } El uso de varios atributos permite asignar varios valores a un constructor de C# usando un solo atributo. El siguiente constructor marca el atributo Ñame como un atributo de varios usos y permite a los progr amadores usar el atributo más de una vez en un solo elemento de C#: [Mame("Jeíf Ferguson")] [Ñame("Jeff Ferguson's As si s t a n t " )] public class MyClass
{ } Los atributos de varios usos también pueden apa rec er en un solo conjunto de corchetes, s eparados por una coma: [Ñame ("Je f f Ferguson") , Ñame ("Jeff public- class MyClass
Ferguson's
As sistant") ]
{ } Si no especifica un valor par a el par ámet ro A l l o w M u l t í p l e . no se permite el uso v ariado.
Cómo asignar parámetros de atributo Sus clases de atributo pueden acept ar parámetros, que aparecen entre parénte sis después del nombre de atributo. En el ejemplo anterior, el atributo Ñame recibe una cadena que da nombre al creador de código como parámetro. Algunos atributos necesitan parámetros para asociar los datos al atributo, como la cadena de nombre en el at ri but o Ñame mos trado anteriormente. Los valores de los par ámet ros se pasan al constructor de la clase de atributo y la clase de at ri bu to debe i mple me nt ar un co n st ru c to r que pueda recibir los parámetros: [At t ributeUsage (Attnbut eT ar ge ts .C la ss | A t t n b u t e T a r g e t s .Struct) ] public class CodeAuthorAttr ibute : A t t n b u t e
{ public:
{ } }
392
CodeAuthorAttr ibute ( s t n n g
Ñame)
Este atributo necesita que se suministre un p ar ámet ro de cadena cada vez que es usado: [CodeAuthor ("Jeff
Ferguson") ]
Debe s uministrar los p ar ámet ros especificados en el const ructor de la clase cuando se usa el atributo. En caso contrario, obt endrá un error del compilador: error CS1S01: Ninguna sobrecarga para el método 'CodeAuthorAttríbute ' adquiere '0' argumentos
Los par ámet ros proporci onados al constructor de la clase de atributo reciben el nombre de p a r á me t ro s p o s i c i o n a l e s . Los par ámet ros posicionales asocian los datos de par ámet ro con sus nombres de par ámet ro basandose en la posicion de los datos en la lista de parámet ros. Por ejemplo, el elemento de datos del segundo par amet ro está asociado a la variable del segundo par ámet ro especificada en la lista de parámetros de la declaración de la función. También se pueden p ropor ci o n a r p a r á m e t r o s co n n o m b r e , q ue son a l m a c e n a d o s p o r las p r o p i e d a d e s implementadas en la clase de atributo. Los p ar ámet ros con nombre se especifican con el nombre de la propiedad, un signo igual y el valor de la propiedad. Los par ámet ros con nombre se asocian a datos de par ámet ros con el nombre del p ar ámet ro basado en el nombre del p ar ámet ro que aparece antes del valor. Dado que la asociación entre un nombre de variable y su valor se especifica mediante el nombre del parámetro y no mediante la posicion del valor en la lista de parámetros, los p ar ámet ro s con nombre pueden apa rec er en cualquier orden. S up on ga que añade un p ar am et ro con nombre llamado Date al atributo CodeAuthorAt tribute. Esto significa que la clase puede admitir una p r o piedad llamada Date cuyo valor puede asi gnarse en la definición del atributo: fAttríbuteUsage (Att ributeTargets.Class | At tributeTargets.Struct) ] public class CodeAuthorAttri bute : At.tri.bute
{ public
Co de Au th or At tr ib ut e(str ing
Ñame)
{ } public
strmg
Date
{ set { } } } Tras definir la propiedad, un p ar amet ro con nombre puede establecer su p r o piedad cuando el atributo a pa rez ca en el código: [CodeAuthor("Jeff
Ferguson",
Date
= "Apr
01 2001")]
393
A diferencia de los par ámet ros posicionales. los par ámet ros con nombre son opcionales y pueden omitirse de una especificación de atributo.
Ejemplo explicativo de las clases de atributo En este a pa rt ado creará un nuevo atributo llamado ClassAuthor y lo usará en código C#. Esto le dar á una idea sobre cómo el código N E T define y usa nuevos atributos. El listado 17.3 agrega una nueva clase al código del listado 17.2. Esta nueva clase recibe el nombre de ClassAuthorAttribute y deri va de la clase N E T Attribute. Listado 17.3. Cómo definir nuevas clases de atributo us ing using using
Sys tem; System.Diagnostics ; System.Reflection;
[At tributeUsage (A t t ributeTargets.Class) ] public class C l a s s A u t h o r A t t n b u t e : Attribute
{ private public;
string AuthorName; Clas sAuthor At t r ibute (st ring
AuthorName)
{ t h i s .AuthorName
= AuthorName;
} public
string Author
{ get
{ return
AuthorName;
} } } [ClassAuthor("Jeff Ferguson")] public class TestClass
{ public
void Method! ()
{ Console . W r i t e L m e ("Hello
f r om Method 1 !" ) ;
[C on di ti onal("DEBUG")] public void Method2()
{ Console . W r i t e L m e ("Hello
} public
394
void M e t h o d 3 ()
from Met hod 2 !" ) ;
{ C o n s o l e . Wr it eL in e("Hello
from Method3!");
} } public
class MainClass
{ public
static void M a i n ()
{ TestClass MyTestClass
= new T e s t C l a s s ();
MyTestClass.Methodl () ; MyTestClass.Method2 () ; M y T e s t C l a s s . M e t h o d 3 () ; object [] ClassAttributes ; Memberlnfo Type Informat 1 o n ; Type Informa1 1 on = typeof (TestClass) ; ClassAttributes = Type Informat 1 o n .GetCustomAttrlbutes (typeof (C 1 assAuthorAttribute) ,
false) ; ii (ClassAttributes.GetLength(0)
!=
0)
{ C 1 as sAutho rAt tribute
Clas sAt t r i b u t e ;
C 1assAttribute = (Cl as sA ut ho r A t t r i b u t e ) (ClassAttributes [U] ) ; C o n s o l e .WriteLine ("Class Author: {0}", C 1 a s sA tt ri b u t e . A u t h o r ) ;
1 El codigo de 1 listado 17.3 comienza con una nueva clase de atributo llamada
CodeAuthorAttribute. La clase sirve como una clase de atributo para un atributo que sólo puede aplicarse a otras clases. La clase recibe un par amet ro de cadena, que se al macena en una v ariable priv ada y al que se accede publicamente mediante una propi edad de solo lectura llamada Author. La intención del p ar amet ro es m ar c ar una clase como poseedora de un nombre de prog ramad or específico adjunto, de modo que los demás progr amadores sepan con quien deben c ont act ar si tienen al guna duda sobre la implementacion de la clase. La clase TestClass usa el atributo CodeAuthor y proporciona el parametro
Jeff
Ferguson.
L1 rasgo mas interesante del listado 17.3 es el me;t o d o M a i n ( ) . que obtiene un objeto de atributo de la clase y escribe el nombre del autor. Esto lo hace mediante un concepto llamado reflexión, que implementa las clases de un espacio de n o m bres NET llamado System. Reflection. Mediante la reflexión, el código puede, en tiempo de ejecución, est udi ar la i mplementación de una clase y descu-
395
brir cómo está construida. La reflexión permite que el código examine otros frag mentos de código p ar a derivar información, como los métodos y propiedades que admite y la clase base de la que deriva. La reflexión es una función muy potente y es completamente c ompatible con . NET Framework. El código del listado 17.3 usa la reflexión p ar a obtener una lista de atributos asociados a una clase particular. El código de atributo comienza recuperando un obj et o Type para la clase Tes tciass . Para conseguir el objeto Type se usa el oper ador de C # typeof (). Este oper ador t oma como ar gument o el nombre de la clase cuyo tipo de información se va a recuperar. El objeto Type devuelto, que está definido en el espacio de nombre de N E T F ramework System, funciona como una t abl a de contenidos, describiendo todo lo que se debe saber sobre la clase requerida. Después de r ecuper ar el objeto Type p ar a la clase, el método Main ( ) llama a un método llamado GetCustomAttributes ( ) p ar a conseguir una lista de los atributos que permite la clase descrita por el objeto Type. Este método de vuelve una matriz de objetos y ac ept a como p ar ám et ro el tipo del atributo que debe recuperarse. En el listado 17.3, el método GetCustomAttributes ( ) es invocado con información de tipo p ar a la clase CodeAuthorAttribute como parámetro. Esto obliga al método GetCustomAttr ibutes ( ) a devol v e r s ó l o i n f o r m a c i ó n s o b r e los a t r i b u t o s de c l a s e q u e s e a n del t ipo CodeAuthorAttribute. Si la clase hubiera usado algún otro atributo, la l lamada no podria devolverlos. El código del listado 17.3 finaliza tomando el pri mer atributo CodeAuthorAttribute de la matriz y solicitándole el valor de su propiedad Author. El valor de la cadena se escribe en la consola. Si se ejecuta el código del listado 17.3 se escribe lo siguiente en la consola (si compi la el código sin definir el símbolo DEBUG): Helio from Methodl! Helio from M e t h o d 3 ! Class Author: Jeff Ferguson
Resumen N E T F ramework permite u sar atributos en los lenguajes que se ejecutan con el CLR. El concepto de atributo abre la puer ta a la expansión de la funcionalidad de los lenguajes N E T con clases que pueden agr egar comport ami ent os al código. El lenguaje C# permite el uso de atributos creados por otras personas en su código C# y también la creación de atributos propios, que pueden ser usados por otros progr amadores de NET. El concepto de atributo no es exclusivo de C #; más bien, está disponible para cualquier lenguaje que se ejecute con el CLR. Los atributos le conceden la posibi lidad de extender el entorno del lenguaje y apo rt a nuevas herrami ent as p a r a que los pr og ramad or es trabajen con código NET. El proceso de serialización es un
396
buen ejemplo de esto. La s eña li z ac ión no está integrada en la especificación del lenguaje C#. pero su funcionalidad está disponible por medio de una clase de atributo escrita por Microsoft. La clase de atributo extiende el lenguaje en tiempo de ejecución p ar a que admi ta una caracterí st ica que no fue diseñada en ese len guaje. Al igual que los demás constructores de . NET Framework. los atributos son objetos. Se definen por clases que derivan de la clase S y s t e m .Attribute de N E T Framework. Puede us ar C# p ar a desarrollar nuevas clases de atributo con sólo derivar una nueva elase de la clase base S y s t e m .Attribute. Los atri butos que desarrolle en CU y los atributos ya definidos por N E T Framework pueden ser usados por cualqui er lenguaje compatible con el CLR. Los atributos se usan especificando entre corchetes el nombre de clase del atributo, inmediatamente antes del const ructor de CU al que se aplica el atributo. Los atributos pueden ac ep tar datos en forma de parámet ros, que pueden asociar datos de estado al atributo. Estos datos pueden ser recuperados por código de reflexión que puede consul tar el código y bu s ca r atributos.
397
fclfl Cómo utilizar versiones en sus clases Casi todo el código escrito p a r a las modernas aplicaciones evoluciona con el tiempo. Los proyectos de soft ware comienzan con un conjunto de requisitos y se diseñan las clases p ar a que cumpl an esos requisitos. Este primer código base sirve como código fuente de la versión 1.0 de la aplicación. Sin embargo, casi todas las aplicaciones van más allá de la versión 1.0. Las actualizaciones de la aplicación proceden de un grupo de requisitos mejorado y la versión 1.0 del codigo base debe ser revisada p ar a implementar los requisitos actualizados. El lenguaje C# admite const ructores que hacen las clases lo suficientemente estables como p ar a evolucionar mientras cambi an los requisitos de la aplicación. En este capítulo, aprenderemos a usar las pal abr as clave new y override en métodos de clase de C# par a as egurarnos de que las clases pueden continuar usándose a medida que cambi an los requisitos de la aplicación.
El problema de las versiones Antes de aprender a usar las pal abr as clave new y override para hacer las clases de su código de C# compatibles con el código base que tiene que mant ener se al día con los requisitos de cambio, observe como sería la vida sin estas pala-
399
bras clave. Si recuerda el capítulo 8. las clases que creamos y usamos pueden ser consideradas clases base. Estas clases tienen la funcionalidad básica que necesi tan las aplicaciones. Al declarar una instancia de una clase, está derivando de esa clase para usar su funcionalidad. Las bibliotecas de clases base de N E T Framework están bas adas en este m o delo; todo lo que hacemos mientras p ro gr amamos aplicaciones N E T está basado en una clase base. Todo el entorno deriva de la clase base System .Ob j ect. de m o d o que i ncl us o c u a n d o d e r i va un a s imp le v a r i a b l e e st á d e r i v a n d o una funcionalidad de la clase base System. Object. El listado 18.1 muestra las características de las clases base y derivadas. Listado 18.1. Una clase base y una clase derivada using
S ys tem;
public
class
BaseClass
{ protected public
int Valué;
B a s e C l a s s ()
{ Valué
public
- 12 3;
class
DerivedClass
: BaseClass
{ public
void
P n n t V a l u e ()
{ Consolé.WriteLine("Valué
= " + Valué);
} } class
MamClass
{ public
static
void M a i n ()
{ DerivedClass DerivedC1assObject = new D e n v e d C l a s s O b j e c t .P n n t V a l u e () ;
D e r i v e d C l a s s ();
} } El código del listado 18.1 es relativamente sencillo. Contiene una clase base l lamada BaseClass que i ncl ine una variable entera protegida. Ot ra clase, lla mada Der ivedCl ass. deriva de la clase BaseClass e iniplementa un mét o do llamado P n n t V a l u e ( ) . El método Main ( ) crea un objeto de tipo DerivedClass y llama a su método PrintValue ( ) . Si se ejecuta el codigo del listado 18.1 se escribe lo siguiente en la consola: Valué
400
= 12 í
Ahora suponga que los requisitos cambian y otro p rog ra ma do r decide de sa r ro llar la clase BaseClass mientras cont inuamos t rabaj ando en nuevas mejoras par a la clase De r ivede 1 as s . ¿Qué ocurrirá si el otro p ro gr amado r agrega un método a la clase BaseClass llamado PrintValue ( ) y propor ci ona un implementación ligeramente diferente? El código seria como el listado 18.2. L istad o 18.2. Adición de PrintValue() a la clase BaseClass using System; public
c 1 a s s BaseClass
{ prote cted public
in t Valué;
B a s e C l a s s ()
í Valué
- 123;
} public
virtual
void
P r i n t V a l u e ()
I Consolé . W r i t e L m e ("Valué:
" + Valué) ;
( } public
class
DerivedClass
: BaseClass
( public
void
P r i n t V a l u e ()
{ Consolé.WriteLine ("Valué
= " + Valué) ;
} } class
MamClass
{ public
static void M a m ( )
{ DerivedClass
DerivedClassObject
= new
DerivedClass () ;
DerivedClassObject.PrintValue () ;
} } Ahora tenemos un problema. La clase DerivedClass deriva de la clase BaseClass y a mbas implementan un método llamado PrintValue (). La clase BaseClass ha sido actualizada a una nueva versión, mientras que la clase DerivedClass ha p er maneci do con su i mplementación original. En el listado 18.2. la relación entre el método PrintValue ( ) de la clase base y el método PrintValue ( ) de la clase derivada no está clara. El compi lador debe saber que método reemplaza a la versión de la clase base. Y el compi lador no sabe que
401
i mple me nt aci ón debe ej ec ut ar cu a nd o el mét odo Main ( ) l lama al método
P r i n t V a l u e (). Como resultado, el compi lador de C# remarca esta a mbi güedad con un aviso: w a r m n g CS0I14: 'De r i vedCl as s .Pr int Va lúe () ' oculta el miembro heredado 'BaseClass.PrintValue () ' . Para hacer que el método actual reemplace esa implementación, agregue la palabra clave override. De lo contrario, agregue la palabra clave new.
Este es un buen aviso, porque la filosofía del lenguaje C# fomenta la claridad y el compi lador de C # siempre avisa sobre los constructores de código que no están claros.
Cómo solucionar el problema de las versiones C# tiene dos modos de s olucionar la a mbi güedad del listado 18.2: Usar el modificador new p a r a especificar los dos métodos que. en reali dad. son diferentes. Usar el modi fi cador overr ide par a especificar que el método de la clase derivada debe reempl azar al método de la clase base. Exami nemos estos dos métodos.
Mediante el modificador new Si las dos implementaciones de método del listado 18.2 deben ser tratadas como métodos separados que simplemente tienen el mi smo nombre, el método de la clase derivada debe llev ar ant epuest o el modi fi cador new. Al usar el modifica dor new, p u e d e o c u l t a r e x p l í c i t a m e n t e los m i e m b r o s h e r e d a d o s de la i mplementación de la clase base. Simplemente debe dec la rar un mi embro en su clase derivada con el mismo nombre y ant eponer a la declaración el modificador new y se usará la funcionalidad de la clase derivada, como muest ra el listado 18.3. Listado 18.3. C ómo resolver la ambig üedad mediante la palabra reservada new using public
System; class
BaseClass
{ protected public
402
int Valué;
B a s e C l a s s ()
{ Value
= 12 3;
} public
void
P n n t V a l u e ()
{ Console .W r i t e L m e ("Value : " + Value) ;
} } pu b l i c
class
DerivedClass
: BaseClass
{ new public void
P n n t V a l u e ()
{ Console . W r i t e L m e ("Value
= " + Value);
) } class
MainClass
{ public
static void M a i n ()
( DerivedClass
DerivedClassObject
- new
DerivedClass () ;
Derived Class Ob ject ,P n n t V a l u e () ;
}
NOTA: El operador new y el modificador new son implementaciones di ferentes de la palabra clave new. El operador new se usa para crear ob jetos, mientras que el modificador new se usa para ocultar un miembro heredado de un miembro de clase base. El codigo del listado 18.3 usa la pal abra clave new en la i mplement aci ón del mét odo Pr intValue () de la clase Der ivedCia.ss. Esto indica al compilador de C# que debe t rat ar este método como distinto del método de la clase base, aunque los dos métodos tengan el mismo nombre. El uso de la p al ab ra clave resuelv e la a mbi güedad y permite que el c ompi lador de C# compile el eodigo sin emitir advertencias. En este caso, el método Main () llama al método de la clase derivada y el listado 18.3 escribe lo siguiente en la consola: Valué
= 12 3
Todaví a puede ejecutar el método de la clase base porque la pal abra clave new ha as egu rad o b ási cament e que los dos métodos P r i n t V a l u e ( ) de cada una de las clases puede ser llamado por separado. Puede llamar al método de la clase base conv irtiendo explícitamente el objeto de la clase deriv ada en un objeto de tipo de la clase base:
403
BaseClass
BaseClassObject
=
(BaseClass)DerivedClassObject;
B a s e C l a s s O b j e c t .PrintValue () ;
C omo puede ver. el uso del modi fi cador new solamente permite reemplazar la funcionalidad en una clase base. Si necesita usar la funcionalidad de la clase original, use el nombre de la clase completo y su ruta de acceso con el miembro de la clase p a r a estar seguro de que está us ando la funcionalidad correcta.
Mediante el modificador override La otra opción par a resolver la a mbi güedad con los nombres de método dupli cados es u sar el modificador override par a especificar que la implementación de la clase derivada reemplaza a la implementación de la clase derivada. El modi ficador override "reemplaza" la funcionalidad del mi embro de la clase base al que sustituye. Para reempl azar a un mi embro de una clase, la firma del mi embro que reem plaza debe ser la mi sma que el miembro de la clase base. Por ejemplo, si el mi embro que realiza el reemplazo tiene un constructor, los tipos en el constructor deben coincidir con los del mi embro de la clase base. En el listado 18.4 puede ver el f unc iona mie nto del mo di fi cado r override. Listado 18.4. Cómo resolver la ambigüedad mediante el modificador override us m g
Sys t em;
public
class
BaseClass
{ protected public
int Valué;
B a s e C l a s s ()
{ Valué
= 12 3;
} public
virtual
void
P r i n t V a l u e ()
{ Consolé . W n t e L m e ("Valué:
" + Valué);
} } public
class
DerivedClass
: BaseClass
{ override
public
void
P r i n t V a l u e ()
{ C o n s o l e . W n t e L m e ("Valué
}
404
= " + Valué);
class
HamClass
{ public
static
void M a i n ()
{ DerivedClass
DerivedClassOb]ect
= new
DerivedClass () ;
Der ivedC 1 assOb j ect .P n n t V a l u e () ;
} } En el listado 18.4. la pal abr a clave override indica al compi lador de C# que la i mplement aci ón de PrintValue ( ) en la clase derivada reemplaza a la implementación del mismo método en la clase base. La implementación de la clase base está oculta bási cament e a los invocadores. A diferencia del listado 18.3. el código del listado 18.4 sólo contiene una implement aci ón de PrintValue (). La implementación de la clase base de PrintValue ( ) no es accesible al código del mét odo Main ()„ incluso si el código convierte explícitamente el obje to de la clase derivada en un objeto de clase base y llama al método en el objeto de la clase base. Debido a que la p al ab ra clave override se usa en el listado 18.4. todas las l lamadas al método realizadas mediante un objeto convertido expl ícitamente son dirigidas a la i mplementación reempl aza da de la clase derivada. Observe el código us ado en la llamada a la implementación de la clase base de PrintValue ( ) cuando se usa el oper ador new p a r a resolver la ambigüedad: BaseClass
BaseClassObject
=
(BaseClass)D e n v e d C l a s s O b ] e c t ;
BaseClassObject.PrintValue () ;
Este código no es suficiente p ar a hacer que se llame a la implementación de la clase base cuando se use la pal ab ra clave override. Esto es debido a que el objeto fue creado como un objeto de la clase DerivedClass. Puede llamar al método de la clase base pero, debido a que la implementación de la clase base ha sido reempl aza da con el código de la clase derivada, se seguirá i nvocando a la implementación de la clase derivada. Debe us ar la p al ab ra clave de C# base p a r a llamar a la implementación de la clase base, como en el siguiente ejemplo: base.PrintValue () ;
Esta instrucción llama a la implementación de PrintValue ( ) que se e n cuentra en la clase base de la clase actual. Al colocar esta instrucción en la clase DerivedClass, por ejemplo, se llama a la implementación de PrintValue ( ) que se encuent ra en la clase BaseClass. Puede considerar el uso de la p al abr a clave b a s e i g u al al u s o de la s i n t a x i s c o m p l e t a y con r u t a de a c c e s o namespace .ob j e c t .method. a mbas simplemente hacen referencia a la ins t ancia de clase base correcta que se está usando.
405
Resumen Los ejemplos de este capítulo situaban j unt as todas las clases del listado en un único archivo fuente p a r a hacerlos más sencillos. Sin embargo, la programaci ón en el mundo real puede ser más complicada. Si varios progr amadores están t r aba j a nd o en un único provecto, el provecto podría estar formado por más de un archivo fuente. El p rog ramado r que t rabaja en la clase base podría colocarla en un archivo fuente de CU y el pr og r am ad or que t rabaj a en la clase derivada puede colocarla en otro archivo fuente de C#. El asunto podría ser aun más complicado si la clase base se compila en un ensambl ado y se implcmenta la clase derivada en un provecto que hace referencia al ensamblado. Lo importante en este caso es que las clases base y las clases deriv adas pueden proceder de varias fuentes diferentes y que coordinar la progr amaci ón de las clases cobra una gran importancia. Es de vital importancia comprender que. con el tiempo, las clases base y las clases deriv adas añadirán funcionalidades a medi da que el provecto progrese. C omo progr amador, debería tener esto en cuenta: diseñe sus clases de modo que pueden ser usadas en varias versiones de un provecto y puedan ev olucionar a medida que evolucionen los requisitos del p ro vecto. A priori. la otra solución par a los problemas que presentan las versiones es i n cl us o mas sencillo: no use el m i sm o n o m b r e p a r a m ét o do s que tengan i m pl e me n t a c i o n e s d ifer ent es a menos que esté r e e m p l a z a n d o re al men te la funcionalidad de la clase base. Aunque, en teoría, éste puede parecer el mejor medio para solventar el problema, en la practica no siempre es posible. Las pal a bras clave de CU now y o ve r ri d e le ayudan a evitar este problema de pr og ra mación y le permiten reutilizar nombres de métodos en caso de que sea necesario para su provecto. El principal uso de la pal abra clave o ve rr i de es avisar de la creación de nuevas i mplementaciones de métodos virtuales en clases base, pero también tiene un papel en las versiones de CU.
406
19 Cómo trabajar con código no seguro Al usar la pal abra clave n e w para crear una nueva instancia de un tipo de referencia, está pidiendo al C LR que reserv e suficiente memoria para la v ariable. El C LR asigna suficiente espacio en la memoria para la variable y asocia la memoria a la variable. En condiciones normales, el codigo desconocería la local i zacion actual de esa memoria, por lo que respecta a la dirección de memoria. Después de que la operacion n e w tenga éxito, el codigo puede usar la memoria asignada sin saber ni importarle en que parte del sistema está realmente situada la memoria. En C y C ++. los progr amadores tienen acceso directo a la memoria. Cuando un fragmento de código de C o de C ++ solicita acceso a un bloque de memoria, se le otorga la dirección específica de la dirección asi gnada y el código lee y escribe directamente en esa posición de memoria. La ventaja de este enfoque es que el acceso directo a la memori a es ext remadament e rápido y contribuye a la eficien cia del codigo. Sin embargo, hay algunos inconvenientes que superan a las vent a jas. El probl ema de este acceso directo a la memoria es que es fácil usarlo incorrectamente v esto hace que el código falle. Un mal funcionamiento del codigo de C o C++ puede fácilmente escribir en la memoria de otra variable. Estos probl emas de acceso a memori a producen numerosos fallos y errores de software difíciles de localizar. La arquitectura del C L R elimina todos estos problemas rea lizando toda la gestión de memoria por nosotros. Esto significa que su codigo de
409
C# puede t ra ba j ar con variables sin que necesitemos conocer los detalles de cómo y dónde se al macenan las variables en memoria. Como el C L R protege al código de C# de estos detalles de la memoria, el código de C# está libre de errores relacionados con el acceso directo a la memoria. No obstante, alguna vez tendrá que t ra ba j ar con una dirección de memoria especifica del código C#. El código puede necesitar un poco más de rendimiento o quizas el código de C# quiera t raba jar con código heredado que necesite que le proporci one la dirección de un fragment o de memori a específico. El lenguaje C# admite un modo especial, llamado modo no s e g u r o . que permite t rabaj ar directa mente con memori a desde el interior del código C#. Este const ructor de C# especial recibe el nombre de modo no seguro porque el código ya no dispone de la protección que ofrece la gestión de memori a del CLR. En el modo no seguro, el código de C# puede acceder directamente a la memoria y puede tener los mismos fallos relacionados con la memori a que los códigos de C y C ++ si no es ext remadament e cui dadoso con su for ma de gestionar la memoria. En este capítulo, est udi aremos el modo no seguro del lenguaje C# y como puede ser us ado par a permitirle acceder directamente a las direcciones de memo ria usando constructores de estilo puntero de C y C++.
Conceptos básicos de los punteros En C# se accede a la memori a usando un tipo de dato especial llamado p u n tero. Un puntero es una variable cuyo valor a pun ta a una dirección específica de la memoria. En C#. un puntero se declara con un asterisco situado entre el tipo del puntero y su identificador. como se mu es tr a en la siguiente declaración: int * My ln te gerPointer;
Esta instrucción declara un puntero entero llamado M y l n t e g e r P o i n t e r . El tipo del puntero indica el tipo de la variable a la que el p untero puede apuntar. Por ejemplo, un puntero entero sólo puede a pu nt ar a memori a us ada por una variable entera. Los punteros deben ser asignados a una dirección de memoria y C# hace que sea fácil escribir una expresión que evalúe la dirección de memoria de una variable. Si se antepone el operador de concatenación, el símbolo de unión, a una expresión uñaría, devuelve una dirección de memoria, como mues tra en el siguiente ejemplo: int int
Mylnteger = 123; * MylntegerPointer
= SMylnteger;
El código anterior hace dos cosas: Declara una variable entera llamada M y l n t e g e r y le asigna el valor 123.
410
Declara una variable entera llamada MylntegerPointer y la apunta en la dirección de la variable Mylnteger. La figura 19.1 mues tra cómo se interpreta esta asignación en memoria.
Mylnteger
123
MylntegerPointer
Figura 19.1. Un puntero apuntando a una variable
Los punteros en realidad tienen dos valores: El val or de la dirección de memori a del puntero El valor de la variable a la que a pu nt a el puntero C # permite escribir expresiones que evalúen cualqui era de los dos valores. Si se antepone un asterisco al identifícador del puntero se obtiene el valor de la variable a la que ap un t a el puntero, como demu es tra el siguiente código: int int
Mylnteger = 123; * MylntegerPointer
- SMylnteger;
Consolé .W r i t e L m e (‘MylntegerPointer) ;
Este código escribe en la consola 12 3.
Tipos de puntero Los punteros pueden tener uno de los siguientes tipos:
•
sbyte
•
byt e
•
short
•
ushort int
•
uint
411
lo n g
ulong •
char
•
float double
•
dec: ima 1
•
boo 1
•
un tipo de enumeración
void. usado par a especificar un puntero par a un tipo desconocido No se puede declarar un puntero p ar a un tipo de referencia, como un objeto. La memori a par a los objetos está gestionada por el C L R y la memoria puede ser b or r ada cada vez que el recolector de elementos no utilizados necesite liberar la memori a del objeto. Si el compi lador de CU le permite mant ener un puntero sobre un objeto, su código corre el riego de ap un tar a un objeto cuya memoria puede ser liberada en otro punto por el recolector de elementos no utilizados del CLR. Imagine que el compi lador de CU permitiese escribir código como el siguiente: MyClass MyClass
MyObject = new MyClass () ; * MyOb] e c t P o m t e r ;
MyObj ectPo.i nter
=
SMyObject;
La memoria usada por MyObj ect es gestionada aut omát icamente por el CLR v su memori a es liberada cuando todas las referencias al objeto se liberan y se ejecuta el recolector de elementos no utilizados del CLR. El probl ema es que el codigo no seguro ahora contiene un puntero al objeto y el resultado es que tiene un puntero que apunt a hacia un objeto cuya memoria se ha liberado. El C L R no tiene ningún modo de saber que hay un puntero par a el objeto y el resultado es que. después de que el recolector de elementos no utilizados haya liberado la memoria, tiene un puntero apun tand o hacia la nada. C# solventa este problema no permi tiendo que existan variables a tipos de referencia con memoria que es gestionada por el C LR
Cómo compilar código no seguro Por defecto, el compi lador de CU sólo compila código seguro de CU. Para obligar al compi lador a compi lar código de CU no seguro debe us ar el argumento del c om pi l ado r /unsafe: ese
412
/unsafe
filel.es
El código no seguro permite escribir código que acceda di rectamente a la m e moria. sin hacer caso de los objetos que gestionan la memoria en las aplicaciones. Como a las direcciones de memori a se accede directamente, el codigo no seguro puede funcionar mejor en algunos tipos de aplicaciones. Esta instrucción compila el archiv o fuente f i l e í .es y permite compi lar el codigo no seguro de C#.
NOTA: En C#, el código no seguro permite declarar y usar punteros del mismo modo que en C++.
Cómo especificar punteros en modo no seguro El compi lador de C# no permite por defecto usar punteros en el código C#. Si intenta t rabaj ar con punteros en su código, el compilador de C# emitirá el siguien te mensaje de error: error CSÜ214: Los punteros contexto no seguro
solo se pueden utilizar
en un
Los punteros sólo son válidos en C# en código no seguro y hay que definir explícitamente el código no seguro al compilador. Para hacerlo se emplea la p a l a bra clave de C # unsafe. La pal abr a clave unsafe debe aplicarse a un bloque de codigo que use punteros. P ar a especificar que un bl oque de codigo se ejecute en el modo no seguro de C# se aplica la pal abr a clave unsafe a la declaración del cuerpo de codigo. como se muest ra en el listado 19.1. Listad o 19.1. Métodos no seguros using
System;
public
class MyClass
{ public
unsafe
static void Maini)
{ int Mylnteger = 123; int * MylntegerPointer
= iMylnteger;
C o n s o l é . W n t e L i n e (*MyIntegerPoínter) ;
} } El método Main ( ) del listado 19.1 usa el modi fi cador unsaf e en su d ec la ración. Esto indica al compi lador de C# que todo el código del método debe ser considerado no seguro. Después de us ar esta pal ab ra clave, el código del método puede us ar constructores de puntero no seguros.
413
La palabra clave unsafe se aplica sólo al método en el que aparece. Si la clase del listado 19.1 va a contener otro método, esc otro método no podrá usar constructores de puntero no seguros a menos que. también, sea declarado con la p al abr a clave unsafe. Las siguientes reglas se aplican al modificador unsafe. Clases, est ructuras y delegados pueden incluir el modi fi cador unsafe. que indica que todo el cuerpo del tipo se considera no seguro. Campos , métodos, propiedades, eventos, indizadores, operadores, cons tructores. destructores y constructores estáticos pueden definirse con el modi fi cador unsafe, que indica que la declaración del miembro especifi co no es segura. Un bloque de código puede ser m a r c ad o con el modi fi cador unsafe. que índica que todo el código debe ser consi derado no seguro.
Cómo acceder a los valores de los miembros mediante punteros El modo no seguro de C# permite usar el operador - > p ar a acceder a los miembros de las estructuras a las que hace referencia el puntero. El operador, que se escribe como un guión seguido por el símbolo m ayo r que. le permite acceder directamente a los miembros, como se mu es tra en el listado 19.2. Listado 19.2. Cómo acceder a los miembros de una estructura con un puntero usmg
System;
public
struct
Point2D
{ public public
int X; int Y;
} public
class MyClass
( public
unsafe
static void M a i n ()
{ Pomt2D Pomt2D
MyPoint; * PointerToMyPoint ;
MyPoint = new P o m t 2 D ( ) ; PointerToMyPoint = S M y P o m t ; P o i n t e r T o M y P o m t - > X - 10 0; P o i n t e r T o M y P o m t - > Y - 200 ; Consolé . W r i t e L m e ("({ 0 } , {1})", PomterToMyPomt->Y) ;
} }
414
PointerToMyPoint->X ,
El listado 19.2 contiene la declaración de una est ructur a llamada Point2D. La e st ruc tur a tiene dos miembros públicos. El listado también incluye un método no seguro Main ( ) que crea una nueva variable del tipo de la est ructura y crea un puntero p ar a la nueva estructura. A continuación, el método usa el oper ador de acceso a miembros del puntero par a asignar valores a la estructura, que se escribe en la consola. Esto es diferente del acceso a mi embro s del modo seguro, por defecto, de C#. que u sa el oper ad or .. Si se usa un o per ador incorrecto en un modo incorrecto, el compi lador de C# emite un error. Si usa el oper ador . con un puntero no seguro, el c ompi lador de C# emite el siguiente mensaje de error: error CS0023: El operador del tipo 'Point2D*'
no se puede
aplicar
a operandos
Si se usa el oper ador - > en un contexto seguro, el compi lador de C# también emite un mensaje de error: error CS0193:
El operador
* o -> se debe
aplicar
a un puntero
Cómo usar punteros para fijar variables a una dirección específica C uando el C L R gestiona la memoria de una variable, el codigo t rabaj a con una variable y los detalles sobre la memori a de la variable son controlados por el CLR. Durant e el proceso de recolección de elementos no utilizados del CLR. el tiempo de ejecución puede ca mb ia r la memori a de lugar varias veces par a af ia n zar la pila de memori a disponible en tiempo de ejecución. Esto significa que durante el curso de una aplicación, la dirección de memori a p ar a una variable puede cambiar. El C L R puede t omar los datos de la variable y moverlos a una dirección diferente. En condiciones normales, el código de C# no tiene en cuenta esta técnica de recolocación. C ómo el código t rab aj a con un identificador de variable, n o rm al mente accederá a la memori a de la variable mediante el identificador de la v a r i a ble y puede confiar en que el C L R trabaje con el fragment o de memori a correcto mientras t rabaj a con la variable. Sin embargo, la situación no es tan sencilla cuando se t rabaj a con punteros. Los punteros a punt an a una dirección de memori a específica. Si asigna un pun te ro a una dirección de memori a us ada por una variable y el C L R después mueve la dirección de memoria de esa variable, su puntero estará apunt ando a una memoria que ya no está siendo us ada por la variable. El modo no seguro de C# permite especificar una variable como excluida de la recolocación de memori a del CLR. Esto permite mant ener una variable en una dirección específica de memoria, lo que le permite us ar un puntero con la variable
415
sin p re ocupar se de que el C'LR p ueda mov er la dirección de memori a de la v aria ble de la dirección a la que a punt a su puntero. Para especificar que la dirección de memoria de una variable debe ser fija se usa la pal abr a clave de C# f ized. La pal abra clave f ized va seguida de una expresión entre paréntesis que contiene una declaración de puntero con una asignación a una variable. La expresión fija da va seguida de un bloque de código y la variable fijada permanece en la misma dirección de memori a a lo largo del bloque de código fijado, como se muest ra en el listado 19.3. Listado 19.3. Cómo fijar en memoria los datos gestionados u sin g S y s t e m ; p u b 1 i c r;1a s s M y C 1 a s s
í p u b 1ic unsafe
static
void M a i n ()
I mt int
Ai r ay Index ; [] IntegerArray;
IntegerArray - new m t [5] ; f ized !m t * I n t e g e r P o m t e r = IntegerArray)
I for (Arraylndex. -- 0; Arraylndex < 5; Ar r ay Index + + ) IiiteqerPointer [Airaylndex] = Arraylndex;
) for (Arraylndex - 0; Arraylndex < 5; Arraylndex + +J Conso 1e .Wr 1 1 c L m e (IntegerArray [Arraylndex] ) ;
La palabra clav e f ized del listado 19.3 declara un puntero entero que apunta a una matriz entera. Va seguida por un bloque de codigo que escribe valores en la matriz usando un puntero. Dentro de este bloque de código, está g ar ant izado que la dirección de la matriz IntegerArray es fija y que el C L R no moverá su posición. Esto permite al código usar un puntero con la matriz sin preocuparse de si el C L R va a mover la posicion de la memoria física de la matriz. Después de que el bloque de codigo fijado termine, ya no puede usarse el puntero y el CLR vuelve a tener en cuenta a la variable IntegerArray cuando reubica la memo ria.
Sintaxis del elemento de matriz puntero El listado 19.3 también muest ra la sintaxis del elemento de matriz puntero. La siguiente línea de codigo t rata un puntero de modo no seguro como si fuera una matriz de bytes: I n t e g e r P o m t e r [Arraylndex]
416
= Arraylndex;
Esta linca de código t rata al puntero como si fuese una matriz. La sintaxis del elemento de matriz puntero permite al código de C# no seguro ver la memoria a la que a punt a el puntero como una matriz de variables en la que se puede escribir y leer de ella.
Cómo comparar punteros El modo no seguro de C# permite c o m pa r ar punteros usando los siguientes operadores: Igualdad (==) Desigualdad (!=) Menor que (<) Mayo r que (>) Menor o igual que (<^) May or o igual que ( > - ) Al igual que los tipos de valores, estos operadores devuelven los valores bool eanos True y False cuando se usan con tipos de puntero.
Cálculo con punteros Se pueden c ombi nar punteros con valores enteros en expresiones matemát icas par a c ambi ar la posición a la que a pun ta el puntero. El operador + s uma un valor al puntero y el oper ador - resta un valor del puntero. La instrucción f ixed del listado 19.3 también puede escribirse como se indica a continuación: fixed ( m t
* IntegerPointer
= IntegerArray)
{ for (ArrayIndex = 0; Arraylndex *( IntegerPointer + Arraylndex)
< 5; ArrayIndex ++ ) = Arraylndex;
} En este bloque de código, el puntero es despl azado por un valor y la s uma se usa par a a pun ta r a una dirección de memoria. La siguiente instrucción realiza aritmética de puntero: * (IntegerPointer
+ Arraylndex)
= Arraylndex;
Esta instrucción debe interpretarse como: " Toma el valor de IntegerPo inter e i n c r e m é n t a l o en el n ú m e r o de p o s i c i o n e s e s p e c i f i c a d a s por Arraylndex. Col oca el valor de Arraylndex en esa posición". La aritmética de punt ero aument a la posición de un puntero en un número especificado de bytes, dependiendo del t amañ o del tipo al que se está apuntando.
417
El listado 19.3 declara una matriz entera y un puntero entero. Cuando se usa aritmética de puntero en el puntero entero, el valor usado par a modificar el punte ro especifica el número de t a maños de variable que deben moverse, no el numero de bytes. La siguiente expresión usa aritmética de puntero para despl azar la loca lización de un puntero en tres bytes: IntegerPointer + 3
El valor literal 3 de esta expresión especifica que se debe incrementar el puntero en el espacio que o cupan tres números enteros, no en tres bytes. Dado que el puntero a punt a a un número entero, el 3 se interpreta como "espacio nece sario par a tres números enteros" y no "espacio p ar a tres bytes". Dado que un numero entero ocup a cuatro bytes de memoria, la dirección del puntero se a u menta en doce bytes (tres números enteros multiplicado por cuatro bytes por cada numero entero), no en tres.
Cómo usar el operador sizeof Se puede usar el operador s i z e o f en modo no seguro par a calcular el nu mero de bvtes necesarios par a contener un tipo de datos específico. El operador va seguido por un nombre de tipo no admi ni strado entre paréntesis y la expresión da como resultado un numero entero que especifica el número de bytes necesa rio para contener una variable del tipo especificado. La tabl a 19.1 enumera los tipos administrados admitidos y los valores que devuelve una operación s i z e o f . Tabla 19.1. Tipos sizeof() admitidos
Expresión
Resultado
sizeof(sbyte)
1
sizeof(byte)
1
sizeof(short)
2
sizeof(ushort)
2
sizeof(int)
4
sizeof(uint)
4
sizeof(long)
8
sizeof(ulong)
8
sizeof(char)
2
sizeof(float)
4
sizeof(double)
8
sizeof(bool)
1
418
Cómo asignar espacio de la pila para la memoria C# proporciona un sencillo mecani smo de asignación de memoria en código no seguro. Puede solicitar memori a en modo no seguro usando la p al ab r a clave de C# stackalloc. como se mues tra en el listado 19.4. L istado 19.4. Cómo asignar espacio de la pila para la memoria using
System;
public
class MyClass
{ public
unsafe
static void M a i n ()
{ int int
* CharacterBuffer Index;
= stackalloc
int
[5];
for (Index = 0; Index < 5; Index++) CharacterBuffer[Index] = Index; for(Index = 0; Index < 5; Index++) Consolé.WriteLine (CharacterBuffer[Index] ) ;
} } Tras la palabra clave stackalloc se escribe un tipo de dato. Devuelve un puntero al bloque de memori a al que se le asigna el espacio y se puede usar la memori a exact ament e igual que la memori a gest ionada por el CLR. No hay una operación explícita par a liberar la memoria asignada por la p a l a bra clave stackalloc. La memoria se libera aut omát icame nte c uando finaliza el método que asignó esa memoria.
Resumen El modo no seguro de C# permite a su codigo t raba jar directamente con la memoria. Su uso puede mej orar el rendimiento porque el código accede di rec ta mente a la memoria, sin tener que moverse con cuidado por el CLR. Sin embargo, el modo no seguro puede ser peligroso y puede hacer que el codigo falle si no t rabaj a adecuadament e con la memoria. En general, evite el uso del modo no seguro de C#. Si necesita un poco más de rendimiento p ar a su código o si está t raba jando con código heredado de C o C ++ que necesita que especifique una posición de memoria, siga con el modo seguro que se ofrece por defecto y deje que el C L R gestione los detalles de la asignación de memoria.
419
La p eque ña reducción en el rendimiento que se produc e es c omp en sa da con creces por no tener que realizar la pesada tarea de gest ionar la memori a de su código y por conseguir la posibilidad de escribir código libre de errores relaciona dos con la gestión de memoria.
420
Constructores avanzados de C# En este capítulo exa mi na remos algunas facetas interesantes del lenguaje C#. Ta mb i én veremos algunos ejemplos de código y apr end er emo s por qué el codigo funciona como lo hace. La c omprensión de p robl emas de p r og ramaci ón como los presentados en este capítulo le a yu da rán a enfrentarse a sus propias dudas sobre pr ogr amaci ón en C#. En primer lugar, observe la función de conversión implícita de C# y como se aplica a objetos de clases derivadas a las que se accede como objetos de la clase base de la clase derivada. Recuerde que puede escribir métodos de operadores implícitos que definan c ómo se gestionan las conversiones implícitas de un tipo u otro; pero, como verá, las cosas se vuelven un poco más compl icadas cuando se t rabaj a con tipos de tiempo de ejecución y de tiempo de compilación. A continuación, nos adentr aremos en la inicialización de estructuras. Las es tructuras. al igual que las clases, pueden contener campos y propiedades. Sin embargo, la inicialización de est ructur as con campos se realiza de forma ligera mente diferente a la inicialización de est ructuras con propiedades. En este c a pí tu lo, descubri rá por que y cómo resolver este problema. En la t ercera parte de este capítulo, investigaremos el paso de un objeto de una clase derivada a una llamada de método en el que se espera un objeto de una clase base. Dado que los objetos de las clases derivadas son inherentemente objetos de la clase base, p a sa r un objeto de clase derivada a un elemento que espera una
423
clase base puede parecer bastante sencillo. En este apart ado estudiaremos por qué esta técnica no es tan simple como podría parecer. Finalmente, nos adentr aremos en el uso a va nzad o de los indizadores de clase. En la inmensa mayoría de los casos, los indizadores que escriba servirán para hacer que una clase se comport e como una matriz de elementos. Por lo general, las matrices aceptan valores enteros como p ar a especificar el elemento del índice. En este a pa rt ad o estudiaremos la técnica de usar tipos de datos distintos de ente ros par a los índices de matriz.
Operadores implícitos y conversiones no válidas Recuerde que las clases pueden contener un código de conversión de op er ad o res implícitos. Los operadores implícitos se usan p ar a convertir un tipo en otro sin ningún código especial. En el lenguaje C# se crean muchas conversiones im plícitas. Por ejemplo, un entero puede ser convertido implícitamente en un entero lonq sin ningún codigo especial: Mylnt - 12 3; lonq MyLonglnt
= Mylnt;
C# no define conversiones implícitas p ar a todas las combinaciones existentes de tipos de datos Sin embargo, puede escribir código de conversión de oper ado res implícitos que indique al entorno c omún de ejecución (CLR ) cómo debe com port arse cuando un usuario de la clase intente hacer una conversión implícita entre la clase y otro tipo. En este apa rt ado estudiaremos una faceta del operador de conversión implícita que t rat a con la conversión entre dos clases diferentes. El listado 20.1 contiene dos clases: TestClass y MainClass. La clase MainClass contiene el método Main ( ) de la aplicación. La clase TestClass contiene una variable privada de tipo MainClass. Ta mb i én define un método de o p e r a d o r i mplí ci to que c onv ie r te los ob jet os T e s t C l a s s en objetos MainClass. La i mplementación de o per ado r implícito devuelve una referencia al objeto privado MainClass del objeto TestClass. Listado 20.1. E xcepciones a las conversiones inválidas con operadores implícitos public
class
TestClass
{ prívate public
MainClass
MyMai nC la ss Ob je ct ;
T e s t C l a s s ()
{ MyMainClassOb]ect
}
424
= new MainClass () ;
public
static
implicit
operator M a m C l a s s ( T e s t C l a s s
Source)
{ return
Source. M y M a m C 1 assOb] ect;
} } public
class MainClass
( public
static
void M a i n ()
{ ob]ect MyOb]ect; M a m C l a s s MyMainClassOb]ect ; MyObject - new T e s t C l a s s (); M y M a m C l a s s O b j ect = (MainCl ass )MyObj ect ;
} } El código del m é t o d o M a i n () crca un nuevo objeto Tes te 1ass y convierte explícitamente el objeto en otro objeto de tipo MainClass. Como en TestClass se define un o per ador implícito, esta conversión debe tener éxito. Sin embargo, si ejecuta el listado 20.1. recibirá el siguiente mensaje en la consola: Excepción no controlada: System.InvalidCastException: conversión especificada no es valida, at M a i n C l a s s . M a i n ()
La
¿Por qué el C L R considera que la conversión es invalida, incluso cuando se define un oper ado r implícito.) El p rob lema aquí es que la conversión funciona entre tipos de tiempo de compilación, no entre tipos de tiempo de ejecución. El método Main ( ) crea un nuevo objeto de tipo TestClass y asigna el nuevo objeto a la variable de tipo object. A continuación, esta variable se conv ierte en un tipo de clase MainClass. C omo el objeto fue cr eado como un objeto de tipo TestClass. puede esperar que la conversión explícita convierta un objeto de tipo TestClass en un objeto de tipo MainClass. Sin embargo. C# no realiza conversiones explícitas basadas en el tipo usado en el momento de crear el objeto. En su lugar, realiza conv ersiones basadas en el tipo de variable que contiene el nuevo objeto. En el listado 20 1. el nuevo objeto se asigna a una variable de tipo object. Por tanto, el compi lador de C# genera código que conv ierte un objeto de tipo ob j ect a tipoMa inClass. C omo no se ha definido una conversión de ob ject a M a i n C l ass. no se produce la c onve r sión explícita.
Inicialización de estructuras Como sabe, las est ructur as pueden contener constructores de lenguaje que también se encuent ran en las clases, incluyendo métodos, ca mpos y propiedades Para a si gnar valores a los métodos y c ampos de una est ructura se usa una simple
425
instrucción de asignación. Sin embargo, es importante recordar que la diferencia entre una propiedad y un campo es que si se asigna un valor a una propiedad, se ejecuta el código compilado en la estructura. Esto significa que se debe tener especial cuidado cuando se asignan valores a propiedades de est ructur as recién creadas. Este capitulo estudia este tema.
Cómo inicializar estructuras El listado 20.2 contiene dos estructuras. La pri mera recibe el nombre de StructWi thPublicMembers y contiene dos mi embros públicos enteros lla mados X e Y. La segunda estructura se llama StructWithProperties v contiene propiedades publicas llamadas X e Y. que gestionan dos números enteros privados. El m é t o d o Ma in ( ) del listado 20.2 crea dos variables, una que tiene el tipo de estructura StructWithPublicMembers y otra que tiene el tipo de es tructu ra StructWithProperties. El código asigna valores a los miembros X e Y de cada estructura. Listado 20.2. Cómo acceder a las estructuras con propiedades y métodos públicos public
struct
StructWithPublicMembers
{ public public
int X; int Y;
} public
struct
StructWithProperties
{ prívate prívate public
int int
PrivateX; PrívateY;
int X
{ ge t
{ return
PrívateX;
} set { PrivateX
= valué;
} } public
int Y
{ ge t
{ return
}
426
PrivateY;
set
{ PrivateY
= valué;
} } }
public
class MainClass
{ public
static void M a i n ()
{ S t r u c t Wi th Pu b1icMembers MembersStruct; StructWithProperties P r o p e r t i e s S t r u c t ; M e m b e r s S t r u c t .X = MembersStruct .Y = Properti es St ru ct .X PropertiesStruct .Y
100; 200 ; = 100; = 200 ;
} } El listado 20.2 no se compila. El compi lador de C# emite el siguiente error: error CS0165: Uso de 'PropertiesStruct'
la variable
local
no asignada
Lo mas interesante de este error es que hace referencia a la segunda variable de est ructur a definida en el método Main ( ) . Sin embargo, el compi lador de C # compila el código que funciona con la pri mera variable de estructura. En otras palabras, se puede acceder a los miembros públicos del listado 20.2. pero no a las propiedades publicas del listado 20.2. ¿Cual es la diferencia.'
Cómo resolver los problemas con la inicialización La respuesta corta es que el código debe us ar el operador new para crear una nueva instancia de estructura. El listado 20.2 se compila si se crean nuevas refe rencias de estructura: StructWithProperties PropertiesStruct S t r u c t W i t h P r o p e r t i e s ();
= new
Est a r e s p u e s t a tiene que ver con el hecho de que las p r o p i e d a d e s son implementadas por el c ompi lador de C# como funciones publicas cuando genera el codigo del lenguaje intermedio de Microsoft (MS1L) par a la aplicación. Puede c om pr ob a r esto revisando el MSIL generado por el compi lador de C# N E T Framevvork tiene una h e r ra mi en t a l lamada 1LDASM. que son las siglas de desensamblador de IL. Se puede usar esta herramienta para exami nar el Lenguaje
427
intermedio (IL) y los met adatos de cualqui er r esultado binario c ompatible con el C L R de un compilador NET. Modifique el listado 20.2 p ar a que incluya la nueva operación y compílelo en un ejecutable llamado Listing2 0-2 .exe. Tra s generar el ejecutable, puede obs er var su contenido con I L DAS M. Use la siguiente línea de comando para iniciar el des ens ambla dor IL con el ejecutable del listado 20.2: íldasm
Listing20-2.exe
La f i g u r a 20.1 m u e s t r a la v e n t a n a I L D A S M a b i e r t a con el ej ec ut abl e
Listing20-2 .exe. El ejecutable contiene el manifiesto, que i n c l i n e la infor mación de identificación p ar a el ejecutable y también contiene met adatos que describen el código del ejecutable. ^lnjxj
f Listing20-2.exe - IL DASM File
View
Help
Listing20-2.exe M A N I F E S T MainC lass ►
class p u b lic au to ansi beforefieldm it
Ü
c to r
vo id
J
M ain
v o id ;'
Struct W ith Properties ►
class v a lu e p u b lic se q u e n tia l ansi se a le d beforefieldm it
►
e x te n d s im scoriib: System P n va te X
p riv a te int 32
P rivate Y
p riv a te int 32
Value Type
3t get _X int 3 2 C IB g e t _Y int 22 ’ Ü
s e t_ X
v o id ;in t3 2 '
XI
set 'r'
v o id ;in t3 2 ;
A
X
A
y in s ta n c e int 32 f
in s ta n c e int 32;’
Struct W ith P ublic M em bers ►
class v a lu e p u b lic s e q u e n tia l ansi sealed beforefieldm it
^
e xte n d s [m sco riib ’ System v a lu e Type X
p u b lic int 3 2
y p u b lic int 3 2 assembly "Listing20-2"
_ü
Jj
Figura 20.1. ILDASM abierto con el ejecutable del listado 20.2
I L D AS M muest ra las clases y est ructuras del ejecutable en forma de árbol. La parte del árbol StructWithPublicMembers muest ra dos variables publi cas de tipo int32 llamadas X e Y. Estas variables reflejan las dos propiedades progr amadas en la est ructura. La parte del árbol StructWithProperties muest ra las dos variables privadas; también mues tra cuatro métodos que no se escribieron en la estructura: int32get_X() i n t3 2 g e t _ Y ( )
428
•
void set_X(int32) void set_Y(int32)
Estos métodos realmente implementan el acceso a la propi edad de la e s t r u c t u ra. El método get X ( ) contiene el código del descri ptor de acceso get de la propiedad X y el método get Y ( ) contiene el código del des cri ptor de acceso get de la propi edad Y. Del mi smo modo, el método set X () contiene el código del descriptor de acceso set de la p ropiedad X y el método set Y ( ) contiene el código del descri ptor de acceso set de la propiedad Y Esto significa que cuando el código accede a una propiedad, en realidad está llamando a un método que implementa la f uncionalidad de la propiedad. El probl ema con el listado 20.2 es que la variable PropertiesStruct no esta inicializada con un oper ador new antes de ser usada. Esto significa que la variable no está asociada a una instancia de es tructur a y los métodos no pueden ser llamados en instancias que no existen. Las instrucciones de la propi edad del método Main ( ) obligan a que se llame a los métodos de propiedades subyacentes, pero la l l amada no e ncuent ra ninguna instancia. El c ompi lador de C# detecta este probl ema y emite el mensaje de error mostrado tras el listado 20.2. La inicialización del mi embro público tiene éxito porque las clases y las es truct uras pueden inicializarse directamente, sin usar un constructor, siempre y cuando se haya dado explícitamente un v alor a todas las v ariables de la instancia.
Clases derivadas Cuando t rat amos las clases de C# v imos como las clases pueden deriv arse de otras clases. Las clases derivadas heredan la funcionalidad de su clase pri mari a o base. La relación entre una clase derivada y una clase base recibe el nombre de una relación "es un" en términos orientados a objetos. Por ejemplo, todas las clase de C# deriv an en última instancia del tipo Sys t e m .Ob j ect de NET. de modo que se puede decir que la clase "es un" Sys tem .Ob j ec t . Como todas las clases deriv adas heredan funcionalidad de su clase base, po de mos pres uponer que los objetos de una clase derivada pueden ser usados en c u al quier lugar donde se pueda usar un objeto de la clase base de la clase. Sin embargo, la seguridad de tipo integrada en C# tiene preferencia y el código de este a part ado explica este tema.
Cómo pasar clases derivadas El listado 20.3 contiene una clase l lamada TestClass que contiene un m é todo llamado Test ( ) . El método Test ( ) acept a una referencia a un objeto como parámetro.
429
El listado 20.3 también contiene una clase llamada MainClass. que crea un objeto de la clase TestClass y una cadena. La cadena se usa como parámetro par a la llamada al método Test ( ) del objeto TestClass. Listado 20.3. C ó m o p a s a r c a d e n a s d o n d e se e s p e r a n o b j e t o s public class TestClass
{ public
void
Test(ref
ob]ect
O bjectReference)
{ } } public
class MainClass
{ public
static
void M a i n ()
{ TestClass TestObject = new TestClass (); string TestString = "Helio from C# !" ; TestObject.Test (ref
TestString) ;
} } El listado 20.3 no se compila. El compi lador de C# emite los siguientes erro res: Lis 1 1 n g 2 U- 3.c s (1t , S ) : error C S 15 0 2 : La me jor coincidencia de método sobrecargado para T e s t C l a s s .T e s t (ref object)' tiene algunos argumentos no validos Listing20-3.cs (le, 25) : error CS1503: Argumento convertir de 'ref string' a 'ref object'
'1' : no
se puede
A pri mera vista, no parece logico. Como cada tipo de dato de N E T es un objeto, cualquier tipo de dato debería conv ertirse en ultima instancia en una v a riable de tipo ob] ect . Esto debería incluir a los objetos string. ¿Por que no se puede convertir implícitamente el objeto string en una variable de tipo
object'.'
Cómo resolver problemas que surgen cuando se pasan clases derivadas El p robl ema de compilación surge de las estrictas reglas de seguridad de tipos integradas en el lenguaje C#. El método Test ( ) del listado 20.3 toma el valor de tipo object como p ar amet ro y el compi lador emite un error si se le proporciona algo distinto de un objeto. Otro problema con la estrategia del listado 20.3 proce de del mo di fi cador de p ar am et ro ref us ado en el método Test (). El modifica-
430
dor ref permite que la implementación del método cambie el valor de la variable v el cambi o es visible p ar a el invocador. Esto le da permiso al método Test ( ) par a sobrescribir la variable Ob j ectRef erence con cualquier valor que p u e da v ol ver a c o n ve r ti rs e e xp l í ci t a m e n t e en un objeto. O b s e r v e la si gui ent e i mplement aci ón a lt er nat iva del método Test (): public
void
Test (ref
ob]ect
O bj ec tReference)
{ Téstelass
TestObject;
TestObject = new TestClass () ; Ob]ectReference = (object)Tes tO bj ec t;
} En esta implementación. se crea un objeto de clase T e s t C l a s s . La variable de referencia Obj e c t R e f e r e n c e se asigna al nuevo objeto y el invocador ve esta asignación. El probl ema es que el listado 20.3 pasa una cadena al método T e s t ( ) . Con esta nueva i mplementación del método T e s t ( ) situado en el listado 20.3. el i nvocador podría p a s a r una cadena y re cu per arí a un objeto T e s t C l a s s . El invocador podría no esperar que la variable ca mbi ara a un tipo diferente del s umi nist rado y podrían surgir numerosos probl emas si el inv ocador siguiera t raba jando con el código presuponiendo que la v ariable sigue siendo una cadena. C # evita este p robl ema exigiendo que solo se usen tipos de par ámet ros correctos cuando se inv oquen métodos.
Cómo usar no enteros como elementos de matriz El lenguaje C# especifica que sólo se pueden us ar enteros como elementos de matriz. Sin embargo, a veces se encontr ará en situaciones en las que los enteros no son el modo más conveniente p ar a e xpr es ar un índice de mat ri z en el codigo. Por ejemplo, imagine un tablero de ajedrez en el que una letra desde A hasta H hace referencia a una de las ocho columnas del tablero y un número de 1 a K hace referencia a una de las ocho filas del tablero. Si necesita representar un tablero de ajedrez en el código de C#. podría escoger usar una matriz rect angul ar de dos dimensiones: in t
[,]
Chessboard;
Chessboard
= new int
[8,8];
Tras inicializar la matriz del tablero de ajedrez, querrá hacer referencia a una casilla usando la tradicional sintaxis par a las casillas del tablero de ajedrez Q ui zás quiera hacer referencia a la casilla B7. por ejemplo, de esta manera. C h e s s b o a r d [ 'B ' , 7] ;
431
Sin embargo, no puede hacer esto porque C# no permite el uso de caracteres, como la B del ejemplo, como referencias de elementos de matriz. El listado 20.4 usa un indizador p a r a solucionar este problema: Listado 20.4. Cómo representar un tablero de ajedrez con un indizador using
System;
public
class
Chessboard
{ private public
in t
[,]
Board;
C h e s s b o a r d ()
( Board = new i n t [8,8];
} public
int
this
[char Column,
{ get
{ int
Columnlndex;
switch(Column)
{ case 'A ' : case 'a ' : Columnlndex break;
case case
= 0;
' B' : 'b' :
Columnlndex
=
1;
break; case case
'C ' : 'c ' :
Columnlndex break; case 'D ' :
case
'd' :
Columnlndex brea k ; case 'E ' : case 'e ' : Columnlndex break;
case
= 3;
= 4;
' F' :
case 'f ' : Columnlndex break; case 'G' : case 'g ' : Columnlndex
432
= 2;
= 5;
= c;
int Rowlndex]
break; c a s e ' H' : case 'h': Column Index = 7; break; default: throw new Exception("Illegal
column
specif ie r. ") ;
} C o n s o l e . W r i t e L m e (" (returning cell [ {0 ) , { 1} ] " , Columnlndex, Rowlndex) ; return Board[Columnlndex, Rowlndex];
public
class
MamClass
{ public
static
void M a i n ()
{ int CellContents; Chessboard MyChessboard CellContents
= new C h e s s b o a r d ();
= MyChessboard
[ 'B' , 7] ;
} } El código del listado 20.4 declara una clase llamada Chessboard para re presentar un tablero de ajedrez. La clase i nc lu\ e una matriz de enteros privada de dos di mensiones l lamada Board. que puede usarse p ar a representar que piezas de ajedrez están en qué casillas del tablero. (Par a mant ener el listado del codigo. la matriz no se usa realmente.) La clase también implementa un indizador con un descri ptor de acceso get. El indizador acepta dos parámetros: un carácter y un entero. El indizador da por hec ho que el c a r á c t e r e s p e c i f i c a d o en el p r i m e r p a r a m e t r o r e p r e s e n t a un identificador de col umna y traduce el carácter como una col umna de la matriz priv ada. El especificado!' de col umna A lo t raduce como col umna 0 en la matriz privada Board. El especi fi cador de col umna B lo t raduce como col umna 1 en la matriz privada Board v así sucesivamente. El indizador escribe un mensaje en la consola que especifica los elementos de indizador t raduci dos y luego devuelve el valor de los elementos de la matriz a los que hacen referencia los par ámet ros del indizador. El método Maí n ( ) del listado 20.4 crea un nuev o objeto de tipo Chessboard y usa el indizador par a hacer referencia a la casilla B7: CellContents
= MyChessboard
[ 'B', 7];
Cua nd o el código del listado 20.4 se ejecuta, el codigo del método Main ( ) llama al indizador. que escribe lo siguiente en la consola: (retur n m g
cell
[1,7]
433
El indizador tradujo el primer parámetro. B. en la referencia de columna b as a da en enteros que el compi lador de C# necesita p ar a acceder a un elemento de la matriz privada. Este esquema permite diseñar clases que usan una sintaxis natu ral para la aplicación (en este caso, indizadores de elementos de matriz basados en caracteres) mientras aún cumplen los requisitos de C# de usar indizadores de elementos de matriz basados en enteros.
Resumen El mejor modo de resolver un difícil probl ema de prog ramaci ón de C# es intentar diferentes métodos de codificación con el compilador. No tenga miedo de experimentar. El compi lador de C# le avi sar á si hace algo equivocado. Tambi én podria considerar el uso la herramienta I LD AS M para colarse en sus ensamblados y v er cómo el compilador de C# crea codigo ejecutable a partir de su código fuente. La comprensión del código que genera el compi lador de C# puede ayudarle a entender por que el código funciona de la manera que lo hace. En este capitulo, aprendió que el código descriptor de acceso de propiedades se convierte en métodos especiales por el compilador de C#. Esto puede ser difícil de descubrir sin observ ar el codigo generado por I LD ASM . La herramienta I LD AS M funciona con cualquier ensamblado generado por un compilador de N E T que genere MSIL Examine con I L D AS M algunos ensambl ados y aplicaciones de NET. Abralos con la herramienta y vea cómo están construidos. El análisis de otros ens ambl a dos v su contenido puede darle una mejor comprensión de cómo funciona el códi go N E T y puede ayudarle a ser un mejor p rog ramado r NET.
434
Parte IV
Cómo desarrollar soluciones .NET usando C#
435
Cómo construir aplicaciones WindowsForms m í
La mayor parte del material escrito sobre N E T Framework se centra en la a yu da que reciben los pr og ramado re s que escriben aplicaciones par a Internet. El motor A S P . N E T y los modelos de desarrollo del "software como un servicio" son. sin lugar a dudas, unas potentes herramientas para el desarrollo de aplicaciones de Internet. Sin embargo. N E T Framework no t rata sólo con Internet. Microsoft se dio cuenta de que. aunque muchos pro gr amado re s están escri biendo aplicaciones par a Internet, otros muchos se dedican a desarrol lar ap li ca ciones de escritorio al estilo Wi n32. N E T Framewo rk no ha olvidado a estos programadores. Incluye un conjunto de clases de N E T que facilita el desarrollo de aplicaciones Windows de escritorio que usan un lenguaje compatible con NET Este conj unt o de clases y el modelo de p r o g r a m a c i ó n que admi te se llama Wi ndowsForms. En este c a p í t u l o e s t u d i a r e m o s la e s t r u c t u r a b á s i c a de u n a a p l i c a c i ó n Wi ndows Forms . Verá cómo crear una forma básica y cómo añadir controles a los formularios. Exami nar emos las clases de N E T Framewo rk que puede usar una aplicación W i nd ow sF o rm s y tambi én estudiaremos al gunos de los at ri but os de ensambl ado que se pueden usar par a a gr egar la información de versión y derecho de a utoría a sus aplicaciones.
437
Arquitectura de WindowsForms Para pr ogr a ma r una aplicación Wi ndows Forms es necesario comprender cómo se usan las clases base de Wi ndows Forms con las aplicaciones N E T Este capítu lo exa mi na la arquit ect ura de la biblioteca de clases de Wi ndows Forms . Todas las clases que se usan p ar a construir aplicaciones de Wi ndo ws Fo rms se incluyen en un espacio de n ombre de N E T F ra me wo rk llamado System. Windows .Forms. Este espacio de nombre contiene todas las clases necesarias par a construir elaboradas aplicaciones de escritorio p ar a Windows. Estas clases permiten t rab aj ar con formularios, botones, controles de edición, casillas de veri ficación. listas y muchos otros elementos de interfaz de usuario. Todas estas c l as e s e s tá n a su d i s p o s i c i ó n listas p a r a ser u s a d a s en sus a p l i ca c io ne s Wi ndowsForms. Las aplicaciones W i n do ws F or m s usan dos clases fundamentales de NET Framework: La clase Form. que gestiona los formularios de la aplicación \ los controles del formulario, y la clase Application, que gestiona el modo en que la aplicación controla los mensajes Wi ndows enviados y recibidos de los formula rios de la aplicación. Estas dos clases se i n cl i n en en el espacio de nombre System. Windows .Forms de N E T Framework y componen la estructura basica de una aplicación Wi ndo ws Fo rms .
La clase Form El espacio de nombre S y s t e m .Windows .Forms i ncl i ne una clase base llamada Form. La clase Form representa una forma o ventana de su aplicación Al crear una aplicación de W i n d ow sF o rm s en C#. se diseña una clase de ventana y se usa la clase Form como clase base par a la clase de ventana. Esta clase de v en tan a hereda todo el c o mp or t a mi e nt o b ás ico de una vent ana y agr ega la funcionalidad necesaria par a la aplicación. Todos los comport ami ent os básicos de ventana están integrados en la clase base Form y esos comport ami ent os se heredan a ut omát icame nte si se deriva una clase de ventana de la clase Form.
La clase Application La s clases Form definen el aspect o y c omport ami ent o de las ventanas de una aplicación paro no se ejecutan por sí mismas. W in do ws F or ms debe ejecutarse d en tro del c on t ex t o de u na ap li ca ci ón . El e s p a ci o de n o m br es System. Windows .Form.s incluye una clase llamada Application, que contiene métodos que ayudan a gest ionar las aplicaciones Wi ndo ws Fo rms . La clase Application contiene métodos que permiten iniciar, gestionar v detener las aplicaciones WindowsForms. W i n do ws F o r ms responde a eventos iniciados por el usuario, como mover el ratón o pul sar una tecla del teclado.
438
Desde los comienzos de Wi ndows, la ar quit ect ura de las aplicaciones de escri to rio de Wi nd ows ha sido diseñada sobre el concepto de bucle de mensajes. En una aplicación Wi ndows estandar. la parte mas importante del codigo se incluye en un bucle y recibe mensajes del sistema operativ o Wi ndows. Cu and o el usuario mueve el ratón, pul sa una tecla o realiza alguna otra operación sobre la que puede a ctuar una ventana, el sistema operativo toma nota de la acción y envía un mensaje a la v ent ana correspondi ent e informándole de la acción. Es el codigo de la v ent ana el que debe utilizar convenientemente el mensaje. En la ar quit ect ura WindovvsForms. los conceptos básicos siguen siendo los mismos. Las aplicaciones WindovvsForms tienen un fragment o de codigo que espera la llegada de mensajes de interacción del usuar io desde el sistema o pe r at i vo y luego los envían a la ventana apropiada. En la arquit ect ura WindovvsForms. la clase Application es el código principal que gestiona este control de m e n sajes y la clase Forms es la clase que controla los mensajes que env ía la clase
Appl i ca t ion. La clase Application está sellada. No se pueden derivar clases de ella. Sus métodos son estáticos, lo que significa que pueden ser llamados sin que haya un objeto de la clase Application disponible.
Cómo crear la primera aplicación WindowsForms El listado 21.1 mues tra una sencilla aplicación WindovvsForms. Representa una sencilla aplicación W ind ows For ms que abre un sólo formulario y sigue ejecu tándose hasta que el usuar io cierra el formulario. Listado 21.1. La aplicación Helio, W in d o w s F o rm s using
Syst em .W in dow s.F o r m s ;
public
class
SimpieHe11oWorId
: Form
{ public
static void M a i n ()
{ A p p l i c a 1 1 o n .Run (new
S impleHe11oWor1d () ) ;
} public
Sim p1eHe 11oWorId ()
{ Text
=
"Helio,
W i n d o w s F o r m s !";
} 1 El código del listado 21.1 declara una clase llamada SimpleHelloWorld. Se deriva de la clase Form de N E T Framework. que califica la clase como una
439
clase Windows Form. La clase contiene un método Main ( ) . que crea un nuevo objeto de la clase SimpleHelloWorId y usa ese objeto como parámetro del método en la clase Application llamado Run ( ) . El constructor de la clase S impleHe 1 ] oWor id establece el valor de la propiedad heredada Text a He 1l o , Windows Forms ! Esta cadena se muestra como el titulo del for mu lario. La figura 21.1 muest ra el aspecto del formulario cuando se ejecuta el codigo del listado 21.1.
Figura 21.1. El listado 2 1 1 muestra un sencillo formulario
Cómo compilar una aplicación WindowsForms Al igual que las aplicaciones de consola diseñadas usando C#. las aplicaciones W i nd ow sF o rm s deben ser compi ladas con el c ompi lador de C# antes de poder ejecutar su código. Las aplicaciones Wi ndowsForms compiladas con el compilador de C# pueden comport arse de f orma ligeramente diferente al ser iniciadas, depen diendo del modo en el que se compiló el código. La siguiente línea de comando es la mas simple que se puede usar p ar a compi lar el código del listado 2 l . I : c s c L is t in g 2 1- 1 .c s
Como todas las aplicaciones de C#. este código genera un ejecutable llamado Li st inq2 1-1 .e;:e. Si ejecuta este archivo desde una consola, aparecera la ventana de la aplicación. Sin embargo, ocurre algo interesante cuando se hace doble clic sobre el icono del ejecutable en el expl orador de Wi ndows. Si se inicia el ejecutable desde el Expl orador de Wi ndows aparecen dos v entanas: una v enta na de consola, que esta vacia y la ventana WindowsForms de la aplicación. Este comport ami ent o es diferente de la mayorí a de las aplicaciones Windows. La mayorí a de las aplicaciones Wi ndows no presentan una ventana de consola vacía
440
cuando se inicia la aplicación. (,Por que el ejecutable del listado 21.1 muestra una vent ana de consola y. lo más importante, como podemos el i mi na rl a‘} Por defecto, el compilador de C# genera aplicaciones de consola. Estas apl ica c i o n e s n e c e s i t a n u n a v e n t a n a de c o n s o l a p a r a q u e los m é t o d o s , c o m o Consolé .WriteLine () . dispongan de una consola en la que escribir sus mensajes. El tiempo de ejecución de N E T consulta el ejecutable cuando es inicia do y si el ejecutable está compilado como una aplicación de consola, el cargador del tiempo de ejecución crea una nueva vent ana de consola par a la aplicación. Si se compi la el listado 21.1 mediante el compi lador de C# por defecto, se generara una aplicación N E T para consola. Esto explica la ventana de consola vacia que aparece cuando se ejecuta el ejecutable compilado. WindovvsForms t ra ba j a con ventanas, no con consolas y las aplicaciones WindovvsForms no necesitan la ventana de consola. El compi lador de CU admite un a r gu me nt o de linea de co man do ll amado target que se usa p ar a especificar el tipo de la aplicación que se quiere compilar. Puede usar este ar gument o para indicarle al compi lador de CU que quiere compi lar una aplicación Wi nd ows que no necesita una consola. La siguiente línea de comando: ese /target :win e x e L i s t m g 2 1 - l . c s
ordena al compi lador de CU que compile el archivo L i s t i n g 2 ] - 1 . e s y use sus contenidos para crear un ejecutable Wi ndows que no necesita una c on sola. Si usa esta línea de c omando p ar a compilar el código del listado 21.1 e inicia el ejecutable desde el expl orador de Wi ndows, ap arec erá el formul ari o de la aplicación, sin que apa rez ca también una ventana de consola vacía. N O T A : El compilador de C# acepta /t como abreviatura de /target. La anterior línea de comandos puede acortarse a ese /trwinexe
L i s t i n g 2 1 - 1 .es.
Ensamblados: cómo añadir información de versión a las aplicaciones WindowsForms Por defecto, las aplicaciones W i n d o w s F o r m s no contienen i nformaci ón de su v ersión. Sin embargo, se pueden usar atributos integrados en N E T Framevvork para añadir información de la versión a las aplicaciones. Estos atributos empi e zan a ac tua r en el nivel de ens ambl ado y se añaden al código que produce el compilador de CU. Todos estos atributos son opcionales, pero su uso añade infor mación de versión y de derechos de autoría a los binarios NET. Al añadir esta información se permite a los usuarios finales usar el Ex pl orado r de Windows, hacer clic con el botón derecho en la aplicación, seleccionar Propiedades en el menú contextual e inspeccionar los valores del atributo incrustado. Los usuarios
441
finales pueden ex ami na r la información de la versión y de derechos de a utoría de las aplicaciones N E T WindowsForms ex act ament e del mi smo modo que pue den exa mi na r dicha información en las aplicaciones Wi n32 estándar. Las clases que implementan estos atributos están en un espacio de nombre NE T llamado Sys tem .Ref lect ion. C uando se usan estos atributos. ha\ que hacer referencia a este espacio de nombre con la p al abr a clave using o a nt eponer el nombre del espacio de nombre a los nombres de los ensamblados. Parte de la información especificada en estos atributos se escribe en el mani fiesto del ens ambl ado y otros fragment os de información se al mac enan como recurso de información de versión incrust ado en el ejecutable del ensamblado Puede obser var el manifiesto del e nsambl ado mediante la herramienta ILDASM v puede ver el recurso de i nformación de versión del ejecutable haciendo clic con el boton derecho del ratón en el archiv o en el Expl orador de Wi ndows y seleccionan do Propiedades>Versión.
TRUCO: Puede agregar estos atributos a cualquier proyecto de código .NET. La información de la versión siempre está disponible en el producto compilado del código. En este ap art ado estudiaremos los siguientes atributos:
AssemblyTit le. que asigna un título al ensamblado. AssemblyDescr ipt ion. que asigna una descripción al ensamblado. AssemblyConfiguration. que describe las opciones usadas para construir el ensamblado.
AssembiyCompany. que asigna un nombre de compañía al ensamblado. AssemblyProduct. que asigna información del producto al ensamblado. AssemblyCopyright. que asigna información de derechos de autoría al ensamblado.
AssemblyTrademark. que asigna información de marca al ensamblado. AssemblyCulture. que asigna información local al ensamblado. AssemblyVersion. que asigna un número de versión al ensamblado.
AssemblyTitle El atributo AssemblyTitle permite asi gnar un título al ensamblado. El atributo t oma un par ámet ro de cadena en su const ructor que especifica el título, como mues tra el siguiente ejemplo: [assembly:
442
AssemblyTitle ( " L i s t m g
21-2")]
El titulo del ensamblado no se escribe en su manifiesto, pero esta disponible en el c a mp o Descriptíon del bloque de información de versión del archivo c o m pilado.
Assem bly Descri ption El atributo AssemblyDescription permite pro po rci onar una d es cri p ción del ensamblado. El at ri but o toma un par ámet ro de cadena en su constructor que especifica la descripción, como mues tra el siguiente ejemplo: [assembly: A s s e m b l y D e s c n p t i o n ("This executable c o m p i l m g the code in Listín g 21-2.")]
was
produced
by
La descripción del e ns ambl ado se escribe en el manifiesto del ensambl ado; también está disponible en el ca mp o Comment del bloque de i nformación de versión del archivo compilado.
AssemblyConfiguration El atributo AssemblyConfiguration permite especificar información de configuración de compilación del ensamblado. Por ejemplo, la i nformación de configuración de e ns ambl ado puede especi fi car si el ens ambl ado se compilo con configuración par a su distribución o depuración. El atributo toma un parámet ro de cadena en su cons tructor que especifica la información de configuración: [a s s e m b l y : A s s e m b l y C o n f i g u r a t i o n ("retail" ) ]
La descripción del e ns ambl ado se escribe en el manifiesto del ensamblado, pero no esta disponible en el bloque de información de versión del archivo compilado.
AssemblyCompany El atributo AssemblyCompany permite especificar un nombre de compañí a para asociarlo al ensamblado. El atributo toma un par amet ro de cadena en su const ructor que especifica el nombre de la compañía: [assembly:
AssemblyCompany("John
Wiley,
Inc.")]
El nombre de la co mpañí a del en sa mbl ado se escribe en el manifiesto del e n sambl ado; también está disponible en el c a mp o Company Ñame del bl oque de información de versión del archivo compilado.
AssemblyProduct El atributo AssemblyProduct permite especificar un nombre de producto p ar a asociarlo al ensamblado. El atributo toma un par ámet ro de cadena en su
443
cons tructor que especifica el nombre del producto compañía, como muest ra el siguiente ejemplo: [assembly:
AssemblyProduct("C#
Bible")]
El nombre del producto del ens ambl ad o se escribe en el manifiesto del e n sa m blado; también está disponible en el ca mpo Product Ñame del bloque de información de versión del archivo compilado.
AssemblyCopyright El atributo AssemblyCopyright permite especificar información de dere chos de aut orí a par a el ensamblado. El atributo toma un par ámet ro de cadena en su cons tructor que especifica la información de derechos de autoría, corno mues tra el siguiente ejemplo: [assembly:
A ss e m b l y C o p y r i g h t (" (c)
2002
John Wiley,
Inc.")]
El nombre del prod uc to del ens ambl ado se escribe en el manifiesto del ens am blado; también está disponible en el c a mp o Copyright del bl oque de informa ción de versión del archivo compilado.
Assem blyTrademark El atributo AssemblyTrademark permite especificar información de m a r ca registrada p ar a el ensamblado. El atributo t oma un p ar ámet ro de cadena en su const ructor que especifica la i nformación de m ar c a registrada, como muest ra el siguiente ejemplo: [assembly: AssemblyTrademark("Windows Microsoft Corporation.")]
ís
a trademark
of
El nombre del producto del e ns ambl ado se escribe en el manifiesto del ens am blado; también está disponible en el ca mp o Legal del bl oque de información de versión del archivo compilado.
AssemblyCulture El atributo As semblyCul ture permite especificar información de referen cia cultural par a el ensamblado. La información de referencia cultural especifica la información de lenguaje v pais que el ensamblado usa para hacer su trabajo. El atributo t oma un parámetro de cadena en su cons tructor que especifica la i nformación de referencia cultural, como mue st ra el siguiente ejemplo: [assembly:
444
As se mb ly Cu lture("us-en")]
Las cadenas de referencia cultural son definidas mediante un estándar de Internet llamado RFC 1766. El est ándar se titula Tags for the Identification of Languages y está disponible en Internet en www.ietf.org/rfc/ rf cl7 66 .txt. El nombre del pro du cto del e ns ambl ado se escribe en el m a n i fiesto del ensamblado; t ambién está disponible en el campo Legal Trademarks del bloque de información de versión del archi vo compilado.
NOTA: La información de referencia cultural sólo se puede añadir a bi bliotecas y módulos. No puede añadirse a ejecutables, porque los ejecutables no pueden ser localizados. Si se intenta añadir el atributo AssemblyCulture al código base que se compila en el ejecutable final, el compilador de C# emite el siguiente mensaje de error: error CS0647: Error al emitir el atributo 'S y s t e m .Ref 1e c t i o n .A s s e m b ly Cu lt ur eA tt ri bu te '— 'Los archivos ejecutables no se pueden adaptar y no deben tener referencia c u 1 tu r a 1'
La descripción del ens amb la do se escribe en el manifiesto del ensamblado, pero no está disponible en el bloque de información de versión del archivo c ompi lado.
AssemblyVersion El atributo AssemblyVersion permite asi gnar un número de versión al ensamblado. Los números de versión de N E T constan de cuatro partes: Un n úmero de versión principal •
Un número de v ersión secundari a Un n úmero de rev isión Un número de compilación
C ad a una de estas partes está s eparada por un punto. El atributo toma un p ar ámet ro de cadena en su const ructor que especifica el número de versión, como m ues tra el siguiente ejemplo: [assembly:
A s s e m b l y V e r s i o n ("1.O .O .O " )]
Siempre se debe especificar un número de versión. Si se especifican los nú me ros de versión principal y secundaria, puede dejar que el compilador de CU genere automát icamente los otros números al compilar el código. Esto puede ser útil si se quiere que c ada compilación del código tenga un número de versión único. Si usa el carácter asterisco p a ra un número de compilación, el compi lador de CU asigna
445
uno automát icamente. El número de compilación generado es igual al número de días desde el 1 de enero del 2000. como mues tr a el siguiente ejemplo: [assembly: AssemblyVersión ("1.0.0.*") ]
Este ejemplo asigna al ensamblado una versión principal igual a 1. una versión secundaria igual a 0. un número de revisión igual a 0 y un número de compilación asignado aut omát icame nte por el compi lador de C#. Si usa el carácter asterisco p ar a un número de revisión, el compilador de C# asigna aut omát i came nt e un número de revisión y un número de compilación. El número de revisión generado es igual al número de días desde el 1 de enero del 2000. como mues tra el siguiente ejemplo: [a s s e m b l y : AssemblyVersión ("1.0.*") ]
Este ejemplo asigna al ensambl ado una versión principal igual a 1. una versión secundari a igual a 0. un numero de revisión igual a 0 y un número de revisión asignado au to mát icame nte por el compi lador de C#. El nombre del p roducto del ens amb la do se escribe en el manifiesto del ensam blado: tambie'n está disponible en los campos Assembly Versión. Product Versión y File Versión del bloque de información de versión del archivo compilado. El listado 2 1.2 añade atributos de versión de ensamblado al código del listado 21.1. Listado 21.2. Sencilla aplicación de formulario W in d o w s con inform ación de versión using us ing
System.Reflection; Syst em .W in do ws .Fo rms ;
[assembly: As s emb 1yT 1 1 1 e (" Iu s t m g 21-2")] [assembly: A s s e m b l y D e s c n p t i o n ( " T h i s executable was produced compiling the code in Listing 21-2.")] As s emblyConfiguration("Retail") ] [asse m b 1y A s s e m b 1y C o m p a n y ("John Wiley, Inc.")] [assembly AssemblyProduct ("C# Bible") ] [asse m b 1y AssemblyCopyr lght (" (cj) 2002 John Wiley, Inc." [asse mb 1y [asse m b 1y As s em b1yVe r s l o n ("1.0.*") ] public
class
Simp 1eH e 11oWo r1d
: Form
{ public
static
void M a i n ()
{ Appi icat i o n .Run(new
public
SimpieHe11o W o r I d () ) ;
S impieHe11oWo r1d ()
{ Text
446
- "Hello,
Windows F o r m s !" ;
by
El objeto Application con más detalle El objeto Application contiene propiedades, métodos y eventos que p u e den usarse en el código WindowsForms . En este ap art ado estudiaremos los miembros de clase más usados.
Eventos Application La clase Application admite cuatro eventos que pueden usarse en las aplicaciones WindowsForms: El evento App licationExit se desencadena cuando la aplicación esta a punto de cerrarse. A este evento se le pueden asi gnar los delegados de tipo EventHandler . El delegado EventHandler esta definido en el espacio de nombre System de NET. El delegado toma dos parámetros: un objeto que hace referencia al objeto que envía el evento y un objeto EventArgs que especifica los ar gu me nt os del evento. No devuelve ni n gún valor. El evento Id le se desencadena cuando la aplicación termina de env iar mensajes desde el sistema operativo y está a punt o de ent rar en estado de inactividad. La aplicación a ba ndona el estado de inactividad cuando consi dera que hay más mensajes que procesar. A este evento se le pueden a si g nar delegados de tipo EventHandler. El delegado EventHandler está definido en el espacio de nombre N E T System. El delegado toma dos parámetros: un objeto que hace referencia al objeto que envía el evento y un objeto EventArgs que especifica los a rgume nt os del evento. No devuelve ningún valor. •
El evento ThreadExcept.ion se desencadena cuando se inicia una e x cepción de s ubproces o no captur ada. A este evento se le pueden asignar delegados de tipo ThreadEzecptionEventHandler. El delegado ThreadExecptionEventHandler se define en el espaci o de n o m bre .NET S y s t e m .Threading. El delegado toma dos parámetros: un objeto que hace referencia al objeto que envía el evento y un objeto ThreadingExcept íonEventArgs que especifica los argument os del evento. No devuelve ningún valor. El evento ThreadExit se desencadena cuando un subproceso está a punto de cerrarse. C uando el proceso principal de una aplicación esta a punto de cerrarse, antes se desencadena este evento, seguido de un evento ApplicationExit. A este evento se le pueden a si gnar delegados de tipo EventHandler. El delegado EventHandler esta definido en el espacio de nombre System de NET. El delegado toma dos parámetros: un objeto que hace referencia al objeto que envía el evento y un objeto
447
EventArgs que especifica los a rgume nt os del evento. No devuelve nin gún valor.
Cómo trabajar con eventos en el código El código del listado 21.3 se suma al código de formulario del listado 21.2 mediante el control de ev entos desde el objeto Appl i cat ion. Listado 21.3. Cómo controlar los eventos de la aplicación u sing using using us inq
Sy s t e m ; System.Threading; System.Reflection; S ystera.Windows.F o rm s;
[assembly: A.ssemblyTitle ("Listing 21-3")] [assembly: As se mb l y D e s c r i p t i o n ("This executable was produced compiling the code in Listing 21-3.")] [assembly: A s s e m b l y C o m p a n y ("John Wiley, Inc.")] [assembly: AssemblyProduct ("C# Bible")] Ía s s e m b l y : A s s e m b 1y C o py right (" (c ) 2002 John Wiley, Inc.")] [a s s e m b 1y : As s e m b 1yVersion ("1.0.*") ] public
class
He 11oWorIdForm
by
: Form
{ pub lie
Hell oW or 1dForm í¡
{ Text
- "Hello,
W m d o w s F o r m s !" ;
} } public
class
A p p 1icationEventHandlerCl ass
{ public:
void
OnApplicationExit (object
sender,
EventArgs
e)
{ try { Console.WriteLine("The
application
is
shutting
} catch(MotSupportedException)
{ } } public
void Onldle (object
sender,
EventArgs
e)
{ Console.WriteLine("The
application
is
idle.");
} public void OnThreadException(object Thr eacJExcept i onEventArgs e )
{
448
sender,
down.");
C o n s o l é . W i i t e L m e i"an threacl was caught !" ) ;
exception
thrown
from
an
applicaticn
} public
void OnThreadExit(object
sender,
EventArys
e)
1 Consolé.WriteLine f"The s h u t t m g down ." ) ;
application' s m a m
thread
ís
} } public
class
MamClass
{ public
static void M a i n ()
{ He 11 oWor ldForm FormOb] ect - new Hel.loWorldFormf ¡ ; ApplicatíonEventHandlerClass AppEvents = new A p p 1 icatíonEventHandlerClass () ; Application.ApplicdtionEy.it +- new Even tHandler (AppEvents.OnApplicationEx it ) ; Application.Idle new EventHandler (AppEvents.O n l d l e ) ; Application.ThreadException +- new Thre ad Ex ce p1 1 onEventHandle r (AppEvent s .OnThreadException) ; A p p l i c a t i o n .ThreadExit new EventHandler (AppEvents.OnThreadExit ) ; A p p l i c a t i o n . R u n ( F o r m ü b j e c t );
} i La nueva clase del listado 21.3 recibe el no mbr e de A p p l i c a t i o n EventHandierClass v contiene métodos que controlan los eventos d es e nc a denados desde el objeto Appl i catión. El método Main ( ) crea un objeto de la clase ApplicationEvent.HandlerCla.ss y agrega su codigo a la lista de controladores de exentos de la clase Application. Los controladores de evento de la clase Appl icat i onEvent Hand IerClass escriben en la c on sola. Esto es perfectamente v alido, incluso en una aplicación W indows Forms. Si compila el código del listado 21.3 usando el destino ejecutable de consola (ese / target :exe List ing2 1-3 .es), los mensajes del c ont rol ador de eventos se escriben en la ventana de la consola que se usó para iniciar la aplicación. Si compila el código del listado 21.3 us ando el destino de ejecutable de Wi ndows (esc /target : winexe Li sting2 l-3.es). ni nguna consola esta a s oc i a da al proceso y no se muestran mensajes en la consola.
Propiedades Application La clase Application es compatible con varias propiedades que pueden usarse en las aplicaciones de C#. Los siguientes apart ados describen cómo usar cada una de estas propiedades.
449
AllowQuit La propi edad booleana AllowQuit especifica si el código puede o no termi nar la aplicación mediante programación. La propiedad devuelve True si el códi go puede ordenar a la aplicación que termine y False en caso contrario. Esta propiedad es de sólo lectura y no se le puede as ignar un valor. Por lo general, los usuarios pueden t erminar las aplicaciones cerrando el formulario principal de la aplicación. Algunas aplicaciones se incluyen en contenedores, como clientes Web. v no pueden cerrarse mediante programaci ón. Sólo se cierran c uando su contene dor se cierra. Si la propiedad AllowQuit devuelve true. se puede llamar a un método de Application llamado Exit ( ) p ar a terminar la aplicación me diante progr amaci ón. Los métodos del objeto Application se estudiaran en el a pa rt ado titulado "Métodos Application."
CommonAppDataRegistry La propiedad CommonAppDataRegistry devuelve una referencia a un objeto de la clase RegistryKey. El objeto RegistryKey hace referencia a una clave en el registro que la aplicación puede usar par a al macenar datos de registro que deberían estar disponibles p ar a todos los usuarios de la aplicación. Esta propiedad es de sólo lectura y no se le puede as ignar un valor. La clase RegistryKey es parte de N E T F ramework y está disponible en un espaci o de nombre llamado M i c r o s o f t .W i n32 . Representa una clave especí fica del registro y contiene métodos que permiten crear subclavcs. leer valores y realizar otras tareas relacionadas con las claves del registro.
CommonAppDataPath La propiedad de cadena CommonAppDataPath hace referencia a una ruta en el sistema de archivos que la aplicación puede u s ar p ar a al macenar datos bas ados en archivos que deberían estar disponibles par a todos los usuarios de la aplicación. Esta propiedad es de sólo lectura y no se le puede asi gnar un valor. La ruta de datos de la aplicación se almacena dentro de la ruta de carpeta de d ocument os de W in do ws p a r a todos los usuar ios, que suele enc ontr ars e en
C:\Documents and Settings\All Users\Datos
de programa.
La ruta real de la aplicación a punt a a una carpeta dentro de esta ruta de documen tos "a/l users" que tiene la f o r m a C o m p a n y N a m e \ ProductName\ ProductVersion. Los nombres de carpet a CompanyName. ProductName v ProductVersion se basan en los valores de las propiedades de la aplicación del mi smo nombre. Si no se a si g na n val ores a est as prop ieda des , la clase Application propor ci ona unos valores por defecto válidos. Por ejemplo, el codi go del listado 21.1 tiene u na rut a de dat os c o m un es de la aplicación
C:\Documents and Settings\All Users\Datos de programa\SimpleHelloWorld\SimpleHelloWorld\VO .0. Si el código del listado 21.1 va a a s i g n a r val ores a las p r o p i ed a de s CompanyName.
450
ProductName o ProductVersion de la clase Application, los n o m bres de carpetas en la ruta de datos comunes de la aplicación puede c ambi ar para reflejar los valores de esas propiedades. La ruta de la carpet a de la versión del producto sólo usa los números de versión principal y secundario especificados en la aplicación, independientemente del número de valores que asigne en el at ri but o de la aplicación [AssemblyVersion] . Si la aplicación usa un atributo [AssemblyVersion] con un valor, por ejemplo, 1. 2 .3 .4. la parte de número de versión de la ruta de datos comunes de la aplicación será 1 .2. La letra V siempre se ant epone al número de v ersión principal de la aplicación en la parte de la v ersión de numero de la ruta de datos.
CompanyName La propiedad de cadena CompanyName devuelve el nombre de la c ompañí a asociado a la aplicación. Esta propiedad es de sólo lectura y no se le puede asignar un valor. Por defecto, este valor se asigna al nombre de la clase que contiene el m é t o d o M a i n ( ) de la aplicación. Si la aplicación especifica un n o m bre de co mpañí a con el at ri but o [ AssemblyCompany]. el valor de ese a t r i b u to se usa como el valor de la propiedad CompanyName de la aplicación.
CurrentCulture La propiedad CurrentCulture permite t ra ba j ar con la información de referencia cultural de la aplicación. Esta propiedad se puede leer y escribir. La propiedad tiene una clase de tipo Culturelnf o. que es una clase definida por N E T F ra me wo rk y que se e nc uen tr a en el es paci o de nombres System. Globalization. La clase Culturelnfo contiene métodos y propiedades que permiten al código t ra ba j ar con datos específicos del entorno cultural en el que se ejecuta la aplicación. Los objetos Culturelnfo ofrecen información como el formato preferido par a mo st rar la fecha y la hora, la configuración de la hora y el formato numeral.
CurrentlnputLanguage La propiedad CurrentlnputLanguage permite t rab aj ar con el idioma actual de la aplicación. La propiedad tiene una clase de tipo InputLanguage. que es una clase definida por N E T F ramework y que se encuentra en el espacio de nombres System. Windows .Forms. La clase InputLanguage contiene métodos y propiedades que permiten al codigo t ra ba j ar con el conocimiento que tenga la aplicación de las teclas del teclado y cómo se relacionan con los caracteres que pueden introducirse en la aplicación. Las diferentes versiones específicas de cada lenguaje de Wi ndows establecen equivalencias entre las teclas del teclado y los caracteres específicos de los diferentes lenguajes y la clase CurrentlnputLanguage especifica el aspecto de esta asignación.
451
ExecutablePath La propiedad de cadena ExecutablePath devuelve la ruta del ejecutable de la aplicación. Esta propiedad es de sólo lectura y no se le puede asi gnar un valor.
LocalUserAppDataPath La propiedad de cadena LocalUserAppDataPath hace referencia a una ruta en el sistema de archivos que la aplicación puede usar par a al macenar datos basados en archivos que deberían estar disponibles par a el usuario conectado en ese momento al equipo. Esta propiedad es de solo lectura y no se le puede asi gnar un valor. Es usada por los usuarios locales con perfiles de sistema operativo del equipo local. Los usuarios con perfiles móviles usados a través de un sistema de redes tienen una propiedad distinta, llamada UserAppDataPath. par a especificar dónde deben al macenar se los datos de la aplicación. Al igual que la propiedad CommonAppDataPath. la ruta de datos de u s u a rio local señala a una carpet a incluida dentro de la carpet a de documentos de u s u a r i o c o n e c t a d a con u n a e s t r u c t u r a de c a r p e t a s q ue t i e n e la f o r m a CompanyName\ ProductName\ ProductVers i on. Los nombres de carpeta CompanyName. ProductName y ProductVersion se basan en los valores de la aplicación del mi smo nombre. Si no se asigna un valor a estas propiedades, la clase Application propor ci ona unos valores válidos por de fecto. Si el có di g o a s i g n a un v a l o r a las p r o p i e d a d e s C o m p a n y N a m e . ProductName o ProductVers ion de la clase Application, los n o m bres de carpetas en la ruta de datos de la aplicación de usuario local cambia para reflejar los valores de esas propiedades. Al igual que la ruta de datos comunes de la aplicación, la ruta de carpeta de versión de producto solo usa los números de v ersión principal y s ecundario espe cificados en la aplicación, independientemente del número de valores especi fi ca dos en el at ri but o de la aplicación [ AssemblyVersion ] . Si la aplicación usa un atributo [AssemblyVersion] con un valor, por ejemplo. 1.2. 3. 4. la parte correspondiente al numero de v ersión de la ruta de datos de usuario local de la aplicación será 1.2. La letra V siempre se ant epone al numero de versión principal de la aplicación en la parte de la v ersión de número de la ruta de datos
MessageLoop La propiedad booleana MessageLoop devuelve True si existe un bucle de mensajes p a r a la aplicación y False en caso contrario. Esta propiedad es de solo l e c t u r a y no se le p u e d e a s i g n a r un v al or. C o m o t o d a s las a p l i c a c i o n e s Windows Forms necesitan un bucle de mensajes par a que los mensajes de W i n d o w s p u e d a n ser e n v i a d o s al f o r m u l a r i o c o r r e c t o , las a p l i c a c i o n e s Windows Forms dev uelven True para esta propiedad.
452
ProductName La propiedad de cadena ProductName devuelve el nombre del producto asociado a la aplicación. Esta propiedad es de sólo lectura y no se le puede asi gnar un valor. Por defecto, a este valor se le asigna el nombre de la clase que contiene el mét odo Main ( ) de la aplicación. Si la aplicación especifica un n om bre de producto con el at ri but o [AssemblyProduct ]. el valor de ese atributo se usa como el valor de la propiedad de la aplicación ProductName.
ProductVersion La propiedad de cadena ProductVersion devuelve el nú mer o de versión asociado a la aplicación. Esta propiedad es de sólo lectura y no se le puede asi gnar un valor. Por defecto, este valor es 0 . 0 . 0 . 0. Si la aplicación especifica un numero de versión con el at ri but o [ AssemblyVers ion ] . el valor de ese atributo se usa como el valor de la propiedad de la aplicación ProductVersion.
SafeTopLevelCaptionFormat La propiedad de cadena Sa f eTopLeveICapt ionFormat hace referencia a la cadena de formato que el tiempo de ejecución aplica a los títulos de la v entana de nivel superior cuando las aplicaciones se ejecutan desde un contexto no seguro. La seguridad es una parte integral de N E T Framevvork y el entorno común de ejecución (CLR). El C L R respeta la configuración de las diferentes zonas de seguridad en Internet Expl orer (Internet. Intranet local. Sitios de confianza y Sitios restringidos) y restringe los serv icios de tiempo de ejecución par a las apl i caciones que se ejecutan en zonas no fiables. Las aplicaciones Windows Forms que se ejecutan desde zonas no fiables, como la zona Internet, tienen una etiqueta de aviso que describe la aplicación como procedente de una localizacion no fi a ble El texto de esta etiqueta de aviso esta bas ado en la plantilla de formato de cadena al macenada en la propiedad SafeTopLevelCaptionFormat.
StartupPath La propiedad de cadena StartupPath devuelve la ruta al archivo ej ecut a ble que inició la aplicación. Esta propi edad sólo devuelve la ruta. No i nc li ne el nombre del archivo ejecutable. Esta propiedad es de sólo lectura y no se le puede asignar un valor.
UserAppDataPath La propiedad de cadena UserAppDataPath hace referencia a una ruta en el sistema de archiv os que puede us ar la aplicación para a lmac enar datos basados en archivos que deberían estar disponibles par a el usuario de red que esta conec tado en ese momento al equipo local. Esta propiedad es de sólo lectura y no se le puede asignar un valor. Es u sad a por los usuarios locales con perfiles de sistema operativo en la red. Los usuarios que tengan perfiles de equipo local no utilizados
453
en la red usan una propiedad distinta, llamada LocalUserAppDataPath. para especi fi car dónde deben a l m a c en ar s e los datos de la aplicación. Al igual que la propi edad CommonAppDataPath. la ruta de datos de u s u a rio local señala a una carpet a incluida dentro de la carpet a de documentos de u s u a r i o c o n e c t a d o co n u n a e s t r u c t u r a de c a r p e t a s q ue t i e n e la f o r m a CompanyName\ ProductName\ ProductVersion. Los nombres de carpeta CompanyName. Pr oductName y ProductVersion se bas an en los valores de la aplicación del mismo nombre. Si no se asigna un valor a estas propiedades, la clase Application propor ci ona unos valores válidos por de fecto. Si el có di g o a s i g n a un v a l o r a las p r o p i e d a d e s C o m p a n y N a m e . ProductName o ProductVersion de la clase Application, los n o m bres de carpetas en la ruta de datos de la aplicación de usuario local cambia para reflejar los valores de esas propiedades.
UserAppDataRegistry El me'todo UserAppDataRegistry devuelve una referencia a un objeto de clase RegistryKey. Al igual que la propiedad CommonAppDataRegistry. la propiedad devuelve un objeto de tipo RegistryKey. El objeto RegistryKey devuelto hace referencia a una clave en el registro que la aplicación puede usar par a al macenar datos de registro que sólo deberían estar disponibles para el u s u a rio actual de la aplicación. Esta propiedad es de sólo lectura y no se le puede asi gnar un valor.
Métodos Application La clase Application admite ocho métodos que pueden ser llamados desde las aplicaciones de C#. Estos métodos se describen en las siguientes secciones.
AddMessageFilter El método AddMessageFilter ( ) añade un filtro de mensajes a la ap li ca ción par a controlar los mensajes de Wi ndows mientras los mensajes son enviados a sus destinos. El filtro de mensajes que se instala en la aplicación recibe los mensajes de W indows antes de que se envíen al formulario. Un filtro de mensajes instalado por AddMessageFilter () puede cont rol ar un mensaje que se le envíe y puede decidir si el mensaje debe enviarse al formulario. El listado 2 1.4 muestra cómo se puede usar el filtro de mensajes en una apl ica ción Wi ndo ws Fo rms . El cont rol ador de mensajes b us ca mensajes que anunciar cuando se hace clic con el botón izquierdo en el formulario. Listado 21.4. Cómo instalar un filtro de mensajes using using
454
System; Syst em .W in do ws .Forms ;
public
class
B1ockLeftMouseButtonMessageFi1ter
: IMessageFi1ter
{ const const
int WM_LBUTTONDOWN = 0x20 1; int WM_LBUTTONUP = 0x202;
public
bool
P r e F il te rM es sa ge (ref Message
m)
{ if(m.Msg
== WM_LBUTTONDOWN)
{ Co ns ol e . W r i t e L i n e ("The return true;
left
mouse
button
is d o w n. ");
} if(m.Msg
== WM^LBUTTONUP)
{ Console.WriteLine ("The return true;
left
mous e button
is up.");
} return
public
false;
class M a m F o r m
: Form
{ public
static void M a i n ()
{ M a m F o r m MyForm = new M a m F o r m () ; BlockLeftMouseButtonMessageFilter MsgFilter B l o c k L e f t M o u s e B u t t o n M e s s a g e F i l t e r ();
= new
Ap p l i c a t i o n . A d d M e s s a g e F i l t e r ( M s g F i l t e r ) ; A p p l i c a t i o n .R u n ( M y F o r m ) ;
} public
MamFormO
{ Text
= "Message
Filter Test";
El método AddMessageFilter ( ) rccibc un argumento: una implementación de una interfaz llamada IMessageFilter . La interfaz IMessageFilter es d ef in i da p o r N E T F r a m e w o r k y se i ncl uye en el e s p a c i o de n om b r e s System. Windows .Forms. IMessageFilter declara un método: public
bool
PreFilterMessage (ref Message
m) ;
El método PreFilterMessage () toma como entrada una referencia a una instancia de una estructura llamada Message La estructura Message describe un mensaje de Wi ndo ws y contiene las siguientes propiedades:
HWnd. que describe el controlador de la ventana que debe recibir el m ens a je
455
L P a r a m . que describe un fragment o de n úmero entero que se envía con el mensaje. Ms g. que describe el numero entero ID asociado al mensaje. Cad a men saje de Wi ndows tiene su propio ID entero R e s u l t . que describe el valor que debe devolverse a Wi ndows en res puesta al control del mensaje. WP a r a m. que describe otro fragment o de número entero que se envía con el mensaje. El listado 21. 4 co mi en za d e c l a ra nd o una clase l la ma da B l o c k L e f t M o u s e B u t t o n M e s s a q e F i i t e r . E s t a c l a s e i m p l e m e n t a la i n t er f a z I M e s s a g e F i 1 t e r . La implement aci ón del método P r e F i l t e r M e s s a q e ( ) de la clase c omp ru eb a el ID del mensaje pasado al método. C om pr ue ba si el ID indica que se ha pulsado el botón izquierdo del ratón. En caso afirmativo, se escribe en la consola el mensaje T h e l e f t m o u s e b u t t o n i s d o wn . A continuación com pr ue b a si el ID indica que se ha soltado el botón izquierdo del ratón. En caso afirmativo, se escribe en la consola el mensaje T h e l e f t m o u s e b u t t o n i s up.
NOTA: La clase BlockLef tMouseButtonMessageFilter declara constantes para dar nombres a los mensajes de Windows que busca el filtro. Los nombres empiezan con WM (por Mensaje de Windows) y coinci den con los nombres definidos por Microsoft. Todos los mensajes de Windows disponibles y sus valores numéricos están explicados en la docu mentación de Platform SDK de Microsoft.
Las implement aci ones del método P r e F i l t e r M e s s a q e ( ) deben devolver un valor booleano que describe si el mensaje debe ser enviado al formulario tras pas ar por el filtro o no Si el filtro considera que el mensaje no debe ser enviado, entonces dev uelve T r u e . Si el filtro considera que el mensaje debe ser env iado, entonces dev uelve el valor F a i s e . El filtro de mensajes del listado 2 1.4 devuelve T r u e para los dos mensajes que controla y F a l s e para todos los demás m en s a jes. El método M a i n ( ) en el listado 21.4 crea un nuevo objeto de la clase B l o c k L e f t M o u s e B u t t o n M e s s a g e F i l t e r y lo usa en una llamada al método A d d M e s s a g e F i 1 t e r ( ) del objeto A p p l i c a t i o n . Tras instalar el filtro de mensajes, se crea y ejecuta el formulario principal. Puede ver el filtro de mensajes en acción compilando el listado 2 1 4. Al ejecu tar el codigo. aparecera el formulario principal de la aplicación. Cuando aparezca el formulario, muev a el ratón par a que el cursor esté dentro del formulario y haga clic con el botón izquierdo del ratón. Se escribirá en la consola un mensaje indi cando que ha pul sado un boton del ratón.
456
DoEvents El mét odo DoEvents ( ) procesa todos los mensajes que se encuentren en la cola de mensajes de la aplicación de Windows. El método no recibe a rgumentos m devuelve ningún valor. Este método se invoca cuando se quiere estar seguro de que los mensajes en espera de Wi ndows se envían al formulario mientras se reali zan otras tareas. Suponga, por ejemplo, que crea un formulario que realiza un calculo largo. Si se mueve otra ventana delante del formul ari o mientras se realiza el calculo. Wi ndows env ía a la aplicación un mensaje de Wi ndows que índica que el f o r mu lario debe ser dibujado de nuevo. Sin embargo, como el eodigo esta realizando un enorme cálculo, el mensaje de nuevo dibujo est ar a en la cola de mensajes de la aplicación; después de todo, el eodigo está ocup ado realizando cálculos y no procesando mensajes. Se pueden hacer llamadas al método DoEvents ( ) en ciertos puntos del proceso par a a se gurarnos de que los mensajes de espera de Wi ndows se procesan mientras el eodigo esta ocup ado realizando otro trabajo.
Exit El mét odo Exit ( ) obliga a la aplicación a terminar. El método informa a la cola de mensajes de la aplicación que debe finalizar y cierra los formularios cuando se procesa el ultimo mensaje de la cola de mensajes de Windows. Por lo general, el eodigo no necesita inv ocar al método Ex i t (). Los for mul a rios de Wi ndo ws incluyen por defecto un cuadro de cierre en la esquina superior derecha del formul ari o v al hacer clic en ese cuadro se envía un mensaje de cierre a la cola de mensajes de Windows. Sin embargo, puede ser útil llamar a Ex it ( ) si el formul ar io incluye un control, como un botón o un elemento de menú que debe finalizar la aplicación al ser seleccionado.
ExitThread El método ExitThread ( ) sale del bucle de mensajes y cierra todos los formularios en el subproceso en curso. El método 110 recibe argumentos 111 devuel ve ningún valor. Si la aplicación WindowsForms contiene un solo subproceso (como es habitual), entonces la acción de llamar a ExitThread ( ) es igual que a llamar a Ex i t (). Sin embargo, si la aplicación usa v a n o s subprocesos. enton ces los dos métodos se comportan de forma diferente. El método Ex itThread ( ) cierra un subproceso pero permite que los otros s ubprocesos sigan ejecutándose Sin embargo, el método Exit ( ) . cierra todos los subprocesos a la vez. Como todos los procesos de Wi ndows, las aplicaciones W indowsForms siguen ej ecu tándose hasta que finaliza el último subproceso.
OleRequired El método OleRequired ( ) inicializa OLE en el subproceso en curso de la aplicación. Si la aplicación va a trabajar con tecnología COM. como COM. DC'OM.
457
ActiveX o OLE. se debe llamar a este método de la aplicación antes de usar COM El método no recibe argument os, pero devuelve un valor desde una e nu m e r a ción l lamada Apartment State que describe el tipo de a p ar tamen to que intro dujo el subproceso. La enumeración Apartment State está definida en un espacio de nombre N E T Framework llamado S y s t e m .Threading y puede tener uno de los siguientes valores: STA. se devuelve cuando el C LR decide inicializar C OM para el subproceso entrando en un a pa rt amen to de un único subproceso. •
M T A . se d ev ue lv e c u a n d o el C L R decide i n i c i al i za r C O M p a r a el s ubproces o entrando en un apa rt amen to de multiproceso.
OnThreadException El método OnThreadException ( ) desencadena un evento ThreadException. El evento puede ser ca pt ur a do por un cont rol ador de eventos OnThreadException ( ) instalado en el objeto Application. El listado 21.5 muest ra cómo puede usarse una excepción de subproceso en una aplicación WindowsForms. Listado 21.5. C ómo trabajar con excepciones de subprocesos using using using
S ys tern; S ys t e m .T h r e a di ng; S ys t e m .W i n d o w s .F o r m s; BlockLef tMous eButtonMessageFilter
p u b i ic: class
: IMessageFi 11er
{ const
int WM_LBUTTONDOWN
public
bool
= 0x20 1;
P r e F i It er Me ss ag e(ref Message
m)
{ if(m.Msg
== WM_LBUTTONDOWN)
{ Exception
LeftButtonDownException;
LeftButtonDownExcept 1 on = new Exception("The left mouse button was p re s s e d . " ) ; A pp li ca ti on .O nT hr eadException(Le ftButt onDownException) ; return true;
} return
false;
} } public
class
A p p l i c a t 1 onEventHandlerC1ass
{ public
458
void
On Th r e a d E x c e p t i o n (object
sender,
T h r e a d E x c e p t i o n E v e n t A r g s e)
{ Exception
LeftButtonDownException;
LeftB uttonDownException = e . E x c e p t i o n ; C o n s o l é . W r i t e L i n e (L e f t B u t t o n D o w n E x c e p t i o n . M e s s a g e ) ;
} } public
c l a s s MamFor m : Form
{ public
static
v o i d M a i n ()
{ A p p l i c a t i o n E v e n t H a n d l e r C l a s s A pp Ev en ts = new A p p l i c a t i o n E v e n t H a n d l e r C l a s s (); MainForm MyForm = new Ma mFo rm( ) ; B l o c k L e f t M o u s e B u t t o n M e s s a g e F i l t e r M s g F i l t e r = new B l o c k L e f tMous e But t onMe s s a g e F i l t e r () ; App1 i c a t i o n . A d d M e s s a g e F i 1t e r ( M s g F i l t e r ) ; A p p l i c a t i o n . T h r e a d E x c e p t i o n += new T h r e a d E x c e p t i o n E v e n t H a n d l e r (A pp E v e nt s . OnThr e a d E x c e p t i o n ) ; A p p l i c a t i o n . Run (My Fo r m) ;
} public
MainFormO
{ Text
= "A pplication
Exception T est";
} } El l istado 21.5 es p a re c i d o al li st ado 2 1. 4 ya q ue i ncluye un c o n t r o l a d o r de m e ns aj es que b u s c a m e nsa je s que i nf or ma n c u a n d o se pu ls a el botón izqui er do del r at ón en el f o r m u l a r i o de la a p l i ca c ió n La d if er en ci a es qu e en el l ist ado 21. 5 se inicia un a e xc e pc i ó n al recibir el m e n s a j e l e f t m o u s e b u t t o n d o w n . El c o nt r o l a d o r de m e n s a j e s crea un n u e v o o b j e t o E x c e p t i o n y lo i n i c i a u s a n d o el m é t o d o O n T h r e a d E x c e p t i o n ( ) del obj et o A p p l i c a t i o n . El c odi go del li st ad o 2 1.5 también i n c l i n e un c o n t r o l a d o r de e v e nt os de la a p li c a c ió n , que se i m p l e m e n t a en una clase l l ama da A p p l i c a t i o n E v e n t H a n d l e r C l a s s . E s t a cl ase controla el e v e n t o O n T h r e a d E x c e p t i o n ( ) v el m é t od o pri nci pal de la apl icaci ón i nstala el c o n t r o l a d o r de e ve nt os u s a n d o la p r o p i e d a d T h r e a d E x c e p t i o n del o b j e t o A p p licatio n . El c o n t r o l a d o r de e x c e p c i o n e s del s u b p r o c e s o i n s t a l a d o en la c l a s e A p p l i c a t i o n E v e n t H a n d l e r C l a s s e x t r a e la e x c e p c i ó n del o b j e t o T h r e a d E x c e p t í o n E v e n t A r g s del c o n t r o l a d o r y e s c r i b e el m e n s a j e de la e xc e pc i ón en la consol a. C u a n d o se e jecut a el codigo en el listado 21.5. a p a r e c e el f o r m u l a r i o pri nc ipa l de la a pl ic ac ió n. C u a n d o a p a r e z c a el formulario, m u e v a el r at ón h a ci a su interior y h a g a clic con el bot ón i zquier do del ratón. El controlador
459
de mensajes iniciara una excepción y el cont rol ador de excepciones de la a p l i c a ción escribirá mensajes de excepción en la consola de la aplicación.
RemoveMessageFilter El mét odo RemoveMessageFilter ( ) elimina un filtro de mensajes i nsta lado por el método AddMessageFilter (). Elimina el filtro de mensajes del generador de mensajes de la aplicación. El método RemoveMessageFilter ( ) recibe un argumento: una i mplementación de una interfaz llamada IMessageFilter. Este ar gument o debe hacer referencia a una clase que implemcnta IMessageFilter y que ya ha sido usada en una llamada a A ddMessage Filter ( ) . El listado 2 1.6 muest ra cómo funciona este método. Listado 21.6. Cómo eliminar un filtro de mensajes instalado using using
System; System. Wi nd ow s.F o r m s ;
public
class
B 1oc:kLe f tMous eBut t onMes s age Fi 11 e r
: IMessageFilter
{ const
int W M _ L BUTTONDOWN = 0x201;
public
bool
PreFilterMessage(ref
Message
m)
{ if(m.Msg
== WM_LBUTTONDOWN)
{ C o n s o l e . W r i t e L m e ("The left mouse button A p p 1 1 cat i o n .RemoveMe ssageFilter (this) ; return true;
is down.");
) return
false;
} } public
class M a m F o r m
: Form
{ public
static
void M a i n ()
{ MainForm MyForm = new MainForm() ; BlockLeftMouseButtonMessageFilter MsgFilter B 1oc k L e f t M o u s eB ut to nM es sa ge Fi 11er () ; A p p 1 1 c at i o n .AddMessageFilter (MsgFilter) ; A p p l i cat i o n .Run(MyForm) ;
) public; M a m F o r m f )
{ Text
} }
460
= "Message
Filter
Removal
Test";
= new
El codigo del listado 21.6 ínstala un filtro de mensajes que bus ca el mensaje left mouse button down. igual que hacía el listado 2 1.4. La diferencia es que la implementación del filtro de mensajes en del listado 21.6 elimina el filtro de mensajes cuando se recibe el mensaje. Observe que. cuando se ejecuta el código del listado 21.6. solo se escribe un mensaje en la consola, independientemente del numero de v eces que pulse el boton izquierdo del ratón con el punt ero sobre el formulario. Esto es debido a que el filtro de mensajes es eliminado de la aplicación c uando se recibe el primer m en s a je y. como se elimina el filtro de mensajes, no se pueden detectar nuevos mensajes. Todav ía se env ían mensajes al formulario, pero el codigo del listado 2 1.6 elimina el objeto que detecta por pri mera vez los mensajes después de que se haya elimi nado el objeto de la lista de filtros de eventos de la aplicación.
TRUCO: El listado 21.6 usa la palabra clave t h i s como parámetro para la llamada al método R e m o v e M e s s a g e F i l t e r ( ) del objeto Application. Recuerde que la palabra clave this se emplea para ha cer referencia al objeto cuyo código se está ejecutando. Puede pensar en la instrucción del listado 21.6 que llama a RemoveMessageFilter () como si indicara "elimina la referencia a este filtro de mensajes del objeto Application".
Run El método Run ( ) inicia el bucle de mensajes de Wi nd ows par a una ap l ic a ción. Todos los listados de este capítulo han usado el método Run (). que acepta como p ar ámet ro una referencia a un objeto de formulario. Ya debería estar f ami liarizado con el f uncionamiento del método Run ().
Cómo añadir controles al formulario El formulario que las aplicaciones WindowsForms crean por defecto no es muy interesante. Tiene una b ar r a de título, un icono por defecto y los botones est andar de Wi nd ows Minimizar. M ax imi zar y Cerr ar Los formularios que en contramos en las aplicaciones reales incluyen controles como botones, cuadros de texto, etiquetas y elementos similares. Esta sección explica como añadir controles a los formularios de las aplicaciones C#. En esta sección, examinaremos cómo se implementan los controles desde NE T Framcvvork. El entorno de desarrollo dispone de compatibilidad de clases N E T par a los controles integrados en el sistema operativ o Wi ndows, y los ejemplos de esta sección muest ran su uso en la creación de aplicaciones Windows Forms que usan controles en los formularios de la aplicación.
461
Jerarquía de las clases de control N E T F r a m e w o r k i n c l u y e v a r i a s c l a s e s en el e s p a c i o de n o m b r e s
S y s t e m .Windows .Forms par a encaps ul ar el comport ami ent o de un control. Los elementos de interfaz de usuario, como botones, cuadros de texto, casillas de verificación y elementos similares, están representados por una clase de control. Todas estas clases se derivan de una clase base llamada Control. La figura 21.2 muest ra la j erarquía de clases de las clases de control. Todos los controles de la interfaz de usuario compart en al guna funcionalidad: todos deben ser capaces de situarse en su contenedor y gest ionar sus colores de pri mer plano v de fondo. Como todos los controles comparten este comportamiento, es lógico encapsularlo en una clase base y derivar la funcionalidad específica al control en las clases derivadas. Los autores de las clases de control de N E T Framework adoptaron este enfoque al construir las clases.
Cómo trabajar con controles en un formulario El listado 21.7 muest ra una aplicación WindowsForm que incluye un boton. El botón incluye un mensaje en un cuadro de texto al hacer clic. Listado 21.7. Cómo trabajar con un botón en un formulario using System; u sin g Sy stem.Drawing; u s m g System.Windows .Forms ; public
class MainForm
: Form
{ public
static
v oid Main()
( MainForm MyFcrm = new M a m F o r m f ) ; A p p 11 cat i o n .Run(MyForm) ;
I public
MainFormO
{ Button MyButton
= new B u t t o n ();
Text - "Button Test"; MyButton.Location - new P o i n t (25, 25); M y B u t t o n .Text - "Click Me"; MyButton.Click new EventHandler (MyButtonCiicked) ; Controls.Add(MyButton);
} public void MyButtonCiicked(object Ar y u m e n t s )
462
sender,
EventArgs
{ M e s s a g e B o x .S h o w ("The
button
has
been
clicked.")
}
Figura 21.2. Jerarquía de las clases de control
463
El listado 21.7 muest ra varios conceptos importantes que deben tenerse en cuenta cuando se trabaja con controles Windows F o r m s . Estudie el constructor del primer formulario. Crea un nuevo objeto de clase Button v establece su posicion en el formul ari o con la propiedad Location del botón. Esta propiedad se hereda de la clase Control (lo que significa que la propiedad esta disponible par a cualquier control derivado de la clase Contro 1) y establece la posición de la esquina superior izquierda del boton respecto a su contenedor. En el listado 21.7. la posición del boton se establece a 25 pixeles a la derecha del borde iz quierdo del formulario y a 25 pixeles por debajo de la parte superior del f o r mu l a rio. La posición se establece con una nueva instancia de una est ructura llamada Poi nt. que esta disponible en el espacio de nombres System. Prawi nq de N E T Framevvork: MyButton .I.ocat ion
- new
Point (2 5,
2 5) ;
TRUCO: El listado 21.7 usa la propiedad L o c a t i o n para establecer la ubicación del control. El uso de esta propiedad para que el programa colo que los controles puede suponer demasiado trabajo en formularios compli cados con muchos controles. Visual Studio NET dispone de un diseñador de formularios que permite, de forma visual, arrastrar y colocar controles en formularios. El diseñador crea a continuación los formularios C# equi valentes, liberando al programador de la tarea de tener que codificar toda la lógica de posicionamiento por sí mismo. El siguiente concepto importante del listado 21.7 está relacionado con el c o n trol de eventos de un control. Las clases de control admiten muchos eventos que se desencadenan cuando el usuario i nteractúa con el control. Muchos de estos ev entos se incluyen en la clase base Contro l. aunque la clase de control especi fica controla otros ev entos. El ev ento mas evidente de un botón de control sería un evento CJlick. Los botones de los formularios no son de utilidad a menos que puedan responder a una acción de un usuario que haga clic con el boton. Los controles de N E T Framevvork usan el modelo es tándar delegado/evento para compatibilizar sus eventos. Los ev entos de control se instalan usando instan cias de un delegado llamado EventHand 1e r . Este delegado admite dos a r g u mentos. un objeto que especifica el elemento que envió el evento y un objeto de una clase llamada EventArqs que en caps ul a los ar gument os del evento. El código del formul ar io del listado 21.7 incluye un método llamado MyButtonC 1 icked que modela el delegado EventUandler. Este método se usa como un nuevo cont rol ador de eventos y está conect ado al evento C1 ick del boton: My But ton.Clic k
<■- new
EventHancller (MyButtonC 1 1 cked) ;
La clase Forrn controla el evento Click del botón mos trando un cuadro de texto. Una clase llamada MessaqeBoz admite la vísualizacion de cuadros de
464
mensajes de Wi ndows La clase M e . s s a g e B o : : contiene un método estático ll amado S h o w ( ) que muest ra un mensaje en un cuadro de mensaje H1 ultimo concepto importante del listado 21.7 es la instrucción que agrega el control a la forma:
Co n t ¿oís
.Acid (M y B u 11 o n ) ;
La propiedad C o n t r o l esta definida en la clase base C o r h r o , (recuerde que la clase F o r r a se deriva de la clase C o n t r o ’ ). Ls un objeto de una clase l lamada C o n t r o 1 s C o i l e e t i o n \ gestiona una lista de controles secundarios que son gestionados p or el control actual. Fl eodigo W i n d o w s F o r ir s debe a ñ a dir controles a su colección C o n t r o l s de los formularios contenedores antes de que pueda ser usada realmente.
Cómo trabajar con recursos En Windows, los recursos se definen como datos que forman parte de una aplicación pero no afectan a la ejecución del código. Estos recursos pueden ser iconos, mapas de bits o cadenas. El sistema Wi n d o w s F o r n . s permite a lmacenar los recursos en un archivo separado durante el desarrollo del p ro gr ama e incluir los en un ensambl ado cuando se distribuya la aplicación. La principal ventaja de g ua r da r los recursos de la aplicación en un archivo separado es que ayuda al desarrollo del programa. Si incrustamos todas las c ade nas dentro del eodigo C#. por ejemplo, entonces solo alguien que conozca C'# s abi a donde buscar par a ca mbi ar los valores de cadena. Si la aplicación se escri be usando cadenas en ingles v luego es necesario c amb ia r la aplicación para que muestre cadenas en alemán, h a b í a que leer todo el codigo fuente > ca mbi ar todas las cadenas. Si g uar damos las cadenas en un archivo de tabla de cadenas aparte, podemos aplicar un t raduct or a ese archivo separado para que traduzca las c ad e nas en ingles a su equivalente en alemán sin ca mbi a r el codigo CU fuente. En el eodigo de la aplicación, el código dirá "lee una cadena de la tabla de cadenas" en lugar de teclear la cadena en el codigo de la aplicación.
Cómo trabajar con recursos de cadena Los recursos de cadena se definen en un archivo de texto separado, que debe tener la extensión . t : : t . El archivo debe contener un conjunto de pares clave, valor, separados por el signo igual. La clave para cada cadena debe ser un n om bre único para la cadena que se v a a usar en el eodigo CU para hacer referencia a ella. El valor de cadena real se escribe tras el signo igual. Se pueden colocar comentarios en los archivos de t abla de cadenas. Los comentarios comienzan con el símbolo de la libra y llegan hasta el final de la línea. El listado 21.8 muestra un ejemplo de archivo de tabla de cadenas. El archivo contiene una cadena c u \ o
465
nombre de clave es Message y cuyo valor es Helio ta b le !
from the
string
Listado 21.8. Ejemplo de un archivo de texto de una tabla de cadena
# String Table
# = == = =- =- - -= -= Message
- Helio
from the
string table!
Los archivos de t abla de cadena deben compilarse par a for mar un ensamblado de modo que las aplicaciones C# puedan leerlos. Esto se hace con una herramien ta llamada R e s G e n . La herramienta R e s G e n se incluye en el S DK de NET Framework. Es una aplicación de consola que lee el archivo de texto y produce una representación binaria de la tabla con extensión r e s o u r c e s . Si la tabla de cadena del listado 2 l .8 se escribe en un archivo de texto llamado L i s t i n g 2 1 8 .txt. puede compi lar la tabla de cadenas usando la siguiente línea de coman dos: resgen
Listing21-8.txt
Esto produce un archivo llamado Listing2 1-8 .resources. Tras com pilar un archivo resources p ar a la aplicación, se puede compilar en el e ns am blado usando el ar gument o /res par a el compi lador de C#. como muest ra la siguiente línea de comando: ese /res:string.resources /o u t :t e s t .exe test.es
Esta línea de comando ordena al compi lador de C# que cree un ejecutable llamado test, exe a partir del archivo fuente de C # test.es. T ambi én le ordena incrustar en el ejecutable test, exe los recursos que encuentre en el archivo str ing .resources. C omo los recursos están i ncrustados en el eje cutable. sólo se necesita env iar el ejecutable cuando se distribuye la aplicación. El archivo de recursos binarios no es necesario en tiempo de ejecución. Una vez que se han incrustado los recursos en la aplicación, se pueden leer desde el codigo de CU. El listado 21.9 es una modificación del listado 2 1.7. en el que el mensaje del c ua dro de mensajes se lee desde un recurso de cadena. Listado 21.9. Cómo leer desde un recurso de cadena using usmg us ing using using public
System; System. D r a w m g ; S ys t e m .W i n d o w s .F o rms ; Sy st em .Resources; System.Re f 1ection; class M a m F o r m
: Form
{ public
466
static
void M a i n ()
{ M a m F o r m MyForm = new M a m F o r m ( ) ; Application.Run(MyForm);
} public
MamFormO
{ Button MyButton
= new B u t t o n ();
Text = "Button Test"; M y B u t t o n .Location = new P o i n t (25, 25); M y B u t t o n .Text = "Click M e "; M y B u t t o n .Click += new EventHandler (MyButtonClicked) ; Controls.Add(MyButton);
} public void M y B u t t o n C l i c k e d (object Arguments)
sender,
EventArgs
{ ResourceManager FormResources ResourceManager ("StringTable" , As s embl y .GetExecut m g A s semb 1 y () ) ; s t n n g Message;
= new
Message = FormRes our ces .Ge tS t r m g ("Mes s age ") ; M e s s a g e B o x .Show(Message) ;
} } El listado 2 1.9 se compila con un recurso de tabla de cadenas cuyo archivo de texto contiene lo siguiente: #===========-= # Stnng
Table
#===========-Message
= The button has been clicked.
Este archivo de texto recibe el nombre de Str ingTabl e .t::t y se compila formando un archivo de recursos binario llamado StringTable .resources mediante la siguiente línea de comando: resgen
S t r i ng Ta bl e.txt
Este comando produce un archivo llamado StringTable. rosources. Este recurso se vincula a la aplicación cuando se compila el código C# principal mediante la siguiente línea de comando: ese
/ r e s :S t r i ng Ta bl e.resources
Listing21-C .cs
Se pueden leer recursos en las aplicaciones de C# usando una clase de NE T F ramework llamada ResourceManager. que se i ncl i ne en un espacio de nom-
467
bres llamado Gystem. Resources. El código del listado 2 1 . c) crea un nuevo objeto PesourceManager par a gestionar los recursos incrustados en el ejecu table. El const ructor recibe dos argumentos: El nombre base del archivo de recursos binarios que contiene el recurso que se esta abriendo. Se debe especificar este nombre, aunque no se nece sita el archivo físico porque el ensambl ado a gr up a los recursos en bloques y da nombre a los bloques usando el nombre base del archivo de recursos binarios original. Una referencia al ensamblado que contiene los recursos que se están a brien do. Este par ámet ro es una referencia a un objeto de una clase llamada Assembly. que se e n c u en t ra en el e spa ci o de n ombr es System. Ref leetion. Como los recursos que se están abriendo están i ncrus ta dos en el ensambl ado que se está ejecutando, si se llama al método estático Ge t E e c u 1 1 nqAs sernb 1y () se dev olv erá una referencia al e n s a m blado actual. Tras micializar el objeto PesourceManager. las cadenas se pueden abrir desde el a dmi ni str ador mediante un método llamado GetStr i ng (). Este metodo recibe un argument o de cadena: el nombre de clave de la cadena que se recupe ra. El método devuelve el v alor de la cadena n om br ada por la clav e.
Cómo trabajar con recursos binarios Las tablas de cadenas basadas en texto no son los únicos recursos que se pueden i ncrustar en los ensamblados. Tambi én se pueden i ncrust ar recursos binarios, como gráficos e iconos. Los recursos binarios están codificados, me diante codificación BASE64 . en un document o X M L con un formato especial Este d ocument o X M L tiene la extensión . resx y es compilado p ar a for mar un archivo de recurso mediante resgen. A partir de aquí, puede usar los métodos de la clase ResourceManager p ara t rabaj ar con los recursos binarios como si fueran recursos de texto. Desafortunadamente, el S DK de N E T Framevvork no incluye una herramienta par a generar documentos X M L con codificación B AS E6 4 a partir de entradas de archivo binario. Sin embargo. Visual Studio N E T permite incrustar recursos binarios en los ensamblados.
Resum en Este capitulo estudia los fundament os del proceso de desarrollo par a la el abo ración de aplicaciones WindovvsForms en C#. Tambi én se estudian algunas clases e l e me n ta l e s, c om o la c las e A p p l i c a t i o n , que g e s t i o n a la a p l i c a c i ó n
468
W i n do ws F or ms como un todo y la clase Form. que gestiona un formulario de la aplicación. Tambi én se hace un repaso a la arquit ect ura de las clases de control W in do ws F or ms y se exami nan los atributos de ens ambl ado que pueden agregar información de versión y descriptiva al ensamblado. N E T Framcwork contiene un variado conjunto de clases para el aborar apl i ca ciones Wi ndowsForms . El subsistema W in d ows Fo rm s esta c ompuest o por varias clases; por desgracia, las limitaciones de espacio no permiten una compl et a des cripción de todas ellas en este libro. Puede exa mi na r la document aci ón de cada clase WindowsForms. Use los conceptos de este capítulo par a comenz ar a investigar todas las clases del espacio de nombres WindowsForms.
469
Cómo crear aplicaciones Web con WebForms
VwX
La última década ha sido testigo del crecimiento sin precedentes de Internet como p lat af orma de negocios. Hoy en día. la may oría de los modelos de negocios están basados en. o al menos incluyen, el concepto de Internet. Por tanto, el enfoque ha c ambi ado de las aplicaciones de escritorio a las aplicaciones Web. Este ca mbi o ha subray ado la necesidad de tecnologías que puedan simplificar el desarrollo de aplicaciones Web. Para crear aplicaciones Web, N E T Framework incluy e A S P .N ET . que es la nueva versión de ASP 3.0. Se pueden crear aplicaciones en A S P . N E T usando Visual Basic N E T o Visual C# como lenguaje de progr amaci ón del servidor. Visual C# permite a los prog ramad ore s desarrollar potentes aplicaciones Web. Pero lo más importante es que ay uda a los progr amadores a luchar contra los ciclos cada vez más rápidos, porque les permite realizar más operaciones con menos líneas de código y con menos errores, lo que reduce consi der abl ement e el coste del proyecto. Aunque par a crear aplicaciones Web A S P . N E T sólo se necesita un editor de texto, como el bloc de notas, lo más habitual es usar una p lat af orma de de sa r ro llo. como Visual Studio NET. que p ropor ci ona un enorme conjunto de he r ra mientas para diseñar páginas Web. En comparación con los primeros lenguajes de programaci ón de páginas Web, en los que había que realizar una gran cantidad de codificación. Visual Studio N E T p ropor ci ona de una interfaz W Y S I W Y G . Esta
471
mterfaz permite ar ras trar y colocar controles en We bFor ms . que luego pueden ser pro gr amadas en Visual ('#. Al pr og ramar en Visual Studio . NET se puede s e pa rar el contenido en codigo y en H T M L de un WebForm. Esto hace que resulte muv sencillo s epa rar la progr amaci ón lógica de la presentación lógica, lo que nos permite concent rarnos en i mplementar la funcionalidad del proyecto, mas que en la presentación de datos En este capitulo aprenderemos a crear una aplicación Web A S P . N E T med ian te Visual CU. Mientras creamos la aplicación, diseñaremos un W e bF or m que usa controles de servidor, como etiquetas, c ua dr os de texto, c ua dr os de listas, hiperv metilos y botones. Por ultimo, aprenderemos a controlar los ex entos g ene rados por los controladores de servidor.
Fundamentos de las aplicaciones ASP.NET Web Las aplicaciones Web A S P . N E T son aplicaciones que se emplean en ser vido res Web Estas aplicaciones forman uno o mas W e b Fo r ms pr ogr amados en Vi sual C# o Visual Basic NET. En esta sección, estudiaremos las ventajas y desventajas de las aplicaciones W'eb A S P . N E T y como crear aplicaciones A S P . N E T con Visual C#. También estudiaremos las diferencias entre las aplicaciones A S P . N E T \ ASP 3.0.
Nuevas características de ASP.NET A S P . N E T incluye algunas nuevas características que no estaban presentes en ASP 3.0. En esta sección se describen brevemente estas características.
Ejecución en el entorno .NET Framework En compar aci ón con los primeros lenguajes de progr amaci ón Web. las a p l ic a ciones en Visual C# (y otros lenguajes Visual Studio NE T) se ejecutan en el entorno del marco de trabajo NET. Así. estas aplicaciones son independientes del navegador cliente y funcionan de la misma manera en todas las pl at aformas clien te. Otra ventaja de usar un tiempo de ejecución diferente para A S P . N E T es que las aplicaciones ASP 3.0 pueden coexistir con las aplicaciones A S P. NE T. Asi. se pueden us ar sitios Web ASP 3.0 y A S P . N E T en el mismo servidor W'eb.
Presentación de WebForms Los W c h h o n n s son la base de una aplicación basada en Web. La aplicación Web los usa para mt eraet uar con el usuario. Un We bFor m puede incluir van os controles de serv idor. como cuadros de texto, etiquetas, cuadros de listas, botones
472
de opcion. casillas de verificación y botones. los cuales facilitan la interacción del usuario con la aplicación. Un W e b Fo r m consta de dos componentes, la interfaz de usuario (1U) > la lógica de la aplicación (aplicación). La interfaz de usuario es el componente v isual de un We bFo rm. Se c ompone de H T M L y controles específicos de la a pl i cación Web. La interfaz de usuario es el contenedor del texto y los controles que deben apa rec er en la pagi na Web. Se especifica en un archivo con la extensión . ciS p X . La lógica de progr amaci ón de una aplicación Web de ASP N E T esta en un archivo s eparado que contiene el código e nc arga do de controlar las interacciones del usuar io con el formulario. Este archivo recibe el nombre de archivo de "codi t o o c u l t o ". Cuand o se ejecuta un formulario escrito en C'#. el archivo de código oculto genera di námi cament e el resultado H T M L de la pagina. El archivo de codigo oculto de C# tiene una extensión . a s p : - : . e s La ventaja de s eparar el código del contenido es que el pr og ramad or no necesi ta concentrarse en la lógica que se usa par a mos trar el resultado El diseñador Web puede controlar esta tarea.
Integración con Visual Studio .NET Visual Studio . NET es la herramienta de desarrolló rápido de aplicaciones para ASP.NET. Visual Studio .NET ofrece una completa integración con AS P. NET v permite ar r as t ra r y colocar controladores de serv idor y diseñar W e b Fo r ms con el aspecto que tendrán cuando un usuario los vea. Algunas de las otras ventajas de crear aplicaciones A S P . N E T con Visual Studio . NET se resumen en la siguiente lista: Visual Studio NE T es una herramienta de desarrollo rapida de apl icaci o nes (RAD). En lugar de añadir cada control al We b Fo r m mediante p r o g r a mación. le ayuda a añadir estos controles usando el cuadro de herramientas, ahorrándole trabajo de programación. Visual Studio N E T admite controles personalizados y compuestos. Se pueden crear controles personalizados que encapsulen una funcionalidad común cuy o uso puede ser necesario en varias aplicaciones, del mismo modo que se usan los controles Web A S P . N E T proporci onados por Visual Studio .NET.
Presentación de los controles de servidor Ademas de los controles H T ML que existían en la época de ASP 3.0. A S P . NE T presenta controles de servidor que son componentes de una aplicación Web que se ejecutan en el servidor y encapsul an la funcionalidad de la aplicación. Los controles H T M L hacen referencia a los elementos H T M L que se pueden usar en los We bFo rms . Por lo general, cuando los controles H T M L se envían al serv idor a trav és del nav egador, el serv idor considera que los controles H T M L
473
son opacos. Es dccir. el servidor no los procesa. Sin embargo, al convertir estos controles en controles de serv idor H T M L pueden quedar a la vista del servidor par a que realice el proceso. Mediante el uso de atributos, como ID y RUNAT. se pueden convertir los controles H T M L en controles de servidor H T ML . Puede añadir estos controles a un We b Fo r m usando la ficha H T M L del cuadro de he r ra mientas. Por otra parte, los controles de servidor son completamente t r an s p a r e n tes p ar a la aplicación y permiten al p rog ra ma dor controlar eventos del lado del servidor par a gestionar la aplicación Web. Apart e de los cuadros de texto convencionales, esta categoria de controles también incluye los controles de validación. Los controles de validación son con troles progr amabl es que ayudan a validar las entradas del usuario. Por ejemplo, se pueden usar estos controles par a validar el valor de un campo o el patrón de caracteres introducido por el usuario. Para validar la entrada del usuario, es necesario adj unt ar estos controles a los controles de la entrada.
Controles de usuario y compuestos Si quiere dupl icar un conjunto de controles en varias páginas, puede crear controles en cada formulario por separado. Esta no es una opción muy útil. Sin embargo. A S P . N E T nos permite realizar esta operación mediante los controles de usuario y compuestos. Los controles de usuar io son W e b F o r m s normales que hemos convertido en controles eliminando las etiquetas y