La Biblia Del C
La Biblia Del C
C#
Jeff Ferguson, Brian Patterson, Jason Beres, Pierre Boutquin y Meeta Gupta
Conversin OCR y correcciones: jparra, May-Ago, 2006
Todos los nombres propios de programas, sistemas operativos, equipos hardware, etc. que aparecen en este libro son marcas registradas de sus respectivas compaas u organizaciones.
Reservados todos los derechos. El contenido de esta obra est protegido por la ley, que establece penas de prisin y/o multas, adems de las correspondientes indemnizaciones por daos y perjucios, para quienes reprodujeren, plagiaren, distribuyeren o comunicasen pblicamente, en todo o en parte, una obra literaria, artstica o cientfica, o su transformacin, interpretacin o ejecucin artstica fijada en cualquier tipo de soporte o comunicada a travs de cualquier medio, sin la preceptiva autorizacin.
Copyright (c) 2003 by Anaya Multimedia. Original English language edition copyright (c) 2002 by Hungry Minds, Inc. All rights reserved including the right of reproduction in whole or in part in any form. This edition published by arrangement with the original publisher, Hungry Minds, Inc. Edicin espaola: (c) EDICIONES ANAYA MULTIMEDIA (GRUPO ANAYA. S.A.), 2003 Juan Ignacio Luca de Tena, 15. 28027 Madrid Depsito legal: M. 3.033- 2003 ISBN: 84-415-1484-4 Printed in Spain Imprime: Imprime Artes Grficas Guemo, S.L. Febrero, 32. 28022 Madrid.
Para mi familia y amigos. Jeff Ferguson Este libro est dedicado a mi to, Brian Weston, al que no pareci importarle cuando fui de visita y pas todo el da con su TRS-80 Model II. Brian Patterson A Nitin, que fue la motivacin. Meeta Gupta
Agradecimientos
Jeff Ferguson: Pocos libros de este tamao y extensin son el fruto de un solo individuo y ste no es una excepcin. Estoy en deuda con mucha gente por su ayuda y apoyo mientras escriba este libro. En primer lugar, debo dar las gracias a mis padres por la educacin que recib. Sin sus paternales consejos no me habra convertido en la persona que soy y no habra podido completar ninguno de mis trabajos. Siempre os estar agradecido, no slo a vosotros, sino a toda la familia por el amor y apoyo que siempre he recibido. Me gustara dar las gracias a todo el mundo de Wiley por su direccin en la elaboracin de este material. Gracias, Andrea Boucher, Sharon Cox, Eric Newman y Chris Webb, por guiarme por el intimidador mundo de la publicacin de libros tcnicos. Gracias tambin a Rolf Crozier, que discuti conmigo este proyecto en primer lugar en sus primeros das. Debo dar las gracias especialmente a mi colega Bob Knutson, que revis los borradores del material de este libro. Gracias a Greg Frankenfield y a Paul Fridman por crear una excelente organizacin consultora basada en Microsoft que me permite trabajar en los proyectos de mis clientes junto en los mos. El crecimiento tcnico que he experimentado durante mi estancia en Magenic ha sido incalculable. Esto es para que contine el xito de Magenic. Gracias a todo el mundo de las listas de correo y grupos de noticias de DOTNET en Internet. Estoy aprendiendo mucho sobre .NET Framework y C# simplemente leyendo vuestros correos. Los envos de ac para all del banter me han dado una mayor comprensin de cmo encajan todas estas nuevas piezas. Brian Patterson: Me gustara dar las gracias a mi esposa, Aimee, por perdonarme todas esas horas que pas escondido en el ordenador para que pudiera completar este libro. Un agradecimiento especial a Steve Cisco por su duro trabajo en este libro, que abri camino para el resto de nosotros; a Sharon Cox, la editora de adquisiciones, que siempre me mantuvo en el buen camino; al editor de proyecto, Eric Newman, por aguantar todos mis regates; y al editor de la serie, Michael Lane Thomas, que revis todos y cada uno de los captulos, haciendo algunas sugerencias muy buenas y proporcionando una apreciable comprensin de Microsoft y .NET Framework. Pierre Boutquin: Se necesit mucho trabajo para crear este libro y no slo de la gente que aparece en la portada. Debo dar las gracias especialmente al equipo de Wiley por su tremendo esmero por producir un libro de calidad. Los revisores se merecen casi todo el crdito por hacerme parecer un escritor competente. Por ltimo, este trabajo no habra sido posible sin el apoyo de mi familia y amigos: Sandra, Andrea, Jennifer y Paul, Tindy y Doel, Marcel y Diana Ban, Margaret Fekete, y John y Nadine Marshall. Meeta Gupta: Agradezco a Anita que me diera la oportunidad. Pero mi mayor agradecimiento es para Nitin por, bueno, por todo.
NIIT es una compaa de soluciones globales TI que produce productos de enseanza multimedia personalizados y tiene ms de 2.000 centros de enseanza por todo el mundo. NIIT tiene ms de 4.000 empleados en 37 pases y tiene acuerdos estratgicos con varias de las principales corporaciones, incluidas Microsoft y AT&T.
ndice
Agradecimientos Sobre los autores Sobre el editor de la serie Introduccin Quin debera leer este libro Cmo est organizado este libro Parte I: Fundamentos del lenguaje C# Parte II: Programacin orientada a objetos con C# Parte III: C# avanzado Parte IV: Desarrollando soluciones .NET usando C# Parte V: C# y .NET Framework Parte VI: Apndices Cmo usar este libro Normas usadas en este libro Parte I. Fundamentos del lenguaje C#
6 7 8 29 30 30 30 31 31 31 31 31 32 32 35
1. Introduccin a C#.................................................................................................37 .NET Framework 38 Desarrollo Web 38 Desarrollo de aplicaciones 39 Entorno comn de ejecucin 40 Bibliotecas de clase .NET.......................................................................................41
Lenguajes de programacin .NET...........................................................................42 Entorno ASP.NET 43 Historia de C, C++ y C# 43 Introduccin a C# 45 Caractersticas del lenguaje 45 Clases 45 Tipos de datos 46 Funciones 47 Variables 47 Interfaces 48 Atributos 49 Cmo compilar C# 49 Lenguaje intermedio de Microsoft (MSIL) 49 Metadatos 51 Ensamblados 51 Resumen 52 2. Escribir su primer programa en C# Cmo escoger un editor La aplicacin Hello World Cmo construir una clase El mtodo Main() Cmo escribir en la consola Compilacin y ejecucin del programa Las palabras clave y los identificadores Uso de espacios en blanco Cmo iniciar programas con la funcin Main() Cmo comentar el cdigo Cmo usar comentarios de una lnea Usar comentarios normales Cmo generar documentacin XML a partir de comentarios <c> <code> <example> <exception> <list> <param> <paramref> <permission> <remarks> <returns> <see> <seealso> 55 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
<summary> <value> Resumen 3. Trabajar con variables Cmo dar nombre a sus variables Asignacin de un tipo a una variable Cmo aplicar tamao a sus variables Cmo declarar sus variables Uso de valores por defecto en las variables Asignacin de valores a variables Uso de matrices de variables Declaracin de matrices unidimensionales Cmo trabajar con los valores de las matrices unidimensionales Inicializacin de valores de elementos de matriz Declaracin de matrices multidimensionales Uso de matrices rectangulares Definicin de matrices escalonadas Tipos de valor y de referencia Cmo convertir tipos de variable Conversiones implcitas Conversiones explcitas Cmo trabajar con cadenas Uso de caracteres especiales en cadenas Desactivacin de los caracteres especiales en cadenas Cmo acceder a caracteres individuales en la cadena Declaracin de enumeraciones Resumen 4. Expresiones
73 73 73 75 75 76 78 78 79 81 81 82 83 84 85 85 87 88 89 89 90 92 92 94 95 95 96 99
Cmo usar los operadores 99 Uso de expresiones primarias 100 Cmo usar los literales 100 Literales booleanos 101 Cmo usar los literales enteros en notaciones decimales y hexadecimales 101 Cmo usar los literales reales para valores de coma flotante 103 Cmo usar los literales de carcter para asignar valores de carcter...........104 Cmo usar los literales de cadena para incrustar cadenas 104 Cmo usar los literales null 104 Uso de identificadores 105 Expresiones entre parntesis 105 Cmo llamar a mtodos con expresiones de acceso a miembros 106
11
Cmo llamar a mtodos con expresiones de invocacin Cmo especificar elementos de matriz con expresiones de acceso a elementos Cmo acceder a objetos con la palabra clave this Cmo acceder a objetos con la palabra clave base Cmo usar los operadores postfijo de incremento y de decremento Creacin de nuevos tipos de referencia con el operador new Cmo devolver informacin sobre el tipo con el operador typeof Cmo usar operadores checked y unchecked Las expresiones unarias Cmo devolver valores de operando con el operador unario ms Cmo devolver valores de operando con el operador unario menos Expresiones negativas booleanas con el operador de negacin lgica El operador de complemento bit a bit Cmo prefijar operadores de incremento y decremento Los operadores aritmticos Cmo asignar nuevos valores con el operador de asignacin Uso del operador multiplicacin Uso del operador divisin Uso del operador resto Uso del operador suma Uso del operador resta Los operadores de desplazamiento Cmo mover bits con el operador de desplazamiento a la izquierda Cmo mover bits con el operador de desplazamiento a la derecha Cmo comparar expresiones con operadores relacionales Cmo comprobar la igualdad con el operador de igualdad Cmo comprobar la desigualdad con el operador de desigualdad Cmo comprobar valores con el operador menor que Como comprobar valores con el operador mayor que Cmo comprobar valores con el operador menor o igual que Como comprobar valores con el operador mayor o igual que Operadores lgicos enteros Cmo calcular valores booleanos con el operador AND Cmo calcular valores booleanos con el operador OR exclusivo Cmo calcular valores booleanos con el operador OR Operadores condicionales lgicos Comparacin de valores booleanos con el operador AND condicional Comparacin de valores booleanos con el operador OR condicional Comparacin de valores booleanos con el operador lgico condicional El orden de las operaciones Resumen
106 107 108 109 109 110 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
12
131
Instrucciones de C# 131 Instrucciones para declarar variables locales 132 Cmo usar instrucciones de seleccin para seleccionar la ruta del cdigo 133 La instruccin if 134 La instruccin switch 135 Cmo usar instrucciones de iteracin para ejecutar instrucciones incrustadas 137 La instruccin while 138 La instruccin do 138 La instruccin for 139 La instruccin foreach 142 Instrucciones de salto para moverse por el cdigo 142 La instruccin break 143 La instruccin continue......................................................................................143 La instruccin goto 144 Cmo usar instrucciones para realizar clculos matemticos con seguridad 145 Resumen 145 6. Cmo trabajar con mtodos 149
La estructura de un mtodo 149 Tipo devuelto 150 Nombre del mtodo 150 Lista de parmetros 150 Cuerpo del mtodo 151 Cmo llamar a un mtodo 151 Tipos de parmetros 155 Parmetros de entrada 155 Parmetros de salida 156 Parmetros de referencia 159 Matrices de parmetros 160 Sobrecarga de mtodos 162 Mtodos virtuales 163 Mtodos sobrescritos.................................................................................................164 Resumen 166 7. Agrupacin de datos usando estructuras Cmo declarar una estructura Cmo usar estructuras en el cdigo Cmo definir mtodos en estructuras Cmo usar mtodos constructores Cmo llamar a mtodos desde estructuras 169 170 171 173 174 177
13
Cmo definir propiedades en estructuras Cmo definir indizadores en estructuras Cmo definir interfaces en estructuras Cmo usar los tipos simples de C# como estructuras Resumen Parte II. Programacin orientada a objetos con C# 8. Escribir cdigo orientado a objetos Clases y objetos Terminologa del diseo de software orientado a objetos Abstraccin Tipos de datos abstractos Encapsulacin Herencia Herencia simple Herencia mltiple Polimorfismo Resumen
178 179 181 182 185 187 189 192 192 193 193 195 197 197 198 199 202
9. Clases de C#........................................................................................................205 Cmo declarar una clase El mtodo Main Cmo usar argumentos de lnea de comandos Cmo devolver valores El cuerpo de la clase Cmo usar constantes Cmo usar campos Cmo usar mtodos Cmo usar propiedades Descriptores de acceso get Descriptores de acceso set Propiedades de slo lectura y de slo escritura Cmo usar eventos Cmo usar indizadores Cmo usar operadores Cmo usar constructores Cmo usar destructores Cmo usar los tipos de clase Cmo usar la palabra clave this como identificador El modificador static Cmo usar campos estticos Cmo usar constantes estticas 205 206 207 207 209 209 210 212 212 215 216 216 217 217 222 222 223 226 226 228 228 230
14
Cmo usar mtodos estticos Resumen 10. Cmo sobrecargar operadores Operadores unarios sobrecargables Cmo sobrecargar el unario ms Cmo sobrecargar el unario menos Cmo sobrecargar complementos bit a bit Cmo sobrecargar el incremento prefijo Cmo sobrecargar el decremento prefijo Cmo sobrecargar los operadores true y false Operadores binarios sobrecargables Operadores de conversin sobrecargables Operadores que no pueden sobrecargarse Resumen 11. Herencia de clase Cmo compilar con clases mltiples Cmo especificar una clase base en C# mbito Cmo reutilizar identificadores de miembros en las clases derivadas Cmo trabajar con mtodos heredados Mtodos virtuales y de reemplazo Polimorfismo Mtodos abstractos Clases base: Cmo trabajar con propiedades e indizadores heredados Cmo usar la palabra clave base Cmo acceder a campos de clase base con la palabra clave base Clases selladas Contencin y delegacin La clase object de .NET Cmo usar boxing y unboxing para convertir a tipo object y desde el tipo object Resumen Parte III. C# avanzado
230 232 237 238 238 240 242 244 245 246 248 251 253 253 257 258 259 260 261 263 263 265 267 268 269 270 270 270 277 279 281 283
12. Cmo trabajar con espacios de nombres.........................................................285 Cmo declarar un espacio de nombres.......................................................................286 Cmo declarar un espacio de nombres en varios archivos fuente 287 Cmo usar clases en un espacio de nombres.............................................................288 Cmo ayudar a los espacios de nombres mediante la palabra clave using 290 Cmo crear alias de nombres de clase con la palabra clave using 290
15
Cmo declarar directivas de espacio de nombres con la palabra clave using 293 Un rpido recorrido por los espacios de nombres de .NET......................................295 Resumen 298 13. Interfaces Cmo definir una interfaz Cmo definir mtodos de interfaz Cmo definir propiedades de interfaz Cmo definir indizadores de interfaz Cmo definir eventos de interfaz Cmo derivar a partir de interfaces base Cmo usar la palabra clave new para reutilizar identificadores Cmo implementar interfaces en clases y estructuras Cmo implementar mtodos de interfaz con el mismo nombre Cmo acceder a miembros de interfaz Consultar a un objeto por una interfaz Cmo acceder a una interfaz en un objeto Declaraciones de interfaz y palabras clave de mbito Cmo implementar interfaces definidas por .NET Framework Cmo implementar foreach mediante IEnumerable IEnumerator Cmo implementar limpieza mediante IDisposable Resumen 14. Enumeraciones Cmo declarar una enumeracin Cmo usar una enumeracin Cmo usar operadores en valores de enumeracin Cmo usar la clase .NET System.Enum Cmo recuperar nombres de enumeracin Cmo comparar valores de enumeracin Cmo descubrir el tipo subyacente en tiempo de ejecucin Cmo recuperar todos los valores de enumeracin Anlisis de cadenas para recuperar valores de enumeracin Resumen 15. Eventos y delegados Cmo definir delegados Cmo definir eventos Cmo instalar eventos Cmo desencadenar eventos Cmo unirlo todo Cmo estandarizar un diseo de evento 301 303 303 304 304 305 305 307 308 310 311 311 314 316 317 317 322 325 329 331 333 335 337 337 339 341 341 342 343 345 346 346 347 348 348 350
16
16. Control de excepciones...................................................................................359 Cmo especificar el procesamiento de excepciones 361 Cmo capturar excepciones 362 Cmo usar la palabra clave try 362 Cmo atrapar clases especficas de excepciones 362 Cmo liberar recursos despus de una excepcin 364 La clase exception 365 Introduccin a las excepciones definidas por .NET Framework..............................365 OutOfMemoryException 366 StackOverflowException 366 NullReferenceException 367 TypeInitializationException 368 InvalidCastExpression 368 ArrayTypeMismatchException 369 IndexOutOfRangeException 369 DivideByZeroException 370 OverflowException 370 Cmo trabajar con sus propias excepciones 371 Cmo definir sus propias excepciones..................................................................372 Cmo iniciar sus excepciones 373 Cmo usar excepciones en constructores y propiedades 374 Resumen 376 17. Cmo trabajar con atributos Atributos Cmo trabajar con atributos de .NET Framework System.Diagnostics.ConditionalAttribute System.SerializableAttribute class System.ObsoleteAttribute class Cmo escribir sus propias clases de atributo Cmo restringir el uso de atributos Cmo permitir mltiples valores de atributo Cmo asignar parmetros de atributo Ejemplo explicativo de las clases de atributo Resumen 379 380 383 384 386 388 390 390 391 392 394 396
17
399
El problema de las versiones 399 Cmo solucionar el problema de las versiones 402 Mediante el modificador new................................................................................402 Mediante el modificador override 404 Resumen 406 19. Cmo trabajar con cdigo no seguro Conceptos bsicos de los punteros Tipos de puntero Cmo compilar cdigo no seguro Cmo especificar punteros en modo no seguro Cmo acceder a los valores de los miembros mediante punteros Cmo usar punteros para fijar variables a una direccin especfica Sintaxis del elemento de matriz puntero Cmo comparar punteros Clculo con punteros Cmo usar el operador sizeof Cmo asignar espacio de la pila para la memoria Resumen 20. Constructores avanzados de C# Operadores implcitos y conversiones no vlidas Inicializacin de estructuras Cmo inicializar estructuras Cmo resolver los problemas con la inicializacin Clases derivadas Cmo pasar clases derivadas Cmo resolver problemas que surgen cuando se pasan clases derivadas .. Cmo usar no enteros como elementos de matriz Resumen Parte IV. Cmo desarrollar soluciones .NET usando C# 21. Cmo construir aplicaciones WindowsForms Arquitectura de WindowsForms La clase Form La clase Application Cmo crear la primera aplicacin WindowsForms Cmo compilar una aplicacin WindowsForms Ensamblados: cmo aadir informacin de versin a las aplicaciones WindowsForms AssemblyTitle 409 410 411 412 413 414 415 416 417 417 418 419 419 423 424 425 426 427 429 429 430 431 434 435 437 438 438 438 439 440 441 442
18
AssemblyDescription AssemblyConfiguration AssemblyCompany AssemblyProduct AssemblyCopyright AssemblyTrademark AssemblyCulture AssemblyVersion El objeto Application con ms detalle Eventos Application Cmo trabajar con eventos en el cdigo Propiedades Application AllowQuit CommonAppDataRegistry CommonAppDataPath CompanyName CurrentCulture CurrentInputLanguage ExecutablePath LocalUserAppDataPath MessageLoop ProductName ProductVersion SafeTopLevelCaptionFormat StartupPath UserAppDataPath UserAppDataRegistry Mtodos Application AddMessageFilter DoEvents Exit ExitThread OleRequired OnThreadException RemoveMessageFilter Run Cmo aadir controles al formulario Jerarqua de las clases de control Cmo trabajar con controles en un formulario Cmo trabajar con recursos Cmo trabajar con recursos de cadena Cmo trabajar con recursos binarios Resumen
443 443 443 443 444 444 444 445 447 447 448 449 450 450 450 451 451 451 452 452 452 453 453 453 453 453 454 454 454 457 457 457 457 458 460 461 461 462 462 465 465 468 468
19
22. Cmo crear aplicaciones Web con WebForms Fundamentos de las aplicaciones ASP.NET Web Nuevas caractersticas de ASP.NET Ejecucin en el entorno .NET Framework Presentacin de WebForms Integracin con Visual Studio .NET Presentacin de los controles de servidor Controles de usuario y compuestos Controles ms usados en WebForms Control Label Control TextBox Controles CheckBox y CheckBoxList Controles RadioButton y RadioButtonList Control ListBox Control DropDownList Control HyperLink Controles Table, TableRow y TableCell Control ImageButton Controles Button y LinkButton Cmo crear y configurar una aplicacin Web Cmo crear un nuevo proyecto Cmo agregar controles al WebForm Cmo controlar eventos Viajes de ida y vuelta Controladores de eventos Cmo controlar la devolucin de datos Cmo usar el estado de vista Resumen 23. Programacin de bases de datos con ADO.NET Clases Dataset y otras clases relacionadas Compatibilidad con OLE DB SQL Server Operaciones de bases de datos comunes mediante ADO.NET Operaciones que no devuelven filas Operaciones de datos que devuelven entidades de fila nica Operaciones de datos que afectan a las entidades de fila nica Operaciones de introduccin de datos que afectan a las entidades de fila nica Operaciones de actualizacin que afectan a entidades de fila nica Operaciones de borrado que afectan a las entidades de fila nica Operaciones de datos que devuelven conjuntos de filas Operaciones de datos que afectan a conjuntos de filas
471 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 495 496 497 499 500 504 509 509 514 515 517 520
20
Operaciones que no devuelven datos jerrquicos Resumen 24. Cmo trabajar con archivos y con el registro de Windows Como acceder a archivos Acceso binario BinaryWriter Binary Reader Cmo supervisar los cambios de archivo Cmo usar la supervisin de archivos Cmo codificar FileSystemWatcher Cmo manipular archivos Cmo copiar archivos Cmo eliminar archivos Cmo trasladar archivos Cmo acceder al registro Cmo leer claves del registro Cmo escribir claves de registro Cmo enumerar claves del registro Resumen 25. Cmo acceder a secuencias de datos
522 527 529 529 530 530 531 533 534 538 539 539 540 542 544 544 546 549 551 555
Jerarqua de clases de E/S de datos 555 Cmo usar secuencias 556 Cmo usar escritores 557 Cmo usar lectores 557 Cmo trabajar con secuencias 558 E/S sncrona 558 E/S asncrona 563 Cmo leer de forma asncrona.......................................................................564 Cmo escribir de forma asncrona.................................................................568 Escritores y lectores 570 Cmo escribir secuencias con BinaryWriter 570 Cmo leer de secuencias con BinaryReader 572 Cmo escribir XML con un formato correcto mediante la secuencia XmlWriter 573 Resumen 575 26. Cmo dibujar con GDI+ Cmo trabajar con grficos Cmo trabajar con Image en GDI+ Cmo trabajar con lpices y pinceles Cmo usar la clase Pen 577 577 586 591 591
21
Cmo usar la clase Brush Resumen 27. Cmo construir servicios Web
Funcionamiento de los servicios Web 600 Servicios Web y Visual Studio .NET.........................................................................602 Lenguaje de descripcin de servicios Web (WSDL) 605 Cmo usar el Protocolo de acceso simple a objetos (SOAP) 607 Cmo crear servicios Web con Visual Studio .NET 609 Como usar Visual Studio .NET para acceder a un servicio Web 612 Resumen 614 28. Cmo usar C# en ASP.NET 617
Cmo crear un servicio Web 618 Cmo crear una base de datos para un servicio Web 618 Conceptos del sistema de gestin de bases de datos relacionales 619 Tipos de datos de SQL Server 619 Como crear bases de datos y tablas 620 Como recuperar datos 621 Cmo insertar, actualizar y eliminar datos 621 Cmo usar procedimientos almacenados 621 Cmo crear la estructura de la base de datos 622 Cmo usar la plantilla Servicio Web ASP.NET 623 Cmo agregar controles de datos al servicio Web 624 Cmo codificar el servicio Web 627 Cmo crear un cliente de servicio Web 629 Cmo crear un nuevo proyecto de aplicacin Web ASP.NET 630 Cmo agregar una referencia Web 631 Cmo implementar los mtodos del servicio Web 632 Cmo implementar la aplicacin 635 Implementacin de proyectos en Visual Studio .NET........................................635 Cmo usar un proyecto de implementacin para implementar una aplicacin 635 Cmo implementar un proyecto usando la opcin Copiar proyecto 637 Resumen 637 29. Cmo construir controles personalizados 641
Biblioteca de controles de Windows...........................................................................641 Propiedades 642 Mtodos 644 Campos 645 Eventos 645 Aprender con un ejemplo 646
22
Como crear un temporizador de cuenta atrs Cmo crear una prueba de carga CountDown Cmo usar una biblioteca de clases Cmo crear una clase para calcular el efecto de viento Resumen 30. Cmo construir aplicaciones mviles
La red inalmbrica 659 Introduccin al Mobile Internet Toolkit 660 Emuladores 660 Nokia 660 Pocket PC 660 Microsoft Mobile Explorer 661 Cmo crear una calculadora de edades.....................................................................661 Funciones de los dispositivos mviles 666 Funcionamiento de los controles mviles 667 Cmo usar el control Calendar 667 Cmo usar el control Image 668 Paginacin en dispositivos mviles 670 Resumen 672 Parte V. C# y .NET Framework 31. Cmo trabajar con ensamblados Ensamblados Cmo encontrar ensamblados cargados Nombres seguros de ensamblado Cmo asignar la informacin de versin Cmo asignar la informacin de referencia cultural Cmo asignar la informacin de clave Cmo trabajar con la clase Assembly Cmo encontrar la informacin de ubicacin del ensamblado Cmo encontrar puntos de entrada del ensamblado Cmo cargar ensamblados Cmo trabajar con informacin de tipo de ensamblado Cmo generar cdigo nativo para ensamblados Resumen 32. Reflexin La clase Type Cmo recuperar informacin de tipo Cmo recuperar tipos mediante el nombre Cmo recuperar tipos mediante instancias 673 675 675 676 678 680 681 682 682 682 683 684 688 689 691 693 694 694 694 695
23
Cmo recuperar tipos en un ensamblado Cmo interrogar a objetos Cmo generar cdigo dinmico mediante la reflexin Resumen 33. Subprocesamiento en C# Subprocesamiento Multitarea preferente Prioridades de subprocesos y bloqueo Multiprocesamiento simtrico Cmo usar los recursos: cuantos ms, mejor Dominios de aplicacin Ventajas de las aplicaciones de varios subprocesos Aplicaciones con procesos largos Aplicaciones de sondeo y escucha Botn Cancelar Cmo crear aplicaciones multiproceso Cmo crear nuevos subprocesos Prioridad de los subprocesos Estado del subproceso Cmo unir subprocesos Cmo sincronizar subprocesos Sondeo y escucha Resumen 34. Cmo trabajar con COM
696 697 700 702 705 705 706 707 707 708 709 710 710 710 710 711 712 715 716 719 721 722 723 727
Introduccin al Contenedor al que se puede llamar en tiempo de ejecucin .... 728 Cmo crear ensamblados .NET a partir de componentes COM 729 Cmo usar la utilidad Tlbimp 729 Cmo crear un componente COM 731 Cmo usar el ensamblado de interoperabilidad desde C# 735 Cmo hacer referencia a la DLL COM desde C# 739 Cmo manejar errores de interoperabilidad...............................................................740 Cmo usar la invocacin de plataforma 743 Resumen 744 35. Cmo trabajar con servicios COM+ El espacio de nombres System.EnterpriseServices La clase ServicedComponent Cmo registrar clases con COM+ Cmo usar atributos para clases COM+ ApplicationAccessControl ApplicationActivation 747 748 752 754 756 757 757
24
ApplicationID 758 ApplicationName 758 ApplicationQueuing 758 AutoComplete 759 ComponentAccessControl 759 ConstructionEnabled 759 JustInTimeActivation 760 LoadBalancingSupported 760 SecurityRole 761 Cmo procesar transacciones 761 Propiedades ACID 762 Cmo escribir componentes de transacciones.....................................................763 Cmo acceder al contexto de objetos 765 Resumen 768 36. Cmo trabajar con los servicios remotos de .NET 771
Introduccin al entorno remoto 771 Cmo crear un ensamblado de servidor remoto 773 Cmo crear un servidor remoto 775 Cmo especificar canales y puertos 777 Cmo especificar un formato de canal 778 Espacio de nombres System.Runtime.Remoting.Channels.Tcp 779 Espacio de nombres System.Runtime.Remoting.Channels.Http 779 Cmo activar el objeto remoto 781 Cmo registrar objetos con RegisterWellKnownServiceType.......................783 Cmo registrar objetos con el mtodo Configure 785 Cmo escribir el cliente remoto 790 Resumen 794 37. C# y seguridad .NET 797
Seguridad de cdigo 798 Directivas de seguridad de cdigo 799 Permisos de cdigo 799 Seguridad de usuario 800 Seguridad .NET y basada en funciones 802 Cmo asignar las funciones Windows 802 Principales 806 Permisos de acceso a cdigo 806 Cmo crear una sencilla solicitud de cdigo de permiso 807 Denegacin de permisos 809 Cmo usar permisos basados en atributos 810 Directivas de seguridad.............................................................................................811 Niveles de directivas de seguridad 811
25
Grupos de cdigo Conjuntos de permisos con nombre Cmo alterar directivas de seguridad Resumen Parte VI. Apndices Apndice A. Manual de XML Objetivos de diseo de XML Objetivo 1: XML debe ser fcilmente utilizable en Internet Objetivo 2: XML debe admitir una amplia variedad de aplicaciones Objetivo 3: XML debe ser compatible con SGML Objetivo 4: Debe ser sencillo escribir programas que procesen documentos XML Objetivo 5: El nmero de caractersticas opcionales en XML debe mantenerse al mnimo, preferentemente a cero Objetivo 6: Los documentos XML deben ser legibles para las personas y razonablemente claros Objetivo 7: El diseo de XML debe ser preparado rpidamente Objetivo 8: El diseo de XML debe ser formal y conciso Objetivo 9: Los documentos XML deben ser fciles de crear Objetivo 10: La concisin de las marcas XML es de mnima importancia .... Breve leccin de HTML XML = HTML con etiquetas definidas por el usuario Definiciones de tipo de documento Esquemas XML Espacios de nombres XML Apndice B. Contenido del CD-ROM ndice alfabtico
812 812 813 814 817 819 819 821 821 821 822 822 822 822 823 823 823 823 827 829 831 834 839 841
26
Introduccin
La iniciativa .NET Framework de Microsoft supone el cambio ms importante en la metodologa del desarrollo de software para un sistema operativo de Microsoft desde la introduccin de Windows. Este entorno est construido usando una arquitectura que permite a los lenguajes de software trabajar juntos, compartiendo recursos y cdigo, para proporcionar a los programadores las avanzadas herramientas necesarias para construir la siguiente generacin de aplicaciones de escritorio y de Internet. Visual Studio .NET de Microsoft incluye nuevas versiones de sus productos de compilador Visual Basic y C++ dirigidas al desarrollo de .NET, al igual que un lenguaje completamente nuevo llamado C#. Este libro le mostrar cmo escribir cdigo usando este novsimo lenguaje. Todos los trminos de lenguaje tales como declaraciones, variables, bucles de control y clases, son tratados con detalle. Adems, el libro le enseara a usar C# para programar tareas con las que los programadores suelen enfrentarse en el mundo real. La ltima parte del libro explica cmo usar C# para desarrollar pginas Web, acceder a bases de datos, trabajar con objetos COM y COM+ heredados, desarrollar aplicaciones de escritorio para Windows, trabajar con varios conceptos de .NET Framework y mucho ms. El principal objetivo de este libro es el desarrollo .NET usando C# como el lenguaje de implementacin y el compilador de lnea de comandos C# de .NET Framework como la principal herramienta de desarrollo. El desarrollo de C#
29
empleando la herramienta Visual Studio .NET no se trata en este libro, aunque es algo que se puede dominar fcilmente una vez que se comprendan bien los fundamentos del desarrollo .NET usando C#.
30
31
NOTA: Resalta la informacin interesante o adicional y suele contener pequeos trozos extra de informacin tcnica sobre un tema.
TRUCO: Llaman la atencin sobre hbiles sugerencias, pistas recomendables y consejos tiles.
32
Adems en este libro se usan las siguientes convenciones tipogrficas: Los cdigos de ejemplo aparecen en un tipo de letra Courier. Las opciones de men se indican en orden jerrquico, con cada instruccin de men separada por el signo "mayor que" y en un tipo de letra Arial. Por ejemplo, Archivo>Abrir quiere decir hacer clic en el comando Archivo en la barra de men y luego seleccionar Abrir.
Las combinaciones de teclas se indican de esta forma: Control-C. Al final de cada captulo encontrar un resumen de lo que debera haber aprendido al terminar de leer el captulo.
33
Parte I
35
1 Introduccin
a C#
Durante los ltimos 20 aos, C y C++ han sido los lenguajes elegidos para desarrollar aplicaciones comerciales y de negocios. Estos lenguajes proporcionan un altsimo grado de control al programador permitindole el uso de punteros y muchas funciones de bajo nivel. Sin embargo, cuando se comparan lenguajes como Microsoft Visual Basic con C/C++, uno se da cuenta de que aunque C y C++ son lenguajes mucho ms potentes, se necesita mucho ms tiempo para desarrollar una aplicacin con ellos. Muchos programadores de C/C++ han temido la idea de cambiar a lenguajes como Visual Basic porque podran perder gran parte del control de bajo nivel al que estaban acostumbrados. Lo que la comunidad de programadores necesitaba era un lenguaje que estuviera entre los dos. Un lenguaje que ayudara a desarrollar aplicaciones rpidas pero que tambin permitiese un gran control y un lenguaje que se integrase bien con el desarrollo de aplicaciones Web, XML y muchas de las tecnologas emergentes. Facilitar la transicin para los programadores de C/C++ existentes y proporcionar a la vez un lenguaje sencillo de aprender para los programadores inexpertos son slo dos de las ventajas del nuevo lenguaje del barrio, C#. Microsoft present C# al pblico en la Professional Developer's Conference en Orlando, Florida, en el verano del 2000. C# combina las mejores ideas de lenguajes como C, C++ y Java con las mejoras de productividad de .NET Framework de Microsoft
37
y brinda una experiencia de codificacin muy productiva tanto para los nuevos programadores como para los veteranos. Este captulo profundiza en los cuatro componentes que constituyen la plataforma .NET adems de analizar la compatibilidad para las tecnologas Web emergentes. A continuacin, se analizan muchas de las funciones del lenguaje C# y se comparan con otros lenguajes populares.
.NET Framework
Microsoft dise C# desde su base para aprovechar el nuevo entorno .NET Framework. Como C# forma parte de este nuevo mundo .NET, deber comprender perfectamente lo que proporciona .NET Framework y de qu manera aumenta su productividad. .NET Framework se compone de cuatro partes, como se muestra en la figura 1.1: el entorno comn de ejecucin, un conjunto de bibliotecas de clases, un grupo de lenguajes de programacin y el entorno ASP.NET. .NET Framework fue diseado con tres objetivos en mente. Primero, deba lograr aplicaciones Windows mucho ms estables, aunque tambin deba proporcionar una aplicacin con un mayor grado de seguridad. En segundo lugar, deba simplificar el desarrollo de aplicaciones y servicios Web que no slo funcionen en plataformas tradicionales, sino tambin en dispositivos mviles. Por ltimo, el entorno fue diseado para proporcionar un solo grupo de bibliotecas que pudieran trabajar con varios lenguajes. Las siguientes secciones analizan cada uno de los componentes de .NET Framework.
Common Language Runtime Bibliotecas de Clase Lenguajes de Programacin (C#, VC++, VB.NET, JScript.NET) ASP.NET Figura 1.1. Los cuatro componentes de .NET Framework.
Desarrollo Web
.NET Framework fue diseado con una idea en mente: potenciar el desarrollo de Internet. Este nuevo incentivo para el desarrollo de Internet se llama servicios Web. Puede pensar en los servicios Web como en una pgina Web que interacta con programas en lugar de con gente. En lugar de enviar pginas Web, un servicio Web recibe una solicitud en formato XML, realiza una funcin en concreto y luego devuelve una respuesta al solicitante en forma de mensaje XML.
38
NOTA: XML, es decir, Lenguaje Extensible de Marcas, 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 representar una amplia variedad de objetos. Una tpica aplicacin para un servicio Web podra ser como una capa situada en lo alto de un sistema de facturacin de una empresa. Cuando un usuario que navega por la red compra los productos de una pgina Web, la informacin de la compra es enviada al servicio Web, que calcula el precio de todos los productos, aade una lnea a la base de datos de existencias y devuelve una respuesta con una confirmacin de pedido. Este servicio Web no slo puede interactuar con pginas Web, puede interactuar con otros servicios Web, como un sistema de cuentas de pago de una empresa. Para que el modelo de servicio Web sobreviva a la evolucin natural de los lenguajes de programacin, debe incluir muchas ms cosas que un simple interfaz para la Web. El modelo de servicio Web tambin incluye protocolos que permiten que las aplicaciones encuentren servicios Web disponibles en una red interna o en Internet. Este protocolo tambin permite a la aplicacin explorar el servicio Web y decidir cmo comunicarse con l y cmo intercambiar informacin. Para permitir el descubrimiento de servicios Web se estableci la Descripcin, descubrimiento e integracin universal (UDDI). sta permite que los servicios Web sean registrados y consultados, basndose en datos clave como el nombre de la compaa, el tipo de servicio y su localizacin geogrfica.
Desarrollo de aplicaciones
Aparte del desarrollo Web, con .NET Framework tambin puede construir las tradicionales aplicaciones Windows. Estas aplicaciones creadas con .NET Framework se basan en Windows Forms. Windows 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, estn completamente orientados a objetos y basados en clases, de forma muy parecida a los formularios objeto de Microsoft Foundation Class. Estos nuevos Windows Forms ahora admiten muchos de los controles clsicos que aparecan en Visual Studio, como Button, TextBox y Label, junto a los controles ActiveX. Aparte de los controles tradicionales, tambin admite nuevos componentes como PrintPreview , LinkLabel , ColorDialog y OpenFileDialog . La creacin de aplicaciones con .NET tambin brinda muchas mejoras no disponibles en otros lenguajes, como la seguridad. Estas medidas de seguridad pueden determinar si una aplicacin puede escribir o leer un archivo de disco. Tambin permiten insertar firmas digitales en la aplicacin para asegurarse de
39
que la aplicacin fue escrita por una fuente de confianza. .NET Framework tambin permite incluir informacin de componentes, y de versin, dentro del cdigo real. Esto hace posible que el software se instale cuando se lo pidan, automticamente o sin la intervencin del usuario. Juntas, todas estas funciones reducen los costes asistencia para la empresa.
40
Uno de los objetivos de diseo de .NET Framework era unificar los motores de ejecucin para que todos los programadores pudieran trabajar con un solo conjunto de servicios de ejecucin. La solucin de .NET Framework se llama Entorno comn de ejecucin (CLR). El CLR proporciona funciones como la gestin de memoria, la seguridad y un slido sistema de control de errores, a cualquier lenguaje que se integre en .NET Framework. Gracias al CLR, todos los lenguajes .NET pueden usar varios servicios de ejecucin sin que los programadores tengan que preocuparse de si su lenguaje particular admite una funcin de ejecucin. El CLR tambin permite a los lenguajes interactuar entre s. La memoria puede asignarse mediante cdigo escrito en un lenguaje (Visual Basic .NET, por ejemplo) y puede ser liberada con cdigo escrito en otro (por ejemplo, C#). Del mismo modo, los errores pueden ser detectados en un lenguaje y procesados en otro.
41
de Microsoft slo es cdigo que fue escrito usando un lenguaje que .NET admita y se compilaba usando una herramienta de desarrollo .NET. Esto significa que Microsoft est usando las mismas herramientas que usar para escribir su cdigo. Puede escribir cdigo capaz de ser usado en otros lenguajes .NET, exactamente lo mismo que Microsoft con su biblioteca de clases. .NET Framework permite escribir cdigo en C#, por ejemplo, y envirselo a programadores en Visual Basic .NET, que pueden usar ese cdigo que compil en sus aplicaciones. La figura 1.2 ilustra una visin muy general de las bibliotecas de clases .NET.
System.Web Services Description Discovery Protocols Ul HtmlControls WebControls System Runtime InteropServices Remoting Serialization Collections Configuration Diagnostics Reflection Caching Configuration Security SessionState Globalization Imaging System.Windows.Forms Design ComponentModes IO Net Resources Printing Text XPath Security Serialization ServiceProcess Text Threading System.Drawing Drawing2D System.Xml XSLT SQL SQL Types System.Data ADO Design
42
El desarrollo de lenguajes compatibles .NET no se limita a Microsoft. El grupo .NET de Microsoft ha publicado documentacin que muestra cmo los proveedores pueden hacer que sus lenguajes funcionen con .NET, y estos proveedores estn haciendo lenguajes como COBOL y Perl compatibles con .NET Framework. Actualmente, una tercera parte de los proveedores e instituciones estn preparando ms de 20 lenguajes capaces de integrarse en .NET Framework.
Entorno ASP.NET
Internet fue concebida en un principio para enviar contenido esttico a los clientes Web. Estas pginas Web nunca cambiaban y eran las mismas para todos los usuarios que navegaban hasta esa localizacin. Microsoft lanz servidores activos para permitir la creacin de pginas dinmicas basadas en la aportacin e interaccin del usuario con una pgina Web. Esto se consigui mediante el uso de secuencias de comandos que funcionaban por detrs de la pgina Web, generalmente escritas en VB Script. Cuando los usuarios visitaban una pgina Web, se les poda pedir que verificasen la informacin (manualmente o con una cookie), y luego la secuencia de comandos poda generar una pgina Web que le era devuelta al usuario. ASP.NET mejora al original ASP proporcionando "cdigo detrs". En ASP, HTML y las secuencias de comando se mezclaban en un documento. Con ASP.NET y su "cdigo detrs", se puede separar el cdigo y HTML. Ahora, cuando la lgica de una pgina Web necesite cambiar, no hace falta buscar por cientos o miles de lneas de HTML para localizar la secuencia de comandos que necesita modificarse. De forma parecida a Windows Forms, ASP.NET admite Web Forms. Los Web Forms permiten arrastrar y colocar controles en sus formularios y codificarlos como hara en cualquier tpica aplicacin Windows. Como ASP.NET usa .NET Framework, tambin usa el compilador Justo a tiempo (JIT). Las pginas ASP tradicionales se ejecutaban muy lentamente porque el cdigo era interpretado. ASP.NET compila el cdigo cuando es instalado en el servidor o la primera vez que se necesita, lo que aumenta enormemente la velocidad.
Historia de C, C++ y C#
El lenguaje de programacin C# fue creado con el mismo espritu que los lenguajes C y C++. Esto explica sus poderosas prestaciones y su fcil 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 ms pesadas (cmo los punteros). Esta seccin echa un vistazo a los lenguajes C y C++, siguiendo su evolucin hasta C#.
43
El lenguaje de programacin C fue diseado en un principio para ser usado en el sistema operativo UNIX. C se us para crear muchas aplicaciones UNIX. incluyendo un compilador de C, y a la larga se us para escribir el mismo UNIX. Su amplia aceptacin en el mundo acadmico se ampli al mundo comercial y los proveedores de software como Microsoft y Borland publicaron compiladores C para los ordenadores personales. El API original para Windows fue diseado para trabajar con cdigo Windows escrito en C y el ltimo conjunto de API bsicos del sistema operativo Windows sigue siendo compatible con C hoy en da. Desde el punto de vista del diseo, C careca de un detalle que ya ofrecan otros lenguajes como Smalltalk: el concepto de objeto. Piense en un objeto como en una coleccin de datos y un conjunto de operaciones que pueden ser realizadas sobre esos datos. La codificacin con objetos se puede lograr usando C, pero la nocin de objeto no era obligatoria para el lenguaje. Si quera estructurar su cdigo para que simulara un objeto, perfecto. Si no, perfecto tambin. En realidad a C no le importaba. Los objetos no eran una parte fundamental del lenguaje, por lo que mucha gente no prest mucha atencin a este estndar de programacin. Una vez que el concepto de orientacin a objetos empez a ganar aceptacin, se hizo evidente que C necesitaba ser depurado para adoptar este nuevo modo de considerar al cdigo. C++ fue creado para encarnar esta depuracin. Fue diseado para ser compatible con el anterior C (de manera que todos los programas escritos en C pudieran ser tambin programas C++ y pudieran ser compilados con un compilador de C++). La principal aportacin a C++ fue la compatibilidad para el nuevo concepto de objeto. C++ incorpor compatibilidad para clases (que son "plantillas" de objetos) y permiti que toda una generacin de programadores de C pensaran en trminos de objetos y su comportamiento. El lenguaje C++ es una mejora de C, pero an as presenta algunas desventajas. C y C++ pueden ser difciles de manejar. A diferencia de lenguajes fciles de usar como Visual Basic, C y C++ son lenguajes de muy "bajo nivel" y exigen de mucho cdigo para funcionar correctamente. Tiene que escribir su propio cdigo para manejar aspectos como la gestin de memoria y el control de errores. C y C++ pueden dar como resultado aplicaciones muy potentes, pero debe asegurarse de que el cdigo funciona bien. Un error en la escritura del programa puede hacer que toda la aplicacin falle o se comporte de forma inesperada. Como el objetivo al disear C++ era retener la compatibilidad con el anterior C, C++ fue incapaz de escapar de la naturaleza de bajo nivel de C. Microsoft dise C# de modo que retuviera casi toda la sintaxis de C y C++. Los programadores que estn familiarizados con esos lenguajes pueden escoger el cdigo C# y empezar a programar de forma relativamente rpida. Sin embargo, la gran ventaja de C# consiste en que sus diseadores decidieron no hacerlo compatible con los anteriores C y C++. Aunque esto puede parecer un mal asunto, en realidad es una buena noticia. C# elimina las cosas que hacan que fuese difcil trabajar con C y C++. Como todo el cdigo C es tambin cdigo C++, C++ tena que mantener todas las rarezas y deficiencias de C. C# parte de cero y sin ningn
44
requisito de compatibilidad, as que puede mantener los puntos fuertes de sus predecesores y descartar las debilidades que complicaban las cosas a los programadores de C y C++.
Introduccin a C#
C#, el nuevo lenguaje presentado en .NET Framework, procede de C++. Sin embargo, C# es un lenguaje orientado a objetos (desde el principio), moderno y seguro.
Clases
Todo el cdigo y los datos en C# deben ser incluidos en una clase. No puede definir una variable fuera de una clase y no puede escribir ningn cdigo que no est en una clase. Las clases pueden tener constructores, que se ejecutan cuando 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 object. C# admite tcnicas de versiones para ayudar a que sus clases evolucionen con el tiempo mientras mantienen la compatibilidad con cdigo que use versiones anteriores de sus clases. Por ejemplo, observe la clase llamada Family. Esta clase contiene los dos campos estticos que incluyen el nombre y el apellido de un miembro de la familia junto con un mtodo que devuelve el nombre completo del miembro de la familia.
class Family { public string FirstName; public string LastName; public string FullName() { return FirstName + LastName; } }
NOTA: La herencia simple significa que una clase de C# slo se puede derivar de una clase base.
45
C# le permite agrupar sus clases en una coleccin de clases llamada espacio de nombres. Los espacios de nombres tienen nombres y pueden servir de ayuda para organizar colecciones de clases en agrupaciones lgicas. A medida que empiece a aprender C#, descubrir que todos los espacios de nombres relevantes para .NET Framework empiezan con el trmino System. Microsoft tambin ha decidido incluir algunas clases que ayudan a la compatibilidad con versiones anteriores y al acceso a las API. Estas clases se incluyen en el espacio de nombres Microsoft.
Tipos de datos
C# permite trabajar con dos tipos de datos: de valor y de referencia. Los de valor contienen valores reales. Los de referencia contienen referencias a valores almacenados en algn lugar de la memoria. Los tipos primitivos como char, int y float, junto con los valores y estructuras 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 ). Tambin puede definir en el cdigo sus propios tipos de valor y referencia. Todos los tipos de valor y de referencia derivan en ltima instancia de un tipo base llamado object. C# le permite convertir un valor de un tipo en un valor de otro tipo. Puede trabajar con conversiones implcitas y explcitas. Las conversiones implcitas siempre funcionan y nunca pierden informacin (por ejemplo, puede convertir un int en un long sin perder ningn dato porque un long es mayor que un int ). Las conversiones explcitas pueden producir perdidas de datos (por ejemplo, convertir 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 cdigo para que se produzca una conversin explcita. En C# puede trabajar 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 estructuras pueden tener datos miembros llamados propiedades y campos. Los campos son variables que estn asociadas a la clase o estructura a la que pertenecen. Por ejemplo, puede definir una estructura llamada Empleado que tenga un campo llamado Nombre . Si define una variable de tipo Empleado llamada EmpleadoActual , puede recuperar el nombre del empleado escribiendo EmpleadoActual.Nombre . Las propiedades son como los campos, pero permiten escribir cdigo que especifique lo que debera ocurrir cuando el cdigo acceda al valor. Si el nombre del empleado debe leerse de una base de datos, por ejemplo, puede escribir cdigo que diga "cuando alguien pregunte el valor de la propiedad Nombre , lee el nombre de la base de datos y devuelve el nombre como una cadena".
46
Funciones
Una funcin es un fragmento de cdigo que puede ser invocado y que puede o no devolver un valor al cdigo que lo invoc en un principio. Un ejemplo de una funcin podra ser la funcin FullName mostrada anteriormente en este captulo, en la clase F a m i l y . Una funcin suele asociarse a fragmentos de cdigo que devuelven informacin, mientras que un mtodo no suele devolver informacin. Sin embargo, para nuestros propsitos, generalizamos y nos referimos a las dos como funciones. Las funciones pueden tener cuatro tipos de parmetros: Parmetros de entrada: tienen valores que son enviados a la funcin, pero la funcin no puede cambiar esos valores. Parmetros de salida: no tienen valor cuando son enviados a la funcin, pero la funcin puede darles un valor y enviar el valor de vuelta al invocador. Parmetros de referencia: introducen una referencia en otro valor. Tienen un valor de entrada para la funcin y ese valor puede ser cambiado dentro de la funcin. Parmetros Params: definen un nmero variable de argumentos en una lista.
C# y el CLR trabajan juntos para brindar gestin de memoria automtica. No necesita escribir cdigo que diga "asigna suficiente memoria para un nmero entero" o "libera la memoria que est usando este objeto". El CLR monitoriza el uso de memoria y recupera automticamente ms cuando la necesita. Tambin libera memoria automticamente cuando detecta que ya no est siendo usada (esto tambin se conoce como recoleccin de objetos no utilizados). C# proporciona varios operadores que le permiten escribir expresiones matemticas y de bits. Muchos (pero no todos) de estos operadores pueden ser redefinidos, permitindole cambiar la forma en que trabajan estos operadores. C# admite una larga lista de expresiones que le permiten definir varias rutas de ejecucin dentro del cdigo. Las instrucciones de flujo de control que usan palabras clave como if, switch, while, for, break y continue permiten al cdigo bifurcarse por caminos diferentes, dependiendo de los valores de sus variables. Las clases pueden contener cdigo y datos. Cada miembro de una clase tiene algo llamado mbito de accesibilidad, que define la visibilidad del miembro con respecto a otros objetos. C# admite los mbitos de accesibilidad public,
protected, internal, protected internal y private .
Variables
Las variables pueden ser definidas como constantes. Las constantes tienen valores que no pueden cambiar durante la ejecucin del cdigo. Por ejemplo, el
47
valor de pi es una buena muestra de una constante porque el valor no cambia a medida que el cdigo se ejecuta. Las declaraciones de tipo de enumeracin especifican un nombre de tipo para un grupo de constantes relacionadas. Por ejemplo, puede definir una enumeracin de planetas con valores de Mercurio, Venus, Tierra, Marte, Jpiter, Saturno, Urano, Neptuno y Plutn, y usar estos nombres en el cdigo. Usando los nombres de enumeraciones en el cdigo hace que sea ms fcil leerlo que si usara un nmero para representar a cada planeta. C# incorpora un mecanismo para definir y procesar eventos. Si escribe una clase que realiza una operacin muy larga, quizs quiera invocar un evento cuando la operacin se complete. Los clientes pueden suscribirse a ese evento e incluirlo en el cdigo, lo que permite que se les pueda avisar cuando haya acabado su operacin. El mecanismo de control de eventos en C# usa delegados, que son variables que se refieren a una funcin. NOTA: Un controlador de eventos es un procedimiento en el cdigo que determina las acciones que deben llevarse a cabo cuando ocurra un evento, como que el usuario pulse un botn. Si la clase contiene un conjunto de valores, los clientes quizs quieran acceder a los valores como si la clase fuera una matriz. Puede conseguirlo escribiendo un fragmento de cdigo conocido como indexador. Suponga que escribe una clase llamada ArcoIris, por ejemplo, que contenga el conjunto de los colores del arco iris. Los visitantes querrn escribir MiArcoIris[0] para recuperar el primer color del arco iris. Puede escribir un indexador en la clase ArcoIris para 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, mtodos y eventos que especifican un conjunto de funcionalidad. Las clases C# pueden implementar 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 ningn cdigo existente. Una vez que la interfaz ha sido publicada, no se puede modificar, pero puede evolucionar mediante herencia. Las clases C# pueden implementar muchas interfaces, aunque las clases slo pueden derivarse de una clase base. Veamos un ejemplo de la vida real que puede beneficiarse del uso de interfaces para ilustrar su papel extremadamente positivo en C#. Muchas de las aplicaciones disponibles hoy en da admiten mdulos complementarios. Supongamos que tiene un editor de cdigo para escribir aplicaciones. Este editor, al ejecutarse, puede cargar mdulos complementarios. Para ello, el mdulo complementario debe seguir unas cuantas reglas. El mdulo complementario de DLL debe expor-
48
tar una funcin llamada CEEntry y el nombre de la DLL debe empezar por CEd . Cuando ejecutamos nuestro editor de cdigo, este busca en su directorio de trabajo todas las DLL que empiecen por CEd . Cuando encuentra una, la abre y a continuacin utiliza GetProcAddress para localizar la funcin CEEntry dentro de la DLL, verificando as que ha seguido todas las reglas exigidas para crear un mdulo complementario. Este mtodo de creacin y apertura de mdulos complementarios es muy pesado porque sobrecarga al editor de cdigo con ms tareas de verificacin de las necesarias. Si usramos una interfaz en este caso, la DLL del mdulo complementario podra haber implementado una interfaz, garantizando as que todos los mtodos necesarios, propiedades y eventos estn presentes en la propia DLL y que funciona como especifica la documentacin.
Atributos
Los atributos aportan informacin adicional sobre su clase al CLR. Antes, si quera que la clase fuera autodescriptiva, tena que seguir un enfoque sin conexin, en el que la documentacin fuera almacenada en archivos externos como un archivo IDL o incluso archivos HTML. Los atributos solucionan este problema permitiendo al programador vincular informacin a las clases (cualquier tipo de informacin). Por ejemplo, puede usar un atributo para insertar informacin de documentacin en una clase, explicando cmo debe actuar al ser usada. Las posibilidades son infinitas y sa es la razn por la que Microsoft incluye tantos atributos predefinidos en .NET Framework.
Cmo compilar C#
Ejecutar el cdigo C# con el compilador de C# produce dos importantes conjuntos de informacin: el cdigo y los metadatos. Las siguientes secciones describen estos dos elementos y luego terminan examinando el componente esencial del cdigo .NET: el ensamblado.
49
MSIL no es un conjunto de instrucciones especficas para una CPU fsica. MSIL no sabe nada de la CPU de su equipo y su equipo no conoce nada de MSIL. Entonces, cmo se ejecuta el cdigo .NET si su CPU no lo puede interpretar? La respuesta es que el cdigo MSIL se convierte en cdigo especfico de CPU cuando se ejecuta por primera vez. Este proceso se llama compilacin "Justo a tiempo" o JIT. El trabajo de un compilador JIT es convertir el cdigo genrico MSIL en cdigo que su equipo pueda ejecutar en su CPU. Quizs se est preguntando por lo que parece ser un paso innecesario en el proceso. Por qu generar MSIL cuando un compilador podra generar directamente cdigo especfico para la CPU? Despus de todo, los compiladores siempre lo han hecho en el pasado. Hay un par de razones que lo explican. En primer lugar, MSIL permite que el cdigo compilado se transfiera fcilmente a hardware diferente. Suponga que ha escrito algo de cdigo C# y le gustara ejecutarlo en su ordenador personal y en su dispositivo porttil. Es muy probable que estos dos equipos tengan diferentes CPU. Si slo tiene un compilador de C# para una CPU especfica, entonces necesita dos compiladores de C#: uno para la CPU de su ordenador personal y otro para la CPU de su dispositivo porttil. Tendra que compilar el cdigo dos veces, asegurndose de poner el cdigo adecuado para cada equipo. Con MSIL slo se compila una vez. Al instalar .NET Framework en su ordenador personal se incluye un compilador JIT que convierte el MSIL en cdigo especfico para la CPU de su ordenador personal. Al instalar .NET Framework en su ordenador porttil se incluye un compilador JIT que convierte el mismo MSIL en cdigo especfico para la CPU de su dispositivo porttil. Ahora tiene un solo cdigo base MSIL que puede ejecutarse en cualquier equipo que tenga un compilador JIT .NET. El compilador JIT en ese equipo se ocupa de hacer que el cdigo se ejecute. Otra razn para que el compilador use MSIL es que el conjunto de instrucciones puede leerse fcilmente por un proceso de verificacin. Parte del trabajo del compilador JIT es verificar el cdigo para asegurarse de que resulte lo ms claro posible. El proceso de verificacin asegura que el cdigo accede a la memoria correctamente y de que est usando los tipos de variables correctos al llamar a mtodos que esperan un tipo especfico. Estos procesos de verificacin se aseguran de que el cdigo no ejecute ninguna instruccin que origine un fallo. El conjunto de instrucciones MSIL fue diseado para hacer este proceso de verificacin relativamente sencillo. Los conjuntos de instrucciones especficos para cada CPU estn optimizados para la rpida ejecucin del cdigo, pero producen cdigo que puede ser difcil de leer y, por tanto, de verificar. Tener un compilador de C# que produce directamente cdigo especfico para CPU puede hacer difcil la verificacin del cdigo, o incluso imposible. Al permitir al compilador JIT de .NET Framework que verifique el cdigo se asegura de que el cdigo acceda a la memoria en un modo libre de fallos y que los tipos de variable sean usados correctamente.
50
Metadatos
El proceso de compilacin tambin produce metadatos, lo que es una parte importante de la historia de cmo se comparte el cdigo .NET. Tanto si usa C# para construir una aplicacin para un usuario final como si lo usa para construir una biblioteca de clases que ser usada por la aplicacin de alguna otra persona, querr usar cdigo .NET ya compilado. Ese cdigo puede ser proporcionado por Microsoft como parte de .NET Framework, o por un usuario a travs de Internet. La clave para usar este cdigo externo es hacer que el compilador de C# sepa qu clases y variables estn en el otro cdigo base para que pueda comparar el cdigo fuente que ha escrito con el cdigo que aparece en el cdigo base precompilado con el que est trabajando. Piense en los metadatos como en "tablas de contenidos" para el cdigo compilado. El compilador de C# coloca metadatos en el cdigo compilado junto al MSIL generado. Este metadato describe con exactitud todas las clases que escriba y cmo estn estructuradas. Todos los mtodos y variables de las clases estn completamente descritos en los metadatos, listos para ser ledos por otras aplicaciones. Visual Basic .NET, por ejemplo, puede leer los metadatos para que una biblioteca .NET proporcione la funcin IntelliSense de listar todos los mtodos disponibles para una clase en concreto. Si alguna vez ha trabajado con COM (Modelo de objetos componentes), quizs est familiarizado con las bibliotecas de tipos. Las bibliotecas de tipos tratan de proporcionar una funcionalidad "de tabla de contenidos" similar para objetos COM. Sin embargo, las bibliotecas de tipos presentaban algunas limitaciones, una de las cules consista en que no se incluan todos los datos importantes relacionados con el objeto. Los metadatos de .NET no presentan este inconveniente. Toda la informacin necesaria para describir una clase en un cdigo est situada en el metadato. Los metadatos tienen todas las ventajas de las bibliotecas de tipos COM, pero sin sus limitaciones.
Ensamblados
A veces usar C# para construir una aplicacin para un usuario final. Estas aplicaciones se presentan como archivos ejecutables con extensin .EXE. Windows siempre ha trabajado con archivos .EXE como programas de aplicacin y C# admite a la perfeccin la construccin de archivos .EXE. Sin embargo, puede haber ocasiones en las que no quiera construir una aplicacin completa. Quizs quiera construir en su lugar una biblioteca de cdigo que pueda ser usada por otras personas. Quizs tambin quiera construir algunas clases de utilidad, por ejemplo, y luego transferir el cdigo a un programador de Visual Basic .NET, que usar sus clases en una aplicacin de Visual Basic .NET. En casos como ste, no construir una aplicacin, sino un ensamblado. Un ensamblado es un paquete de cdigo y metadatos. Cuando utiliza un conjunto de clases en un ensamblado, est usando las clases como una unidad y estas clases comparten el mismo nivel de control de versin, informacin de seguridad
51
y requisitos de activacin. Imagine un ensamblado como una "DLL lgica". Si no est familiarizado con Microsoft Transaction Server o COM+, puede imaginar un ensamblado como el equivalente .NET de un paquete. Hay dos tipos de ensamblados: ensamblados privados y ensamblados globales. Al construir el ensamblado, no necesita especificar si quiere construir un ensamblado privado o global. La diferencia es ostensible cuando se implementa el ensamblado. Con un ensamblado privado, hace que el cdigo est disponible para una sola aplicacin. El ensamblado se empaqueta como una DLL y se instala en el mismo directorio que la aplicacin que lo est usando. Con el uso de un ensamblado privado, la nica aplicacin que puede usar el cdigo es el ejecutable situado en el mismo directorio que el ensamblado. Si quiere compartir el cdigo con varias aplicaciones, quizs quiera considerar el uso del cdigo como ensamblado global. Los ensamblados globales pueden ser usados por cualquier aplicacin .NET en el sistema, sin importar el directorio en el que est instalada. Microsoft incorpora ensamblados en .NET Framework y cada uno de los ensamblados de Microsoft se instala como ensamblado global. .NET Framework contiene una lista de ensamblados globales en un servicio llamado cach de ensamblado global y el SDK de .NET Microsoft Framework incluye utilidades para instalar y eliminar ensamblados de la cach de ensamblado global.
Resumen
En este captulo se han explicado las bases de .NET Framework. Tras seguir la evolucin desde C a C++ y hasta C#, se han examinado los puntos fuertes de la lista de prestaciones de C#. Tambin se ha investigado el producto del compilador de C#, el cdigo MSIL y los metadatos, y se ha revisado el uso de ensamblados como los bloques de construccin esenciales del cdigo .NET compilado.
52
Este captulo le gua a travs del desarrollo de una sencilla aplicacin de C#. Tambin aprender cmo estn estructuradas las aplicaciones C# ms sencillas y cmo invocar al compilador de C# para convertir el cdigo fuente en cdigo que pueda ser ejecutado por .NET Framework. Finalmente, aprender a documentar el cdigo empleando comentarios de cdigo fuente y cmo convertir automticamente sus comentarios en un documento XML.
55
ciones en C#, sino que tambin servir para aprender las bases necesarias para compilar aplicaciones. Adems, el uso del Bloc de notas servir para demostrarle que no necesita confiar en ningn asistente para generar el cdigo. Puede simplemente concentrarse en el lenguaje mismo, sin tenor que aprender los detalles de un IDE. Sin embargo, tenga en cuenta que para las aplicaciones ms grandes quizs prefiera usar un editor que muestre los nmeros de lnea, lo que puede ser muy til cuando se est buscando cdigo defectuoso.
System.Console.WriteLine("Hello World!");
} }
56
El mtodo Main()
Todas las aplicaciones escritas en C# deben constar de un mtodo llamado M a i n ( ) . Un mtodo es un conjunto de instrucciones que realizan una accin. Este mtodo puede devolver informacin a la seccin de cdigo que lo llam, pero en determinadas circunstancias no es necesario que lo haga. NOTA: Los trminos mtodo y funcin suelen usarse de forma indistinta, pero hay una diferencia. Un mtodo es una funcin contenida en una clase. Una funcin 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 aadir cdigo fuera de una clase, nunca tendr una funcin. La palabra clave public en la declaracin del mtodo Main() tambin contiene la palabra public , que informa al compilador de que el mtodo Main() debe ser pblicamente accesible. El mtodo Main() no slo es accesible por otros mtodos desde dentro de la aplicacin, sino tambin externamente por otras aplicaciones. Al declarar el mtodo Main() como pblico, est creando un punto de entrada para que Windows inicie la aplicacin cuando un usuario lo desee. Cuando un usuario haga doble clic sobre el icono de la aplicacin HelloWorld, Windows explorar el ejecutable en busca de un punto de entrada con ese nombre. Si no encuentra una entrada, la aplicacin no podr ejecutarse. La palabra Static en la declaracin del mtodo significa que el compilador slo debe permitir que exista en memoria una copia del mtodo por vez. Como el mtodo Main() es el punto de entrada a la aplicacin, sera catastrfico permitir que el punto de entrada se abriese ms de una vez, ya que permitira ms de una copia de la aplicacin en memoria e, indudablemente, algunos errores graves. Justo antes de la palabra Main, ver la palabra Void . Void es lo que la funcin principal devuelve cuando ha completado su ejecucin. Significa que la aplicacin no devuelve ningn valor despus de haberse completado. Esta aplicacin de ejemplo no es muy avanzada, as que no necesita devolver ningn valor. Sin embargo, en circunstancias normales, la funcin Main() devolvera un valor entero reemplazando la palabra void por i n t . Valores de devolucin vlidos son cualquier tipo de dato simple definido en .NET Framework. De forma muy parecida a una declaracin de clase, cualquier mtodo que defina debe tambin contener una llave de apertura y otra de cierre entre las que se debe colocar todo el cdigo del mtodo. Puede ver las llaves de apertura y de cierre para el mtodo Main() en las lneas 4 y 6 en el listado 2.1.
57
se ejecuta desde una ventana de la consola, el texto debera aparecer en la pantalla. Si ejecuta este comando desde un entorno de Visual Studio, cualquier resultado que produzca aparecer en la ventana de salida. Ya hemos aprendido que todas las funciones de C# deben ser definidas dentro de una clase. Las funciones de .NET Framework no son una excepcin. La funcin WriteLine() se encuentra en una clase llamada Console . La palabra clave Console , usada justo antes de la llamada a la funcin WriteLine(), indica al compilador que la funcin WriteLine() se encuentra en una clase llamada Console . La clase Console es una de las muchas clases de .NET Framework y la funcin WriteLine() es un miembro de la clase Console . El nombre de la clase est separado del nombre de la funcin que se invoca por medio de un punto. El nombre System aparece inmediatamente antes del nombre de clase Console . Las clases de .NET Framework estn organizadas en grupos llamados espacios de nombres. Los espacios de nombres se explican con ms detalle en un captulo posterior. Por ahora, piense en los espacios de nombres como en una coleccin de clases. La clase Console se encuentra en un espacio de nombres de .NET Framework llamado System y debe escribir el nombre de este espacio de nombres en el cdigo. El compilador de C# necesita encontrar el cdigo de WriteLine() para que la aplicacin se ejecute correctamente y debe dar al compilador suficiente informacin sobre los espacios de nombres y las clases antes de encontrar el cdigo de WriteLine(). El texto que se incluye dentro de los parntesis de WriteLine() es una cadena. Una cadena en C# es una coleccin de caracteres encerrados entre comillas y guardados juntos como unidad. Al colocar la cadena entre los parntesis se indica al compilador que queremos pasar la cadena como parmetro de la funcin WriteLine(). La funcin WriteLine() escribe una cadena en la consola y el parmetro de la funcin indica a WriteLine() qu cadena debe escribirse. La lnea 5 incluye una gran cantidad de informacin que puede interpretarse de la siguiente forma: "Compilador C#, quiero llamar a WriteLine() con el parmetro de cadena 'Hello World!'. La funcin WriteLine() se incluye en una clase llamada Console y la clase Console se incluye en un espacio de nombres llamado System ". La lnea 5 termina con un punto y coma. Todas las instrucciones deben terminar con un punto y coma. El punto y la coma separan una instruccin de otra en C#.
58
NOTA: Antes de compilar el ejemplo en C#, debe asegurarse de que el compilador de C# est en su Path. La aplicacin csc.exe generalmente est en la carpeta C:\Windows\Microsoft.NET\Framework\ v1.0.xxxx (reemplace V1.0.Xxxx con su versin de .NET Framework), lo que puede comprobar buscndola en Windows. Para aadir entradas a su ruta, busque en la Ayuda de Windows la palabra Path. A continuacin abra un smbolo de comandos y dirjase a la carpeta en la que guard el archivo HelloWorld.cs . Una vez all, puede escribir el siguiente comando:
csc HelloWorld.cs
El comando csc invoca al compilador C# de .NET Framework. Al ejecutar este comando se genera un ejecutable llamado HelloWorld.exe , que puede ser ejecutado exactamente igual que cualquier aplicacin de Windows. Si ejecutamos este archivo, se escribir texto en la ventana de la consola tal y como se muestra en la figura 2.1.
c:\ C:\WINDOWS\System32\cmd.exe C:\>helloworld Hello World! C:\>
Figura 2.1. La ventana emergente de comando muestra la aplicacin Hello World en accin.
59
palabras reservadas. Las palabras class, static y void son las palabras reservadas del listado 2.1. Cada palabra clave posee en el lenguaje C# un significado especial. La siguiente lista contiene todas las palabras clave definidas en C#. abstract as base bool break byte case Catch char checked class const continue decimal default delegate do double else enum event explicit extern false finally fixed float for foreach goto if implicit in int interface internal is lock long namespace new null object operator out override pa rams private protected public readonly ref return sbyte sealed short sizeof stackalloc static string struct switch this throw true try typeof uint ulong unchecked unsafe ushort us ing virtual void while
Los identificadores son los nombres que se usan en las aplicaciones. C# no reserva nombres de identificadores. Los identificadores son palabras que designan objetos en el cdigo C#. Su clase necesita un nombre y se ha usado el nombre HelloWorld para su clase. Esto convierte al nombre HelloWorld en un identificador. Su mtodo tambin necesita un nombre y se ha usado el nombre Main para su funcin. Esto convierte al nombre Main en un identificador. El compilador de C# no permite normalmente usar ninguna de las palabras clave reservadas como nombres de identificador. Obtendr un error si, por ejemplo, intenta aplicar el nombre s t a t i c a una clase. Sin embargo, si realmente necesita usar el nombre de una palabra clave como identificador, puede poner delante del identificador el smbolo @. Esto invalida el error del compilador y permite usar una palabra clave como identificador. El listado 2.2 muestra cmo hacerlo. Es una modificacin del cdigo del listado 2.1 y define la palabra virtual como el nombre de la clase.
60
Listado 2.2. Cmo usar la palabra clave virtual como identificador de clase
class @virtual
{
static
{
void Main()
System.Console.WriteLine("Hello World!");
} }
Sin el precedente smbolo @, obtendra un error del compilador como el que se muestra en la figura 2.2.
c:\ C:\WINDOWS\System32\cmd.exe C:\>csc HelloWorld.cs Conpilador de Microsoft (R) Visual C# .NET versin 7.00.9466 para Microsoft (R) .NET Framework versin 1.0.3705 (C) Microsoft Microsoft Corporation 2001. Reservados todos los derechos. HelloWorld.cs(1,7): error CS1041: Se esperaba un identificador y 'virtual' es una palabra clave HelloWorld.cs(2,1): error CS1031: Se esperaba un tipo C:\>
61
disposicin alternativa del cdigo usando caracteres de espacio en blanco diferentes. No tema experimentar con el estilo que ms le guste.
Listado 2.3. Una disposicin de espacios en blanco alternativa Class HelloWorld
{
static void Main()
{
System.Console.WriteLine("Hello World!");
} }
Si compila y ejecuta el listado 2.3, ver que se comporta exactamente igual que el cdigo del listado 2.1: produce la cadena "Hello World!". La nueva disposicin de espacios en blanco no tiene ningn efecto en el comportamiento del cdigo que se ejecuta a la hora de la ejecucin.
62
La funcin Main() definida en el listado 2.1 no devuelve nada (de ah la palabra clave v o i d ) y no toma argumentos (de ah los parntesis vacos). El compilador C#, de hecho, acepta cualquiera de las cuatro posibles construcciones de la funcin M a i n ( ) : public static void Main() public static void Main(string[] public static int Main() public static int Main(string[] Arguments) 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. Cada cadena de la matriz se corresponde a un argumento de la lnea de comando suministrado al ejecutar el programa. Por ejemplo, suponga que modifica el cdigo del listado 2.1 para que el mtodo Main() acepte una matriz de cadenas como argumento. Adems, suponga que ejecuta ese cdigo y suministra algunos argumentos de lnea de comandos:
Listing2-1.exe Param1 Param2 Param3
En este caso, la matriz de cadenas que se pasa a la funcin M a i n ( ) tiene los siguientes contenidos:
Arguments[0]: Param1 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 palabra clave int de la declaracin. Los valores enteros devueltos por M a i n ( ) se usan como cdigos de fin de programa. Por ejemplo, suponga que desea disear sus aplicaciones para que devuelvan un valor (supongamos 0) si la operacin resulta satisfactoria y otro valor (supongamos 1) si la operacin no se cumple. Si ejecuta la aplicacin .NET desde un entorno que pueda leer este cdigo de terminacin de programa, tiene suficiente informacin para determinar si el programa se ejecut satisfactoriamente. La ltima variante de la funcin M a i n ( ) , public static int M a i n ( s t r i n g [ ] Arguments) , especifica una funcin que proporciona argumentos de lnea de comando en una matriz de cadenas y permite a la funcin devolver un cdigo de terminacin de programa. Debe tener presente algunas cosas cuando trabaje con la funcin M a i n ( ) : Las formas de devolucin void de la funcin M a i n ( ) siempre tienen un cdigo de terminacin de programa de 0.
63
La palabra clave static es necesaria en todas las variantes de la funcin M a i n ( ) . Cuando se ejecuta una aplicacin de C#, el usuario siempre proporciona los argumentos de la lnea de comando. Sin embargo, si la aplicacin de C# est escrita con una de las variantes de la funcin M a i n ( ) que no toma argumentos, la aplicacin ser incapaz de leerlos. Est permitido que el usuario especifique argumentos en una aplicacin de C# que no fue escrita para admitirlos (aunque no ser muy til).
*/
/*
comentario interno
*/
ms texto de comentario externo
*/
64
No puede incrustar un comentario normal en otro porque el compilador encuentra los primeros caracteres */ y da por hecho que ha alcanzado el final del comentario de varias lneas. A continuacin, supone que el siguiente texto seguido por los caracteres es cdigo fuente de C# e intenta interpretarlo como tal. Sin embargo, puede incrustar un comentario de una sola lnea en un comentario normal:
/* comentario externo // comentario interno ms texto de comentario externo */
El listado 2.4 muestra la aplicacin Hello World! con comentarios de documentacin XML.
Listado 2.4. La aplicacin Hello World! con comentarios XML /// /// /// /// La clase HelloWorld es la nica clase de la clase "HelloWorld". La clase implementa la funcin Main() de la aplicacin. La clase no contiene otras funciones. HelloWorld sta es la funcin Main() para la clase del listado 2.4. No devuelve un valor y no toma ningn argumento. Escribe el texto "Hello World!" en la consola y luego sale.
65
World!");
Puede compilar esta aplicacin con la opcin /doc para generar documentacin XML para el cdigo fuente:
csc /doc:HelloWorld.xml HelloWorld.cs
El compilador produce un HelloWorld.exe como era de esperar y tambin un archivo llamado HelloWorld.xml. Este archivo contiene un documento XML con sus comentarios de documentacin XML incrustados en l. El listado 2.5 muestra el documento XML que se genera cuando se compila con la opcin / doc el cdigo del listado 2.4.
Listado 2.5. Documento X M L generado para el cdigo del listado 2.4
<?xml version="1.0"?> < doc> <assembly> <name>HelloWorld</name> </assembly> <members> <member name="T:HelloWorld"> La clase HelloWorld es la nica clase de la clase "HelloWorld". La clase implementa la funcin Main() de la aplicacin. La clase no contiene otras funciones. </member> <member name="M:HelloWorld.Main"> Esta es la funcin Main() para la clase del listado 2.4. No devuelve un valor y no toma ningn argumento. Escribe el texto "HelloWorld!" en la consola y luego sale. </member> </members> </doc>
Ahora puede escribir una hoja de estilo para este documento en XML y mostrarlo en una pgina Web, proporcionando a los dems usuarios documentacin actualizada de su cdigo. La principal porcin del documento XML est en el elemento <members>. Este elemento contiene una etiqueta <member> para cada objeto documentado en el cdigo fuente. La etiqueta <member> contiene un atributo, name , que designa al miembro documentado. El valor del atributo name empieza con un prefijo de una letra que describe el tipo de informacin en cuestin. La tabla 2.1 describe los posibles valores del atributo del nombre y su significado.
66
Prefijo E F M N P T
Significado El elemento proporciona documentacin de un evento. El elemento proporciona documentacin de un campo. El elemento proporciona documentacin de un mtodo. El elemento proporciona documentacin de un espacio de nombres. El elemento proporciona documentacin de una propiedad. El elemento proporciona documentacin de un tipo definido por el usuario. ste puede ser una clase, una interfaz, una estructura, una enumeracin 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 para los miembros de tipo. Para los miembros de mtodo, el atributo name= indica el nombre de la clase que contiene el mtodo, seguida por un punto y a continuacin el nombre del mtodo. Sus comentarios de documentacin XML pueden incluir cualquier elemento XML vlido para ayudarle en su tarea de documentacin. La documentacin de .NET Framework recomienda un conjunto de elementos XML que puede usar en su documentacin. El resto de esta seccin examina cada uno de estos elementos. Recuerde que debe emplear XML vlido en sus comentarios, lo que significa que cada elemento debe contener su elemento final correspondiente en alguna parte de sus comentarios. NOTA: El trmino etiqueta se refiere a cualquier elemento descriptivo contenido en el XML. Las etiquetas siempre estn entre los smbolos < y >.
<c>
Puede usar la etiqueta <c> para indicar que una pequea parte del comentario debe ser tratada como cdigo. Las hojas de estilo pueden usar este elemento para mostrar la porcin de cdigo del comentario con una fuente de tamao fijo, como Courier:
/// sta es la funcin <c>Main()</c> para la /// clase HelloWorld.
67
<code>
Puede usar la etiqueta <code> para indicar que varias lneas de texto de sus comentarios deben ser tratadas como cdigo:
/// /// /// /// /// /// /// /// Llamar a esta aplicacin con tres argumentos har que la matriz de cadenas suministrada a Main() contenga tres elementos: <code> Argument[0]: command line argument 1 Argument[1]: command line argument 2 Argument[2]: command line argument 3 </code>
<example>
Puede usar la etiqueta < e x a m p l e > para indicar un ejemplo de cmo usar las clases que desarrolle a otros programadores. Los ejemplos suelen incluir una muestra de cdigo y quizs quiera usar las etiquetas < e x a m p l e > y <code> juntas:
/// /// /// /// /// /// <example>Aqu tiene un ejemplo de un cliente llamando a este cdigo: <code> ponga aqu su cdigo de ejemplo </code> </example>
<exception>
Puede usar la etiqueta < e x c e p t i o n > para documentar cualquier excepcin que pueda surgir en el cdigo del miembro. La etiqueta < e x c e p t i o n > debe contener un atributo llamado cref cuyo valor especifica el tipo de excepcin que se documenta. El valor del atributo cref debe estar entre comillas. El texto del elemento describe las condiciones en las que aparece la excepcin:
/// <exception cref="System.Exception"> /// Aparece si el valor introducido es menor de 0. /// </exception>
El compilador de C# asegura que el valor del atributo cref sea un tipo de datos vlido. Si no lo es, el compilador genera un mensaje de aviso. A continuacin se indica cmo documentar una funcin M a i n ( ) :
/// <exception cref="junk">probando</exception>
En este caso, el compilador de C# todava escribe la etiqueta <exception> en el archivo XML, pero pone un signo de exclamacin antes del atributo cref :
68
<list>
Puede usar la etiqueta < l i s t > para describir una lista de elementos en la documentacin. Puede describir una lista no numerada, una lista numerada o una tabla. La etiqueta < l i s t > usa un atributo llamado type para 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 <list>
Significado La lista es una lista no numerada. La lista es una lista numerada. La lista es una tabla.
Los estilos b u l l e t y number deberan tambin incluir una o ms etiquetas <item> dentro de la etiqueta < l i s t > . Cada etiqueta <item> se corresponde a un elemento de la lista. Cada etiqueta <item> debera contener una etiqueta < d e s c r i p t i o n > , cuyo texto define el texto de la lista de elementos:
/// /// /// /// /// /// /// <list type="bullet"> <item> <description>ste es el elemento 1.</description> </item> <item> <description>ste es el elemento 2 . </description> </item>
El tipo de lista table tambin debe incluir una etiqueta < l i s t h e a d e r > . La etiqueta < l i s t h e a d e r > contiene una o ms etiquetas <term> que describen los encabezamientos de las tablas:
/// /// /// /// /// /// /// <list type="table"> <listheader> <term>Elemento de la tabla </term> </listheader> <item> <description>ste es el elemento 1.</description> </item>
69
<param>
Use la etiqueta <param> para documentar un parmetro para una funcin. La etiqueta <param> usa un atributo, name , cuyo valor identifica al parmetro que se est documentando. El texto de la etiqueta <param> proporciona una descripcin del parmetro:
/// <param name="Flag"> /// El valor debe ser 0 para desactivado o 1 para activado. /// </param>
El compilador de C# asegura que el valor del atributo name realmente especifique el nombre de un parmetro. Si no es as, el compilador emite dos avisos. Por ejemplo, un cdigo fuente como el siguiente:
/// <param name="junk">Esto es junk.</param>
El primer aviso dice que se encontr una etiqueta <param> con un atributo name cuyo valor no concuerda con ninguno de los parmetros de la funcin. El segundo aviso dice que a uno de los parmetros le falta una etiqueta <param>. La etiqueta <param> se coloca en el archivo XML de documentacin, incluso si el atributo name es incorrecto:
<member name="M:Class1.Main(System.String[])"> <param name="junk">Esto es junk.</param> </member>
<paramref>
Puede usar la etiqueta < p a r a m r e f > para hacer referencia a un parmetro desde una descripcin. La etiqueta puede no tener ningn texto; sin embargo, lleva un atributo llamado name . El valor del atributo name debe indicar el nombre del parmetro al que se hace referencia:
/// La matriz <paramref name="Arguments" /> contiene /// los parmetros especificados es la lnea de comandos.
70
<permission>
Use la etiqueta < p e r m i s s i o n > para documentar los permisos disponibles en una funcin o variable dadas. El acceso al cdigo y los datos de una clase puede significar el acceso a todo el cdigo o puede ser restringido a cierto subconjunto de cdigo. Puede usar la etiqueta < p e r m i s s i o n > para documentar la disponibilidad del cdigo y sus datos. La etiqueta < p e r m i s s i o n > hace uso de un atributo: cref . El valor del elemento cref debe designar la funcin o la variable cuyos permisos se estn documentando:
/// <permission name="Main()"> /// Todo el mundo puede acceder a Main(). /// </permission>
<remarks>
Use la etiqueta <remarks> para aadir informacin. El elemento <remarks> es estupendo para mostrar una vista general de un mtodo o una variable y su uso. La etiqueta < r e m a r k s > no tiene atributos y su texto contiene las observaciones:
/// /// /// /// /// <remarks> La funcin Main() es el punto de entrada a la aplicacin. El CLR llamar a Main() para iniciar la aplicacin una vez que sta se haya abierto. </remarks>
<returns>
Use la etiqueta < r e t u r n s > para describir un valor devuelto por una funcin. La etiqueta < r e t u r n s > no tiene atributos y su texto contiene la informacin del valor devuelto:
/// La funcin Main() devolver 0 si la aplicacin /// proces los datos correctamente y devolver 1 /// en caso contrario.
<see>
Use la etiqueta < s e e > para aadir una referencia a una funcin o variable que se encuentre en otra parte del archivo. El elemento < s e e > usa un atributo llamado cref cuyo valor especifica el nombre del mtodo o variable al que se hace referencia. La etiqueta < s e e > no debe contener texto:
/// <see cref="Class1.Main" />
El compilador de C# asegura que el valor del atributo cref realmente especifique el nombre de un mtodo o variable. Si no es as, el compilador emite un aviso. Por tanto, un cdigo fuente como el siguiente:
71
La etiqueta < s e e > est situada en el archivo XML de documentacin, incluso si el atributo cref es incorrecto:
<member name="M:Class1.Main(System.String[])"> <see cref="!:junk"/> </member>
<seealso>
Como < s e e > , puede usar la etiqueta < s e e a l s o > para aadir una referencia a una funcin o variable que est en otra parte del archivo. Puede que necesite generar documentacin que contenga una seccin de referencias < s e e > adems de una seccin de referencias See Also y el compilador de C# le permite hacer esa distincin al admitir las etiquetas < s e e > y < s e e a l s o > . La etiqueta < s e e a l s o > usa un atributo llamado cref cuyo valor especifica el nombre del mtodo o variable al que se hace referencia. La etiqueta < s e e a l s o > no debe contener texto:
/// <seealso cref="Class1.Main" />
El compilador de C# asegura que el valor del atributo cref realmente especifique el nombre de un mtodo o variable. Si no es as, el compilador emite un aviso. Por tanto, un cdigo fuente como el siguiente:
/// <seealso cref="junk" /> public static void Main(string [] strArguments) { }
La etiqueta < s e e a l s o > est situada en el archivo XML de documentacin, incluso si el atributo cref es incorrecto:
<member name="M:Class1.Main(System.String[])"> <seealso cref="!:junk"/> </member>
72
<summary>
Use la etiqueta <summary> para proporcionar una descripcin resumida de un fragmento de cdigo. Esta etiqueta no admite ningn atributo. Su texto debe describir la informacin resumida:
/// /// /// /// <summary> La funcin Main() es el punto de entrada de esta aplicacin. </summary>
La etiqueta <summary> es como la etiqueta < r e m a r k s > . Generalmente, debe usar la etiqueta <summary> para proporcionar informacin sobre un mtodo o variable y la etiqueta < r e m a r k s > para proporcionar informacin sobre el tipo del elemento.
<value>
Use la etiqueta < v a l u e > para describir una propiedad de la clase. La etiqueta < v a l u e > no tiene atributos. Su texto debe documentar la propiedad:
/// /// /// /// <value> La propiedad MyValue devuelve el nmero de registros ledos de la base de datos. </value>
Resumen
Este captulo muestra cmo crear aplicaciones C# con un simple editor de texto, como el Bloc de notas. Tambin examina varias alternativas a Visual Studio para escribir cdigo. Ha construido su primera aplicacin de C#. Las aplicaciones C#, independientemente de su tamao, deben contener una clase con una funcin llamada Main(). La funcin Main() es el punto de partida de su aplicacin de C#. Tambin aprendi a aadir comentarios al cdigo fuente en C#. Puede aadir comentarios al cdigo para ayudar a otros programadores a comprender cmo est estructurado el cdigo fuente. Tambin puede dar formato a los comentarios de tal modo que el compilador pueda convertir los comentarios en un documento XML; y aadiendo palabras clave especiales, puede hacer el documento XML muy rico e informativo.
73
El cdigo de C# suele trabajar con valores que no se conocen cuando se escribe el cdigo. Puede que necesite trabajar con un valor ledo de una base de datos en tiempo de ejecucin o quizs necesite almacenar el resultado de un clculo. Cuando necesite almacenar un valor en el tiempo de ejecucin, use una variable. Las variables son los sustitutos de los valores con los que trabaja en su cdigo.
75
NOTA: C# admite cdigo fuente escrito con caracteres Unicode. Si est escribiendo su cdigo fuente usando un conjunto de caracteres Unicode, puede usar cualquier carcter de entre las clases de caracteres Unicode Lu , Ll , Lt , Lm , Lo , Nl , Mn , Mc , Nd , Pc y Cf en su identificador. Consulte la seccin 4.5 de las especificaciones de Unicode si desea obtener ms informacin sobre las clases de caracteres Unicode. Tambin puede usar una palabra clave de C# como nombre de variable, pero slo si va precedida del carcter @. No obstante, no se trata de una prctica recomendable, ya que puede hacer que su cdigo resulte difcil de leer, pero es factible y el compilador de C# lo permite.
Tipo sbyte
Descripcin Las variables con tipo s b y t e pueden contener nmeros enteros de 8 bits con signo. La 's' de s b y t e 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 s b y t e es -128 y el mayor es 127. Las variables con tipo b y t e pueden contener nmeros enteros de 8 bits sin signo. A diferencia de las variables s b y t e , las variables b y t e no tienen signo y slo pueden contener nmeros positivos. El valor ms pequeo posible para una variable b y t e es 0; el valor ms alto es 255. Las variables con tipo s h o r t puede contener nmeros enteros de 16 bits con signo. El menor valor posible para una variable s h o r t es -32.768; el valor ms alto es 32.767. Las variables con tipo u s h o r t pueden contener nmeros enteros de 16 bits sin signo. La 'u' de u s h o r t signifi-
byte
short
ushort
76
Tipo
Descripcin ca sin signo. El menor valor posible para una variable u s h o r t es 0; el valor ms alto es 65.535.
int
Las variables con tipo i n t pueden contener nmeros enteros de 32 bits con signo. El menor valor posible para una variable i n t es -2.147.483.648; el valor ms alto es 2.147.483.647. Las variables con tipo u i n t pueden contener nmeros enteros de 32 bits sin signo. La 'u' en u i n t significa sin signo. El menor valor posible para una variable u i n t variable es 0; el valor ms alto posible es 4.294.967.295. Las variables con tipo l o n g pueden contener nmeros enteros de 64 bits con signo. El menor valor posible para una variable l o n g es 9.223.372.036.854.775.808; el valor ms alto es 9.223.372.036.854.775.807. Las variables con tipo u l o n g pueden contener nmeros enteros de 64 bits sin signo. La 'u' en u l o n g significa sin signo. El menor valor posible para una variable u l o n g es 0; el valor ms alto es 18.446.744.073.709.551.615. Las variables con tipo char pueden contener caracteres Unicode de 16 bits. El menor valor posible para una variable char es el carcter Unicode cuyo valor es 0; el valor ms alto posible es el carcter Unicode cuyo valor es 65.535. Las variables con tipo f l o a t pueden contener un valor de coma flotante de 32 bits con signo. El menor valor posible para una variable f l o a t es aproximadamente 1,5 por 10 elevado a 45; el valor ms alto es aproximadamente 3,4 por 10 elevado a 38. Las variables con tipo d o u b l e pueden contener un valor de coma flotante de 64 bits con signo. El menor valor posible para una variable d o u b l e es aproximadamente 5 por 10 elevado a 324; el valor ms alto es aproximadamente 1,7 por 10 elevado a 308. Las variables con tipo d e c i m a l pueden contener un valor de coma flotante de 128 bits con signo. Las variables de tipo d e c i m a l son buenas para clculos financieros. El menor valor posible para una variable decimal es aproximadamente 1 por 10 elevado a 28; el valor ms alto es aproximadamente 7,9 por 10 elevado a 28. Las variables con tipo b o o l pueden tener uno de los dos posibles valores: true o false. El uso del tipo b o o l es
uint
long
ulong
char
float
double
decimal
bool
77
Tipo
Descripcin una de las partes en las que C# se aparta de su legado C y C++. En ellos, el valor entero 0 era sinnimo de f a l s e y cualquier valor que no fuese cero era sinnimo de t r u e . Sin embargo, en C# los tipos no son sinnimos. No puede convertir una variable entera en su valor equivalente b o o l . Si quiere trabajar con una variable que necesita tener una condicin verdadera o falsa, use una variable b o o l y no una variable i n t .
78
NOTA: Los espacios en blanco se definen como cualquier nmero de espacios necesario para mejorar la legibilidad del cdigo. Debe declarar sus variables dentro de una clase o de una funcin. El siguiente cdigo es vlido:
class MyClass { int MyIntegerVariable; static void Main() { float AnotherVariable; System.Console.WriteLine("Hello!"); } }
NOTA: Puede declarar una variable donde desee, pero tenga en cuenta que si la declara en una funcin, como se muestra en la variable A n o t h e r V a r i a b l e del ejemplo anterior, slo el cdigo incluido en esa funcin podr trabajar con la variable. Si la declara dentro de la clase, como en la variable M y I n t e g e r V a r i a b l e (tambin en el ejemplo anterior), todo el cdigo de esa clase podr trabajar con esa variable. Si toma el cdigo del ejemplo y le aade otra funcin a la clase, el cdigo de esa nueva funcin podr trabajar con la variable M y I n t e g e r V a r i a b l e pero no podr trabajar con la variable A n o t h e r V a r i a b l e . Si esta nueva funcin intenta acceder a la variable A n o t h e r V a r i a b l e declarada en la funcin M a i n ( ) , obtendr el siguiente mensaje de error del Compilador de C#:
error CS0103: El nombre 'AnotherVariable' no existe en la clase o espacio de nombres 'MyClass'
79
Cul es el valor de M y V a r i a b l e cuando se ejecuta M a i n ( ) ? Su valor es desconocido porque el cdigo no asigna ningn valor a la variable. Los programadores de C# eran conscientes de los errores que podan aparecer como resultado de usar variables a las que no se las ha asignado explcitamente un valor. El compilador de C# busca condiciones como sta y genera un mensaje de error. Si la funcin M a i n ( ) del cdigo anterior hace referencia a la variable M y V a r i a b l e sin que se le haya asignado un valor, el compilador de C# muestra el siguiente mensaje de error:
error CS0165: Uso de la variable local no asignada 'MyVariable'
C# distingue entre variables asignadas y no asignadas. Las variables asignadas han recibido un valor en algn punto del cdigo y las variables no asignadas no han recibido ningn valor en el cdigo. En C# no se permite trabajar con variables no asignadas porque sus valores son desconocidos y emplear estas variables puede generar errores en su cdigo. En algunos 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 asigna un valor en el cdigo. Modifique el cdigo anterior cambiando la variable M y V a r i a b l e de una variable declarada en el nivel de funcin a una variable declarada en el nivel de clase:
class MyClass { static int MyVariable; static void Main() { // MyVariable recibe un valor por // defecto y puede ser usada aqu } }
Esta accin mueve la declaracin de la variable dentro de la variable de clase y la variable ahora es accesible para todo el cdigo incluido en la clase, no slo en la funcin M a i n ( ) . C# asigna valores por defecto a variables de nivel de clase y el compilador de C# le permite trabajar con la variable M y V a r i a b l e sin asignarle un valor inicial. La tabla 3.2 enumera los valores que se asignan por defecto a las variables de clase.
80
Tipo de variable
sbyte byte short ushort int uint long ulong char float double decimal bool
Valor por defecto 0 0 0 0 0 0 0 0 carcter Unicode con valor 0 0.0 0.0 0.0 false
Aprender otros medios de asignar valores a las variables en las secciones posteriores.
81
Esto requerir mucho tiempo y su cdigo ser difcil de leer y mantener con todas esas variables. Lo que necesita es un modo de decir, "Quiero tener una coleccin de 25 variables". Es decir, lo que queremos es una matriz. Una matriz es una coleccin de variables, cada una de las cules tiene el mismo tipo de variable. Las matrices tienen un tamao, que especifica cuntos elementos pueden contener. La declaracin de una matriz es algo as:
byte [] TestScoresForStudents;
La declaracin byte especifica que todos los elementos de la matriz son valores de tipo byte. Mediante los corchetes se indica al compilador de C# que quiere crear 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 declaracin es el tamao de la matriz. Cuntos elementos puede contener esta matriz? El tamao de la matriz se especifica mediante el operador de C# new . Este operador indica al compilador de C# que quiere reservar suficiente memoria para una nueva variable; en este caso, una matriz de 25 variables byte:
byte [] TestScoresForStudents; TestScoresForStudents = new byte[25];
La palabra clave byte indica al compilador que quiere crear una nueva matriz de variables byte, y [25] indica que quiere reservar suficiente espacio para 25 variables byte. Cada variable de la matriz se denomina elemento de la matriz, y la matriz que acaba de crear contiene 25 elementos. Debe acordarse de especificar el tipo de matriz cuando use la palabra clave new , aunque ya haya especificado el tipo de la matriz cuando la declar. Si olvida hacerlo, obtendr un mensaje de error del compilador. El cdigo:
82
Este error aparece porque el cdigo no tiene un tipo de variable entre la nueva palabra clave y el tamao de la matriz. Tambin debe recordar usar el mismo tipo que us cuando declar la matriz. Si usa un tipo diferente, obtendr otro mensaje de error, como demuestra el siguiente cdigo:
byte [] TestScoresForStudents; TestScoresForStudents = new [25];
El error se produce porque el tipo de la instruccin ( byte ) no concuerda con el tipo usado en la nueva instruccin ( long ). Las matrices como sta se llaman matrices unidimensionales. Las matrices unidimensionales tienen un factor que determina su tamao. En este caso, el nico factor que determina el tamao de la matriz es el nmero de estudiantes de la clase. El valor inicial de los elementos de la matriz queda determinado por los valores por defecto del tipo de matriz. Cada elemento de la matriz se inicializa con un valor por defecto de acuerdo con la tabla 3.2. Como esta matriz contiene elementos de tipo byte, cada elemento de la matriz tiene un valor por defecto de 0.
FirstTestScore = TestScoresForStudents[0];
83
Este cdigo accede al primer elemento de la matriz T e s t S c o resForStudents y asigna su valor a la primera variable FirstTestScore.
Para poner un valor en la matriz, simplemente acceda al elemento usando la misma sintaxis, pero coloque el nombre de la matriz y el nmero del elemento a la izquierda del signo igual:
TestScoresForStudents[9] = 100;
Este cdigo almacena el valor 100 en el dcimo elemento de la matriz 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 nmeros de los elementos posibles van de 0 a 24, inclusive. Si usa un nmero de elemento inferior a 0 o mayor que 24, obtendr un mensaje de tiempo de ejecucin, como se muestra en el siguiente cdigo:
TestScoresForStudents[1000] = 123;
Este cdigo se compila sin errores, pero al ejecutar la aplicacin se produce un error porque no hay un elemento 1000 en su matriz de 25 elementos. Cuando se llega a esta instruccin, el Entorno de ejecucin comn (CLR) detiene el programa e inicia un mensaje de excepcin:
Exception occurred: System.IndexOutOfRangeException: An exception of type System.IndexOutOfRangeException was thrown.
IndexOutOfRangeException indica que la aplicacin intent acceder a un elemento con un nmero de elemento que no tiene sentido para la matriz.
Si al escribir el cdigo ya conoce los valores con los que quiere inicializar la matriz, puede especificar los valores en una lista separada por comas y encerrada entre llaves. La lista se coloca en la misma lnea que la declaracin de matriz. Puede poner todo el cdigo anterior en una sola lnea escribiendo lo siguiente:
int [] MyArray = { 0, 1, 2, 3, 4 };
84
Usando esta sintaxis, no especifica el nuevo operador o el tamao de la matriz. El compilador de C# examina su lista de valores y calcula el tamao de la matriz.
Pero esto podra resultar confuso. Cmo se usa esta matriz? Aparecen primero las puntuaciones de un solo estudiante o colocamos en primer lugar las calificaciones del primer estudiante? Un modo mejor de declarar una matriz consiste en especificar cada dimensin por separado. Declarar una matriz multidimensional es tan sencillo como colocar comas entre los corchetes. Coloque una coma menos que el nmero de dimensiones necesarias para su matriz multidimensional, como en la siguiente declaracin:
byte [ , ] TestScoresForStudents;
Esta declaracin define una matriz multidimensional de dos dimensiones. Usar el operador new para crear una nueva matriz de este tipo es tan sencillo como especificar las dimensiones individualmente, separadas por comas, en los corchetes, como se muestra en el siguiente cdigo:
byte [ , ] TestScoresForStudents;
Este cdigo indica al compilador de C# que quiere crear una matriz con una dimensin de 10 y otra dimensin de 25. Puede imaginar una matriz de dos di-
85
mensiones como una hoja de clculo de Microsoft Excel con 10 filas y 25 columnas. La tabla 3.3 recoge el aspecto que podra tener esta matriz si sus datos estuvieran en una tabla.
Tabla 3.3. Representacin en una tabla de una matriz de dos dimensiones
Para acceder a los elementos de una matriz de dos dimensiones, use las mismas reglas para numerar elementos que en las matrices unidimensionales (los nmeros de elemento van de 0 a uno menos que la dimensin de la matriz). Tambin se usa la misma sintaxis de coma que us con el operador new. Escribir cdigo para almacenar una calificacin de 75 para el primer examen del alumno 25 sera algo as:
TestScoresForStudents[0, 24] = 75;
Leer la calificacin del quinto examen del alumno 16 sera algo as:
byte FifthScoreForStudent16; 15];
FifthScoreForStudent16 = TestScoresForStudents[4,
En otras palabras, cuando trabaje con una matriz de dos dimensiones y considere la matriz como una tabla, considere la primera dimensin como el nmero de fila de la tabla y el segundo nmero como el nmero de columna. Puede inicializar los elementos de una matriz multidimensional cuando declare la variable de matriz. Para ello, coloque cada conjunto de valores para una sola dimensin en una lista delimitada por comas rodeada por llaves:
int [ , ] MyArray = {{0, 1, 2}, {3, 4, 5}};
Esta instruccin declara una matriz de dos dimensiones con dos filas y tres columnas. Los valores enteros 0, 1 y 2 estn en la primera fila, y los valores 3, 4 y 5 estn en la segunda fila. Las matrices de dos dimensiones con esta estructura se llaman matrices rectangulares. Estas matrices tienen la forma de una tabla; cada fila de la tabla tiene el mismo nmero de columnas. C# permite definir matrices con ms de dos dimensiones. Para ello basta con utilizar ms comas en la declaracin de la matriz.
86
Puede definir una matriz de cuatro dimensiones con tipo l o n g , por ejemplo, con la siguiente definicin:
long [,,,] ArrayWithFourDimensions;
El acceso a los elementos de la matriz se realiza de la misma manera. No olvide especificar todos los elementos de la matriz:
ArrayWithFourDimensions[0, 0, 0, 0] = 32768436;
Estudiante 25
Calificacin Calificacin Calificacin Calificacin Calificacin Calificacin Calificacin Calificacin Calificacin Calificacin 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 nmero diferente de elementos.
Estas matrices escalonadas tienen dos dimensiones, como las matrices rectangulares, pero cada fila puede tener un nmero de elementos diferente (lo que da a las matrices su aspecto escalonado).
87
Las matrices escalonadas se definen utilizando dos grupos de corchetes inmediatamente despus del nombre del tipo de matriz. Cuando hace una llamada a new , se especifica un tamao para la primera dimensin (la matriz student en nuestro ejemplo), pero no la segunda. Tras definir la primera matriz, haga una nueva llamada a new para definir las otras matrices (las matrices score en nuestro ejemplo):
byte [][] ArrayOfTestScores; ArraysOfTestScores = new byte [25][]; ArraysOfTestScores[0] = new byte[3]; ArraysOfTestScores[1] = new byte[5]; ArraysOfTestScores[2] = new byte[2]; ArraysOfTestScores[24] = new byte[10];
Una vez que ha construido la matriz escalonada, puede acceder a sus elementos de la misma forma que en una matriz rectangular.
Por qu son tan diferentes las matrices? Por qu se necesita utilizar new al crear una matriz? La respuesta est en la diferencia entre tipos de valor y tipos de referencia. Con un tipo de valor, la variable retiene el valor de la variable. Con un tipo de referencia, la variable retiene una referencia al valor almacenado en algn otro sitio de la memoria. Puede imaginar una referencia como una variable que apunta hacia otra parte de memoria. La figura 3.2 muestra la diferencia.
int IntegerVariable 123 long LongVarable 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 algn otro lugar de la memoria.
88
Cada uno de los tipos comentados hasta este punto es un tipo de valor. Las variables proporcionan suficiente capacidad de almacenamiento para los valores que pueden contener y no necesita new para crear un espacio para sus variables. Las matrices son tipos de valor y los objetos son tipos de referencia. Sus valores estn en algn otro lugar de la memoria y no necesita usar la palabra clave new para crear suficiente espacio para sus datos. Aunque necesita usar la palabra clave new para crear espacio de memoria para un tipo de referencia, no necesita escribir ningn cdigo que borre la memoria cuando haya acabado de usar la variable. El CLR contiene un mecanismo llamado recolector de elementos no utilizados que realiza la tarea de liberar la memoria que no se usa. El CLR ejecuta el recolector de elementos no utilizados mientras se ejecuta su aplicacin de C#. El recolector de elementos no utilizados registra su programa buscando memoria no usada por ninguna de sus variables. Liberar la memoria que ya no se usa es tarea del recolector de elementos no utilizados.
Conversiones implcitas
El compilador de C# realiza automticamente las conversiones implcitas. Examine el siguiente cdigo:
int IntegerVariable; long LongVariable; IntegerVariable = 123; LongVariable = IntegerVariable;
En este cdigo, a una variable de tipo entero se le asigna el valor 123 y a una variable l o n g se le asigna el valor de la variable de tipo entero. Cuando se ejecute este cdigo, el valor de L o n g V a r i a b l e es 123. El compilador de C# convierte el valor de la variable de tipo entero a un valor l o n g porque la conversin de un valor i n t a un valor l o n g es una de las conversiones implcitas permitidas por C#. La tabla 3.4 recoge las conversiones
89
implcitas permitidas por C#. La primera columna enumera el tipo original de la variable y la fila superior enumera los tipos de datos a los que puede convertirlo. Una X en la celdilla significa que puede convertir implcitamente el tipo de la izquierda al tipo en la parte superior.
Tabla 3.4. Conversiones implcitas de tipo de valor
.... sbyte byte short ushort int uint long char float ulong
long
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
NOTA: No puede convertir ningn tipo a un tipo char (excepto mediante la variable char, lo que en realidad no es una conversin). Adems, no puede hacer conversiones entre los tipos floating-point y los tipos decimales.
Conversiones explcitas
Si escribe cdigo que intente convertir un valor que use tipos no admitidos por una conversin implcita, el compilador de C# genera un error, como muestra el siguiente cdigo:
char CharacterVariable; int IntegerVariable; IntegerVariable = 9; CharacterVariable = IntegerVariable;
90
Este error se produce porque ninguna conversin implcita admite la conversin de una variable int a una variable char. Si realmente necesita hacer esta conversin, tiene que realizar una conversin explcita. Las conversiones explcitas se escriben en su cdigo fuente y le dicen al compilador "haz que se produzca esta conversin, aunque no pueda ser realizada implcitamente". Escribir una conversin explcita en el cdigo C# requiere colocar el tipo al que est convirtiendo entre parntesis. Los parntesis se colocan justo antes de la variable que est usando como fuente de la conversin. A continuacin se incluye el cdigo anterior pero usando una conversin explcita:
char CharacterVariable; int IntegerVariable; IntegerVariable = 9; CharacterVariable = (char)IntegerVariable;
Esta tcnica es conocida como conversin de la variable de tipo entero a una variable de tipo carcter. Algunos tipos no pueden ser convertidos, ni siquiera mediante una operacin de conversin explcita. La tabla 3.5 enumera las conversiones explcitas que admite C#. La primera columna enumera el tipo original de la variable y la fila superior enumera los tipos de datos a los que puede convertirlo. Una X en la celdilla significa que puede convertir explcitamente el tipo de la izquierda al tipo de la parte superior usando la operacin de conversin explcita.
Tabla 3.5. Conversiones explcitas de tipo de valor
.... sbyte byte short ushort int uint long char float ulong decimal double
sbyte byte short ushort int uint long char float ulong decimal double
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
Tambin puede realizar conversiones explcitas sobre tipos de valor convirtiendo explcitamente el valor al tipo apropiado, como se muestra en el siguiente
91
ejemplo. C# permite usar un operador de conversin explcita incluso con conversiones implcitas, si lo desea:
int IntegerVariable; long LongVariable; IntegerVariable = 123; LongVariable = (long)IntegerVariable;
Esta sintaxis no es necesaria, porque C# permite conversiones implcitas de variables int a variables long, pero puede escribirlo si quiere.
Al igual que con el resto de las variables, puede inicializar una cadena en la misma lnea que su declaracin:
string MyString = "Hello from C#!";
Caracteres
\t
Funcin
Los caracteres especiales \t incrustan una tabulacin en la cadena. Una cadena definida como hello\tthere
92
Caracteres
Funcin
se almacena en memoria con un carcter de tabulacin entre las palabras hello 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 hello y there. El carcter retorno de carro devuelve el cursor al principio de la lnea, pero no mueve el cursor una lnea por debajo. Los caracteres especiales \v insertan una tabulacin vertical en la cadena. Una cadena definida como hello\ vthere se almacena en memoria con un carcter de tabulacin vertical entre las palabras hello y there. Los caracteres especiales \f insertan un carcter de impresin de pgina en la cadena. Una cadena definida como hello\fthere se almacena en memoria con un carcter de impresin de pgina entre las palabras hello y there. Las impresoras suelen interpretar un carcter de impresin de pgina como una seal para pasar a una nueva pgina. Los caracteres especiales \n insertan una nueva lnea en la cadena. Una cadena definida como hello\nthere se almacena en memoria con un carcter de nueva lnea entre las palabras hello y there. La comunidad de desarrolladores de software ha debatido durante mucho tiempo la interpretacin del carcter de nueva lnea. Siempre ha significado, "mueve la posicin de la siguiente salida una lnea ms abajo". La duda est en si la operacin tambin incluye mover la siguiente posicin al primer carcter de la lnea anterior. .NET Framework interpreta el carcter de nueva lnea como bajarlo una lnea y devolver la posicin del siguiente carcter al principio de la siguiente lnea. Si no est seguro, siempre puede escribir los caracteres especiales \n y \r juntos. Los caracteres especiales \x permiten especificar un carcter ASCII usando dos dgitos hexadecimales. Los dos dgitos hexadecimales deben seguir inmediatamente a los caracteres \x y deben corresponder con el valor hexadecimal del carcter ASCII que quiere producir. Por ejemplo, el carcter espacio en ASCII tiene el cdigo de decimal 3 2 . El valor decimal 32 es equivalente al valor hexadecimal 20. Por tanto, una cadena definida como hello\x20there se almacena en memoria con un carcter de espacio entre las palabras hello y there.
\v
\f
\n
\x
93
Caracteres \u
Funcin Los caracteres especiales \u permiten especificar un carcter Unicode usando exactamente cuatro dgitos hexadecimales. Los cuatro dgitos hexadecimales deben colocarse inmediatamente despus de los caracteres \u y deben corresponder al valor hexadecimal del carcter Unicode que quiere producir. Por ejemplo, el carcter espacio en Unicode tiene un cdigo de decimal 32. El valor decimal 32 es equivalente al valor hexadecimal 20. Por tanto, una cadena definida como hello\u0020there se almacena en memoria con un carcter de espacio entre las palabras hello y there. Asegrese de usar exactamente cuatro dgitos despus de los caracteres \u. Si el valor es menor de cuatro dgitos, use ceros para rellenar su valor hasta que llegue a los cuatro dgitos. Los caracteres especiales \\ permiten especificar un carcter de barra invertida en la posicin actual. Una cadena definida como hello\\there se almacena en memoria con un carcter barra invertida entre las palabras helio y there. La razn por la que debe haber dos barras invertidas es simple: el uso de una sola barra invertida podra hacer que el compilador de C# la confundiera con el principio de otro carcter especial. Por ejemplo, suponga que olvida la segunda barra invertida y escribe hello\there en su cdigo. El compilador de C# ver la barra invertida y la 't' en la palabra there y los confundir con un carcter de tabulacin. Esta cadena se almacenara en memoria con un carcter de tabulacin entre las palabras hello y there (Recuerde que la 't' en there se interpretara como un carcter de tabulacin y no sera parte de la palabra real).
\\
Este cdigo asigna a la variable MyString el valor del texto hello\there . Como la cadena tiene delante el signo @, se desactiva el modo habitual de interpretar los caracteres \t como marcador de tabulacin. Esta sintaxis tambin permite escribir nombres de directorio en cadenas de nombres de archivo de C# sin
94
usar la doble barra invertida. Por defecto, siempre necesitar usar las dobles barras invertidas:
string MyFilename = "C:\\Folder1\\Folder2\\Folder3\\flie.txt";
Sin embargo, con el prefijo @ puede conseguirlo con una sola barra invertida:
string MyFilename = @"c:\Folderl\Folder2\Folder3\file.txt";
Este cdigo coloca el valor 'm' en la variable M y C h a r a c t e r . El carcter 'm' est en el elemento 9 de la cadena, si imagina la cadena como una matriz de caracteres. Adems, tenga en cuenta que esta matriz de caracteres es de base cero. El primer carcter de la cadena se encuentra en realidad en el elemento 0. El dcimo carcter de esta cadena, como ha aprendido, est localizado en el elemento 9.
Declaracin de enumeraciones
A diferencia de las variables tratadas hasta el momento, una enumeracin no es un tipo en s misma, sino una forma especial de tipo de valor. Una enumeracin se deriva de System.Enum y proporciona nombres para valores. El tipo subyacente que representa una enumeracin debe ser b y t e , s h o r t , i n t o l o n g . Cada campo de una enumeracin es esttico y representa una constante. Para declarar una enumeracin, debe usar la palabra clave enum seguida del nombre de la enumeracin. A continuacin debe escribir una llave de apertura seguida por una lista de las cadenas de la enumeracin y finalizar con una llave de cierre, como se muestra en el siguiente ejemplo:
public enum Pizza { Supreme, MeatLovers, CheeseLovers,
95
Vegetable, }
Este cdigo crea una enumeracin llamada P i z z a . La enumeracin pizza contiene cuatro pares diferentes nombre/valor que describen diferentes tipos de pizza, pero no se definen valores. Cuando declara una enumeracin, el primer nombre que declara toma el valor 1 y as sucesivamente. Puede invalidar esta funcionalidad asignando un valor a cada nombre, como se muestra a continuacin:
public enum Pizza { Supreme = 2, MeatLovers = 3, CheeseLovers = 4, Vegetable = 5 }
El valor de cada campo de enumeracin ha sido incrementado en 1. Aunque no todo este cdigo es necesario. Asignando a Supreme un valor de 2, los siguientes campos 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 programar sobre sus nombres de campo o puede programar sobre sus valores. Por ejemplo, puede asignar el nombre de campo a una variable de cadena con el siguiente cdigo:
string MyString = Pizza.Supreme;
Quizs quiera hacer referencia al valor de un campo. Puede conseguirlo mediante la conversin de tipos. Por ejemplo, puede recuperar el valor del campo Supreme con el siguiente cdigo:
int MyInteger = (int)Pizza.Supreme;
Resumen
Este captulo examina las variables y sus tipos. Hay muchas clases de tipos de valor y cada uno tiene sus propias caractersticas y requisitos de memoria. Algunos tipos pueden ser convertidos implcitamente a otros tipos, pero otros deben ser convertidos explcitamente usando la sintaxis apropiada. Las matrices contienen colecciones de variables del mismo tipo. Son tiles cuando se necesita mantener un conjunto de variables del mismo tipo. C# admite matrices unidimensionales y multidimensionales. Las matrices de C# son de base cero: es decir, el nmero del primer elemento de una matriz es el 0. Las cadenas le ayudan a trabajar con partes de texto en su cdigo. Son conjuntos de caracteres
96
Unicode. C# permite incrustar caracteres especiales en sus cadenas, pero proporciona el prefijo @ para 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
4 Expresiones
Las expresiones son el elemento bsico y fundamental de cualquier lenguaje de programacin. Mediante el uso de operadores, las expresiones permiten que una operacin realice comparaciones simples, asignaciones e incluso operaciones muy complejas que necesitaran millones de aos para completarse. Este captulo trata del uso de los operadores para realizar funciones matemticas, asignar valores a variables y realizar comparaciones. Una vez que hayamos comprendido estos elementos bsicos, estudiaremos algunas expresiones avanzadas que usan operadores muy especficos del lenguaje C# y que le brindan una ventaja sobre la mayora de los dems lenguajes de programacin. Para cerrar este captulo, revisaremos las expresiones que usan operadores para manipular la parte ms pequeas de un byte, el bit.
99
llaman operandos. Los operadores se aplican a los operandos y el resultado de la operacin es otro valor. C# consta de tres tipos de operadores: Operadores unarios, trabajan con un solo operando. Una expresin con un operando y un operador produce un solo valor. Operadores binarios, trabajan con dos operandos. Una expresin con dos operandos y un operador produce un solo valor. Operadores ternarios, trabajan con tres operandos. C# admite slo un operando ternario.
Literales Identificadores Expresiones con parntesis Acceso a miembros Expresiones de invocacin Acceso a elementos La palabra clave this Acceso a base Operadores de incremento y decremento El operador new El operador typeof
Los operadores checked y unchecked
Las expresiones primarias permiten definir el orden de las operaciones dentro de una expresin, definir nuevos literales (por ejemplo, valores codificados especficamente) y declarar nuevas variables para la aplicacin. En las siguientes secciones se examinarn estas expresiones y cmo usarlas.
100
Para mostrar lo que es un literal, examine la siguiente lnea de cdigo en C# que usa el valor literal Brian .
if (FirstName == "Brian")
Aqu se ha codificado el valor de Brian para usarlo en una comparacin. En lugar de un valor definido por el usuario, es preferible almacenar las cadenas en variables de modo que, si hiciera falta cambiar los valores, slo habra que cambiarlos en un sitio sin necesidad de buscar cada una de sus ocurrencias en todas las lneas de cdigo. El siguiente cdigo muestra el mejor mtodo para almacenar y usar una cadena con vistas a compararla:
string MyFirstName = "Brian"; if (FirstName == MyFirstName)
Como puede ver, ste es un enfoque mucho ms claro para usar un valor literal.
Literales booleanos
C# define dos valores literales booleanos, las palabras clave True y False :
bool MyTrueVariable = true; bool MyFalseVariable = false;
Ambos valores tienen un tipo de valor bool . La palabra clave T r u e es el equivalente entero de uno negativo (-1), mientras que el equivalente de F a l s e es el cero.
Los literales decimales tambin pueden contener un sufijo de un carcter que especifique el tipo del literal. Si el literal tiene como sufijo una U mayscula o minscula, el literal decimal se considera de tipo sin signo:
uint MyVariable = 125U;
101
El trmino sin signo significa que no se especifica si el nmero es positivo o negativo. Por tanto, si convierte un valor de 100 negativo (-100) a un valor sin signo, su resultado sera simplemente cien (100). Si el valor es lo suficientemente pequeo como para poder ser almacenado en un tipo u i n t , el compilador de C# considerar el literal como de tipo u i n t . Si el valor del literal entero es demasiado grande para un tipo u i n t , el compilador de C# considerar el literal como de tipo u l o n g . Los diferentes tipos representan el tamao de la informacin que est almacenando. El tipo u i n t puede contener un nmero comprendido entre 0 y 4.294.967.295; mientras que el valor u l o n g puede contener un valor entre 0 y 8.446.744.073.709.551.615. Si el literal tiene como sufijo una L mayscula o minscula, el literal decimal se considera de tipo l o n g :
long MyVariable = 125L;
Si el valor est dentro del rango de tipo l o n g , el compilador de C# considerar el literal como de tipo l o n g . Si el valor no est dentro del rango de tipo l o n g , el compilador de C# considerar el literal como de tipo u l o n g . NOTA: Aunque el compilador de C# acepta tanto la l minscula como la L mayscula como sufijos, probablemente prefiera usar la L mayscula. La l minscula se parece demasiado al nmero 1 y si otros programadores leen el cdigo, podran confundir la l con el 1. Si el literal tiene como sufijos L y U, el literal decimal se considera de tipo l o n g sin signo:
ulong MyVariable = 125LU;
El compilador de C# acepta 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. Adems, el compilador de C# acepta la combinacin de letras en maysculas y en minsculas. Los sufijos LU, Lu, lU, l u , UL, Ul, uL y ul denominan al sufijo u l o n g . Escribir literales enteros en formato hexadecimal permite escribir un literal usando las letras de la A a la F junto a los nmeros del 0 al 9. Los literales hexadecimales deben tener el prefijo 0X o 0x:
int MyVariable = 0x7D; // 7D hex = 125 decimal
Se pueden usar letras maysculas y minsculas para la notacin hexadecimal. Tambin se pueden usar como sufijos los mismos caracteres que estaban disponibles para los literales decimales:
long MyVariable = 0x7DL;
La decisin de usar un valor hexadecimal queda completamente a discrecin del programador. Usar hexadecimales en lugar de otro tipo de literales no supone
102
ninguna diferencia respecto a usar cualquier otro tipo de nmero. No obstante, es aconsejable usar valores hexadecimales cuando se est construyendo una aplicacin que utilice especificaciones en formato hexadecimal. Por ejemplo, una interfaz para el mdem de su ordenador. La referencia del programador para su mdem podra especificar los valores de algunas operaciones en formato hexadecimal. En lugar de leer toda la referencia del programador y convertir todos los nmeros al sistema decimal, normalmente slo tendra que codificar estos nmeros hexadecimales directamente en la aplicacin, evitando as cualquier error de conversin.
Si el literal real tiene como sufijo una D mayscula o minscula, se considera que el literal decimal es de tipo d o u b l e :
double MyVariable = 7.5D;
Si el literal real tiene como sufijo una M mayscula o minscula, se considera que el literal decimal es de tipo d e c i m a l :
decimal MyVariable = 7.5M;
103
Tambin se pueden usar las secuencias de escape vistas en un captulo anterior para escribir literales de carcter en cdigo C#. Estos literales de carcter tambin deben encerrarse entre apstrofos:
char MyVariable = '\t'; // carcter tabulador
NOTA: Si quiere utilizar una comilla simple como literal de carcter, deber anteponerle una barra invertida. Escribir ' confunde al compilador de C#. Escriba en su lugar \' . Puede definir valores hexadecimales como literales de carcter usando la secuencia de escape \x seguida de uno, dos o tres caracteres hexadecimales:
char MyVariable = '\x5C';
El compilador de C# reutiliza muchos literales de cadena con los mismos contenidos, con lo que conserva espacio en el ejecutable final, como muestra el siguiente cdigo:
string String1 = "Hello"; string String2 = "Hello";
Cuando se compila este cdigo, el ejecutable contiene una copia del literal de la cadena Hello . Las dos variables de cadena leen el valor de la nica copia almacenada en el ejecutable. Esta optimizacin permite al compilador de C# conservar el uso de memoria del cdigo, ya que almacenar slo una copia del literal requiere menos memoria que almacenar dos copias del mismo literal.
104
Uso de identificadores
Los identificadores C# son ejemplos de expresiones simples. Los identificadores tienen un tipo y el tipo se especifica cuando se declara el identificador:
int MyVariable = 123;
El identificador MyVariable se considera una expresin y tiene un tipo i n t . Los identificadores pueden ser definidos en cualquier bloque de cdigo que se encuentre entre llaves, pero su tipo no puede cambiar:
public static void Main() { int MyVariable = 123; MyVariable = 1; // "MyVariable" todava es un "int" MyVariable = 2; // "MyVariable" todava es un "int" }
Si intenta redefinir el tipo de un identificador dentro del mismo bloque de cdigo, el compilador de C# generar un mensaje de error:
public static void Main() { int MyVariable = 123; float MyVariable = 1.25; }
El compilador de C# genera un mensaje de error en la lnea que intenta redefinir MyVariable como un valor float:
error CS0128: Ya se ha definido una variable local denominada 'MyVariable' en este mbito
105
parntesis y el valor de la expresin entre parntesis es el resultado de la evaluacin. Por ejemplo, el valor de la expresin entre parntesis (3+2) es 5.
Los objetos se estudian con ms detalle en captulos posteriores. Lo ms importante ahora es darse cuenta de que la instruccin que llama a M a i n ( ) contiene una expresin de acceso a miembros, que contiene un objeto, un punto y una llamada de funcin. En captulos posteriores ver qu objetos pueden tener datos adems de cdigo. Puede acceder a los datos usando la misma sintaxis de expresin de acceso a miembros.
106
En este ejemplo, el mtodo M a i n ( ) llama a un mtodo DoWork(). Sin embargo, antes hace falta crear una referencia a myClass y luego invocar al mtodo D o W o r k ( ) . El tipo de una expresin de invocacin es el tipo que devuelve la funcin a la que se llama. Si, por ejemplo, el cdigo C# llama a una funcin que devuelve un tipo i n t , la expresin de invocacin que llama a ese mtodo tiene un tipo i n t .
En este ejemplo, al elemento cero de la matriz llamada MyArray se le asigna el valor de 123. C# permite que cualquier expresin que produzca un resultado de tipo i n t , uint, long o ulong se utilice como ndice de acceso. C# tambin permite el uso de cualquier expresin cuyo resultado sea de un tipo que pueda ser convertido implcitamente en tipo i n t , u i n t , l o n g o u l o n g . En el cdigo anterior, se usa un literal entero como ndice de acceso. Podra igualmente escribir un tipo de expresin diferente para especificar el ndice, como muestra el listado 4.2.
Listado 4.2. Acceso a elementos class MyClass { public static void Main() { int [] MyArray; MyClass myclass = new MyClass(); MyArray = new int [5]; MyArray[myclass.GetArrayIndex()] = 123;
107
Este cdigo funciona porque el mtodo G e t A r r a y I n d e x ( ) devuelve un i n t y el resultado de la expresin de invocacin es un i n t . Como cualquier expresin cuyo valor sea i n t puede ser usada como expresin de acceso de una matriz, C# permite que este cdigo se ejecute. El resultado de la expresin de acceso al elemento es el tipo del elemento al que se accede, como se muestra en el siguiente cdigo:
int [] MyArray; MyArray = new int [5]; MyArray[0] = 123;
La expresin 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 expresin es de tipo i n t .
108
void DoWork2() { } }
En este ejemplo, la expresin de acceso this tiene el tipo MyClass porque la clase MyClass contiene el cdigo que incluye la expresin de acceso this.
El tipo de una expresin que usa operadores postfijos de incremento y de decremento concuerda con el tipo cuyo valor se est incrementando o reduciendo. En el listado 4.4. los operadores de incremento y de decremento tienen tipo i n t .
109
La palabra clave typeof devuelve un objeto llamado System.Type que describe el tipo de la variable. El tipo de la expresin typeof es la clase
System.Type .
110
A cada variable entera I n t 1 y I n t 2 se le asigna el valor de dos mil millones. Esta operacin no supone ningn problema porque las variables enteras pueden almacenar valores por encima de dos mil cien millones. Sin embargo, sumar estos dos enteros y almacenar el resultado en otro entero va a suponer un problema. La suma sera cuatro mil millones, lo que supera el valor lmite de un entero, poco ms de dos mil cien millones. Compile el cdigo anterior con la lnea de comando habitual:
csc Listing4-1.cs
Cuando ejecute el archivo Listing4-1.exe, obtendr un gran nmero negativo, como se ve en la figura 4.1.
c:\ C:\WINDOWS\System32\cmd.exe C:\>Listing4-5.exe -294967296 C:\>
Se obtiene un resultado negativo debido al modo que tiene C# de procesar los valores demasiado grandes para encajar en las variables destinadas a ellos. C# no puede representar todo el valor de un entero, as que toma el valor propuesto, cuatro mil millones, y le resta el valor mximo de un valor de 32 bits (4.294.967.296), mostrando el resultado en la consola.
111
Obviamente, el cdigo ha generado un resultado distinto del que queramos. Si no se da cuenta de este tipo de errores matemticos, su cdigo puede comportarse de forma impredecible. Para insertar una medida de seguridad en cdigos como ste, puede usar el operador checked , como aparece en el listado 4.6.
Listado 4.6. Verificacin de los desbordamientos de las operaciones matemticas class Listing4_6 { public static void Main() { int Int1; int Int2; int Int1PlusInt2; Int1 = 2000000000; Int2 = 2000000000; Int1PlusInt2 = checked(Int1 + Int2); System.Console.WriteLine(Int1PlusInt2); } }
En lugar de escribir un valor matemtico sin sentido en la consola, un mensaje de desbordamiento permite saber que se intent comprobar la validez del valor de la suma y que la prueba no fue superada. Se informa de una excepcin y la aplicacin concluye. La expresin unchecked() es la que se usa por defecto. En las expresiones que tienen unchecked() no se comprueba la validez de los valores y la aplicacin sigue ejecutndose usando los valores no verificados, aunque no tengan sentido. El comportamiento por defecto es el de no verificar las operaciones. Sin embargo, si quiere que se compruebe si todos los valores de sus operaciones son vlidos sin usar el operador checked() en el cdigo, puede usar la opcin / checked+ del compilador. Compile el listado 4.1 con la siguiente lnea de comando:
csc /checked+ Listing4-1.cs
Cuando se ejecuta el ejecutable de listing 4-1, se obtiene el mismo mensaje de excepcin que se obtuvo con listing 4-2, porque la opcin /checked+ obliga a comprobar la validez de los valores de todas las operaciones matemticas.
112
113
C#. El operador se coloca antes de la expresin booleana que quiera negar, como se ilustra en el listado 4.7.
Listado 4.7. Operador de negacin lgica class MyClass { public static void Main() { bool MyBoolean; MyBoolean = true; MyBoolean = !MyBoolean; // "MyBoolean" ahora es false } }
114
El tipo de una expresin que usa los operadores prefijo de incremento y decremento concuerda con el tipo cuyo valor se incrementa o reduce. Tenga 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 cambia despus de que se evale la expresin. El listado 4.10 ilustra esta diferencia.
Listado 4.10. Diferencias entre operadores postfijos y prefijos
class Listing4_10 { public static void Main() ( int Int1; Int1 = 123; System.Console.WriteLine(Int1++); System.Console.WriteLine(++Int1); } }
Compile y ejecute el listado 4.3. El resultado de esta aplicacin aparece en la figura 4.2. La primera instruccin del listado 4.10 usa el operador postfijo de incremento, lo que significa que el valor se incrementa despus de que se ejecute la instruccin. La aplicacin escribe el valor actual, 123, en la consola, y luego incrementa el valor a 124. La segunda instruccin usa el operador de incremento postfijo, lo que significa que el valor se incrementa antes de que se ejecute la instruccin. La aplicacin primero incrementa el valor actual a 125 y luego escribe el valor actual en la consola.
115
Se establece el valor de MyInteger en 3 y se pierde el valor anterior de MyInteger. Los operadores de asignacin compuesta permiten usar el operador de asignacin ms de una vez en una instruccin:
MyInteger = MyOtherInteger = 3;
El valor de la expresin a la derecha se usa como el nuevo valor para las variables. En este ejemplo, se asigna 3 a MyInteger y a MyOtherInteger.
116
Si se est multiplicando un valor por una variable y colocando el resultado en la misma variable, se puede escribir una instruccin abreviada para realizar la multiplicacin. Al introducir un asterisco seguido por un signo igual se multiplica un valor por una variable y se actualiza el valor de la variable con el resultado:
MyInteger *= 3;
Si la operacin de divisin da como resultado un resto, el resultado de la operacin ser slo el cociente (vase el listado 4.13).
Listado 4.13. Operador divisin (Ejemplo 2) class MyClass { public static void Main() { int MyInteger; MyInteger } } = 7 / 3 ;
117
Cuando se ejecuta este cdigo, la variable M y I n t e g e r 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 misma variable, se puede escribir una instruccin abreviada para realizar la divisin. Escribiendo una barra inclinada seguida por un signo igual se divide un valor entre una variable y se actualiza el valor de la variable con el resultado:
MyInteger /= 3;
Cuando se ejecuta este cdigo, la variable M y I n t e g e r 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 misma variable, se puede escribir una instruccin abreviada para realizar la operacin de resto. Si se escribe un signo de tanto por ciento seguido del signo igual se calcular el resto de una variable y se actualizar el valor de la variable con el resultado:
MyInteger %= 3;
118
Si se est sumando un valor a una variable y se coloca el resultado en la misma variable, se puede escribir una instruccin abreviada que realice la suma. Al escribir un signo ms seguido de un signo igual se aade un valor a una variable y se actualiza el valor de la variable con el resultado:
MyInteger += 3;
El operador de suma tiene un significado especial cuando los dos operandos son cadenas. La suma de dos cadenas une la primera cadena a la segunda:
string CombinedString = "Hello from " + "C#";
Si se est restando un valor a una variable y se coloca el resultado en la misma variable, se puede escribir una instruccin abreviada 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:
MyInteger -= 3;
Cuando se ejecuta este cdigo, la variable M y I n t e g e r tiene un valor de 48, porque el valor original, 6, es considerado un nmero binario con un valor binario de 00000110. Cada bit en el valor original se desplaza tres lugares, que es el valor que aparece despus del operador de desplazamiento a la izquierda, y se colocan ceros en los bits de orden inferior. Al cambiar cada bit tres lugares da como resultado un valor binario de 00110000 o 48 en el sistema decimal. Se pueden aplicar desplazamientos a la izquierda a los valores de las expresiones de tipo i n t , u i n t , l o n g y u l o n g . Tambin pueden desplazarse a la izquierda otras expresiones que pueden ser convertidas a uno de estos tipos. Las
120
expresiones de tipo i n t y u i n t pueden desplazarse hasta 32 bits de una vez. Las expresiones de tipo l o n g y u l o n g pueden ser desplazadas hasta 64 bits de una vez. Si se est calculando una operacin de desplazamiento a la izquierda de un valor y una variable, y se coloca el resultado en la misma variable, se puede escribir una instruccin abreviada que realice esta operacin. Al escribir dos signos menor que (<<) seguidos por un signo igual se calcula la operacin de desplazamiento a la izquierda y se actualiza el valor de la variable con el resultado:
MyInteger <<= 3;
Cuando se ejecuta este cdigo, la variable M y I n t e g e r tiene un valor de 6, porque el valor original, 48, es considerado un nmero binario con un valor binario de 00110000. Cada bit en el valor original se desplaza tres lugares, que es el valor que aparece despus del operador de desplazamiento a la derecha y se colocan ceros en los bits de orden superior. El cambiar cada bit tres lugares da como resultado un valor binario de 00000110 o 6 decimal. Se pueden aplicar desplazamientos a la derecha a los valores de las expresiones de tipo i n t , u i n t , l o n g y u l o n g . Tambin pueden desplazarse a la derecha otras expresiones que pueden ser convertidas a uno de estos tipos. Las expresiones de tipo i n t y u i n t pueden desplazarse hasta 32 bits de una vez. Las expresiones de tipo l o n g y u l o n g pueden ser desplazadas hasta 64 bits de una vez.
121
Si est calculando una operacin de desplazamiento a la derecha de un valor y una variable y colocando el resultado en la misma variable, puede escribir una instruccin abreviada que realice esta operacin. Escribir dos signos mayor que seguidos por un signo igual calcula la operacin de desplazamiento a la derecha y actualiza el valor de la variable con el resultado:
MyInteger >>= 3;
Si el valor de la variable M y I n t e g e r es 123, el operador de igualdad devuelve T r u e . Si tiene otro valor, el operador de igualdad devuelve F a l s e . El operador de igualdad tiene un significado especial cuando los dos operandos son cadenas. Al comparar dos cadenas se comparan los contenidos de las cadenas. Dos cadenas se consideran iguales si tienen la misma longitud y los mismos caracteres en cada posicin de la cadena.
122
desigualdad devuelve F a l s e . Como operador de desigualdad se usa un signo de exclamacin seguido por un signo igual:
MyInteger != 123;
Si el valor de la variable M y I n t e g e r es 123, el operador de desigualdad devuelve F a l s e . Si tiene otro valor, el operador de desigualdad devuelve T r u e . El operador de desigualdad tiene un significado especial cuando los dos operandos son cadenas. Al comparar dos cadenas se comparan los contenidos de las cadenas. Dos cadenas se consideran desiguales si tienen diferentes longitudes o diferentes caracteres en, al menos, una posicin de la cadena.
Si el valor de la variable M y I n t e g e r es menor de 123, el operador menor que devuelve True. Si tiene un valor mayor o igual a 123, el operador menor que devuelve False.
Si el valor de la variable M y I n t e g e r es mayor de 123, el operador mayor que devuelve T r u e . Si tiene un valor menor que o igual a 123, el operador menor que devuelve False.
123
Si el valor de la variable M y I n t e g e r es menor o igual a 123, el operador menor o igual que devuelve T r u e . Si tiene un valor mayor de 123, el operador menor o igual que devuelve False.
Si el valor de la variable M y I n t e g e r es mayor o igual a 123, el operador mayor o igual que devuelve T r u e . Si tiene un valor menor de 123, el operador mayor o igual que devuelve False.
124
El valor de M y I n t e g e r es 2. Recuerde que un bit en una operacin AND es 1 slo si los dos bits operandos de la misma posicin son 1. El valor 6 en binario es 110 y el valor 3 en binario es 011. Si se realiza un AND booleano de 110 y 011 se obtiene como resultado un valor booleano de 010 o 2 en el sistema decimal. Si se est calculando una operacin AND sobre un valor y una variable y se coloca el resultado en la misma variable, se puede escribir una instruccin abreviada que realice la operacin AND. Si se escribe un signo de unin (&) seguido de un signo igual se calcula la operacin AND sobre una variable y un valor, y se actualiza el valor de la variable con el resultado:
MyInteger &= 3;
El valor de M y I n t e g e r es 5. Recuerde que un bit en una operacin exclusiva OR es 1 slo si uno de los dos bits operandos en la misma posicin es 1. El valor de 6 en binario es 110 y el valor de 3 en binario es 011. Si realizamos un OR exclusivo booleano entre 110 y 011, obtenemos como resultado un valor booleano de 101 o 5 en el sistema decimal. Si se est calculando una operacin OR exclusiva sobre un valor y una variable y se coloca el resultado en la misma variable, se puede escribir una instruccin abreviada que realice la operacin OR exclusiva. Si se escribe un signo de circunflejo (^) seguido de un signo igual se calcula la operacin OR exclusiva sobre una variable y un valor, y se actualiza el valor de la variable con el resultado:
MyInteger ^= 3;
125
El valor de M y I n t e g e r es 7. Recuerde que un bit en una operacin OR es 1 slo si uno de los dos bits operandos de la misma posicin es 1. El valor 6 en binario es 110 y el valor 3 en binario es 011. Si se realiza un booleano OR entre 110 y 011 se obtiene como resultado un valor de 111 o 7 en decimal. Si se est calculando una operacin OR sobre un valor y una variable, y se coloca el resultado en la misma variable, puede escribir una instruccin abreviada que realice la operacin OR. Si se escribe una barra vertical (|) seguido de un signo igual se calcula la operacin OR sobre una variable y un valor, y se actualiza el valor de la variable con el resultado:
MyInteger |= 3;
126
El valor de MyBoolean es False porque uno de los dos operandos devuelve False .
Puede interpretar esta instruccin como "Compara el valor de M y V a r i a b l e con 123. Si esa expresin devuelve T r u e , haz que el valor de M y I n t e g e r sea 3. Si esa expresin devuelve False, haz que el valor de MyInteger sea 5".
Cul es el valor de M y V a r i a b l e aqu? Si C# aplica la multiplicacin en primer lugar, leer la instruccin como "multiplica 3 por dos y luego aade 1",
127
que da como resultado un valor de 7. Si C# aplica la suma en primer lugar, leer la instruccin como "suma 2 y 1 y luego multiplcalo por 3", que da como resultado un valor de 9. C# combina los operadores en grupos y aplica un orden de prioridad a cada grupo. Este orden de prioridad especifica qu operadores se evalan antes que otros. La lista con el orden de prioridad de C# es la siguiente, ordenados de mayor prioridad a menor: Expresiones primarias Operadores unarios + - ! ~ ++ -Operadores multiplicativos * / % Operadores aditivos + Operadores de desplazamiento << >> Operadores relacionales < > <= >= Operadores de igualdad == != AND lgico OR lgico exclusivo OR lgico AND condicional OR condicional Ternario condicional Operadores de asignacin
C# da a M y V a r i a b l e un valor de 7 porque la prioridad del operador de multiplicacin es superior a la del operador de suma. Esto significa que el operador de multiplicacin se evala primero y en segundo lugar el operador suma. Se puede invalidar el orden de prioridad con parntesis. Las expresiones entre parntesis se evalan antes de que se apliquen las reglas de prioridad de operadores:
MyVariable = 3 * (2 + 1);
En este caso, C# da a M y V a r i a b l e un valor de 9, porque la expresin de suma est entre parntesis, obligando a que se evale antes que la operacin de multiplicacin.
128
Resumen
C# define muchos operadores para ayudarle a evaluar expresiones y a calcular nuevos valores a partir de esas operaciones. Este lenguaje permite escribir expresiones que realizan funciones matemticas y booleanas, y compara dos expresiones y obtiene un resultado booleano de esa comparacin. En este captulo se presentan los operadores de C# y se aprende a usar estos operadores en expresiones con literales y variables. Tambin se han revisado las expresiones de operador y la prioridad al usar estos operadores en expresiones. Cuando examinemos las clases en un captulo posterior, descubrir que sus clases pueden redefinir algunos de estos operadores. A esto se le llama sobrecarga de operadores y le permite redefinir la forma en que los operadores calculan los resultados.
129
El comportamiento del cdigo C# a menudo depende de las condiciones que se determinan en tiempo de ejecucin. Quizs quiera escribir una aplicacin que salude a sus usuarios con un mensaje de "Buenos das" 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. y las 6:00 P.M. Comportamientos como ste necesitan que el cdigo C# examine valores en tiempo de ejecucin y realice una accin basada en dichos valores. C# admite varias construcciones de cdigo que le permiten examinar variables y realizar una o varias acciones basadas en dichas variables. En este captulo se examinan las instrucciones de flujo de control de C# que actuarn como el cerebro de las aplicaciones que escriba.
Instrucciones de C#
Una instruccin es una expresin vlida de C# que define una accin realizada por el cdigo. Las instrucciones pueden examinar valores de variables, asignar nuevos valores a una variable, llamar a mtodos, realizar una operacin, crear objetos o realizar alguna otra accin.
131
La instruccin ms corta posible en C# es la instruccin vaca. sta consiste en slo el punto y coma: ; Se puede usar la instruccin vaca para decir, "No hagas nada aqu". Esto podra no parecer muy til, pero tiene su funcin. NOTA: Todas las instrucciones de C# terminan en un punto y coma. Las instrucciones se agrupan en listas de instrucciones que se componen de una o ms instrucciones escritas en secuencia:
int MyVariable; MyVariable = 123; MyVariable += 234;
Por lo general, las instrucciones se escriben en su propia lnea. Sin embargo. C# no exige esta disposicin. C# ignora cualquier espacio en blanco entre instrucciones y acepta cualquier disposicin siempre que cada instruccin est separada 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 instrucciones. Casi siempre usar bloques de instrucciones para escribir el cdigo de la funcin. Toda la lista de instrucciones de la funcin se coloca en un bloque de instrucciones. Es perfectamente posible usar slo una instruccin en un bloque de instrucciones:
public static void Main() { System.Console.WriteLine("Hello!"); }
C# no impone ningn lmite al nmero de instrucciones que se pueden colocar en un bloque de instrucciones.
132
Tambin se puede inicializar la variable cuando se declara usando un signo igual y asignando un valor a la variable:
int MyVariable = 123;
C# permite enumerar varias variables en la misma instruccin. Para separar los nombres de las variables se usan comas.
int MyFirstVariable, MySecondVariable;
Cada variable de la instruccin tiene el tipo especificado. En el ejemplo anterior, MyFirstVariable y MySecondVariable son de tipo int. Las declaraciones de constantes definen una variable cuyo valor no puede cambiar durante la ejecucin del cdigo. Las declaraciones de constantes usan la palabra clave de C# const y deben asignar un valor a la variable cuando se declara dicha variable:
const int MyVariable = 123;
Las declaraciones de constantes permiten una mejor legibilidad y administracin del cdigo. Se pueden tener valores constantes en el cdigo y al asignarles nombres se consigue que el cdigo resulte ms legible que si usara su valor. Adems, si se usan valores por todo el cdigo y luego se necesita cambiarlos, sta ser una tarea muy pesada. Si se usa una constante, slo har falta cambiar una lnea de cdigo. Por ejemplo, suponga que est escribiendo un cdigo para una aplicacin que realiza medidas geomtricas. Uno de los valores con los que querr trabajar es pi, la relacin entre la circunferencia de un crculo y su dimetro. Sin una declaracin de constante, tendra que escribir un cdigo de la siguiente forma:
Area = 3.14159 * Radius * Radius;
Al usar una constante se logra que el cdigo sea un poco ms sencillo de entender:
const double Pi = 3.14159; Area = Pi * Radius * Radius;
133
La instruccin if
La instruccin if trabaja con una expresin que devuelve un valor booleano. Si la expresin booleana resulta ser t r u e , la instruccin incrustada en la instruccin if se ejecuta. Si la expresin booleana resulta ser f a l s e , la instruccin incrustada en la instruccin if no se ejecuta:
if (MyVariable == 123) System.Console.WriteLine("MyVariable's value is 123.");
La instruccin booleana se escribe entre parntesis. La instruccin incrustada sigue a los parntesis. Se usa un punto y coma para cerrar la instruccin incrustada, pero no la expresin booleana. NOTA: Cuando se usa la instruccin if para comprobar una igualdad, siempre se deben usar dos signos igual. Dos signos igual hacen una comprobacin de igualdad, mientras que un signo igual realiza una asignacin. Si se usa accidentalmente un signo igual dentro de una instruccin if, sta siempre devolver un valor true. En el anterior ejemplo, el valor de MyVariable se compara con el valor literal 123. Si el valor es igual a 123, la expresin devuelve t r u e y se escribe el mensaje MyVariable's value is 123 en la consola. Si el valor no es igual a 123, la expresin devuelve false y no se escribe nada. La instruccin if puede ir seguida de una clausula else . La palabra clave else va seguida de una instruccin incrustada que se ejecuta si la expresin booleana usada en la clusula if devuelve false:
if (MyVariable == 123) System.Console.WriteLine("MyVariable's else System.Console.WriteLine("MyVariable's value value is is 123."); not 123.");
En el anterior ejemplo, el valor de M y V a r i a b l e se compara con el valor literal 123. Si el valor es igual a 123, la expresin devuelve true y se escribe el mensaje MyVariable's value is 123 en la consola. Si el valor no es igual a 123, la expresin devuelve f a l s e y se escribe el mensaje
MyVariable's value is not 123 en la consola.
134
Las clusulas if y else permiten asociar una instruccin a la clusula. Por lo general, C# permite asociar slo una instruccin a la clusula, como se ve en el siguiente cdigo:
if (MyVariable == 123) System.Console.WriteLine("MyVariable's value is System.Console.WriteLine("This always prints."); 123.");
La instruccin que escribe This always prints en la consola siempre se ejecuta. No pertenece a la clusula if y se ejecuta independientemente de si el valor de M y V a r i a b l e es 123. La nica instruccin que depende de la comparacin de M y V a r i a b l e con 123 es la instruccin que escribe MyVariable's value is 123 en la consola. Si se quieren asociar varias instrucciones con una clusula if, se debe usar un bloque de instrucciones:
if (MyVariable == 123) { System.Console.WriteLine("MyVariable's value is 123."); System.Console.WriteLine("This prints if MyVariable == 123."); }
Como los bloques de instrucciones pueden contener una sola instruccin, el siguiente cdigo tambin es vlido:
if(MyVariable == 123) { System.Console.WriteLine("MyVariable's }
value
is
123.");
La instruccin switch
La instruccin switch evala una expresin y compara el valor de esa expresin con varios casos. Cada caso se asocia con una lista de instrucciones, que recibe el nombre de seccin de switch. C# ejecuta la lista de instruccin asociada con la seccin de switch que concuerde con el valor de la expresin.
135
La expresin usada como controlador de la instruccin switch se encierra entre los parntesis que siguen a la palabra clave switch. La expresin va seguida por llaves y las secciones de switch estn entre las llaves.
switch (MyVariable) { // aqu se colocan las secciones de switch }
Tambin se puede usar una expresin cuyo valor pueda ser convertido implcitamente a uno de los tipos de la lista anterior. Las secciones de switch empiezan con la palabra clave de C# case , seguida de una expresin constante. A esa expresin constante le siguen dos puntos y a continuacin escribimos la lista de instrucciones:
switch(MyVariable) { case 123: System.Console.WriteLine("MyVariable break; }
==
123");
C# evala la expresin en la instruccin switch y luego busca un bloque switch cuya expresin constante concuerde con el valor de la expresin. Si C# puede encontrar un valor similar en una de las secciones de switch, la lista de instrucciones de la seccin de switch se ejecuta. Una instruccin switch puede incluir muchas secciones de switch, cada una con un caso diferente:
switch(MyVariable) {
136
case 123: System.Console.WriteLine("MyVariable break; case 124: System.Console.WriteLine("MyVariable break; case 125: System.Console.WriteLine("MyVariable break; }
==
123");
==
124");
==
125");
C# permite agrupar varias etiquetas de caso juntas. Si se tiene ms de un caso que necesite ejecutar la misma lista de instrucciones, se pueden combinar las etiquetas de caso:
switch(MyVariable) { case 123: case 124: System.Console.WriteLine("MyVariable break; case 125: System.Console.WriteLine("MyVariable break; }
==
123
or
124");
==
125");
Una de las etiquetas de caso puede ser la palabra clave de C# default . La etiqueta default puede incluir su propia lista de instrucciones:
switch(MyVariable) { case 123: System.Console.WriteLine("MyVariable break; default: System.Console.WriteLine("MyVariable break; }
==
123");
!=
123");
La lista de instrucciones default se ejecuta cuando ninguna de las otras secciones de switch define alguna constante que concuerde con la expresin switch . La lista de instrucciones default es la parte que dice "Si no puedes encontrar algn bloque switch que concuerde, ejecuta este cdigo por defecto". El uso de la palabra clave default es opcional en sus instrucciones de switch.
137
La instruccin while
La instruccin while ejecuta una lista de instrucciones incrustada siempre que la expresin while resulte ser t r u e . La expresin booleana que controla la instruccin while se encierra entre los parntesis que siguen a la palabra clave while. Tras los parntesis situamos las instrucciones que se ejecutarn si la expresin booleana es true:
int MyVariable = 0; while(MyVariable < 10) { System.Console.WriteLine(MyVariable); MyVariable++; }
El cdigo incrustado en la instruccin while contina ejecutndose siempre que el valor de M y V a r i a b l e sea menor que 10. Las instrucciones incrustadas escriben el valor de M y V a r i a b l e en la consola y luego incrementan su valor. Cuando el valor de M y V a r i a b l e alcanza 10, la expresin booleana M y V a r i a b l e < 10 devuelve f a l s e y la lista de instrucciones incrustada en la instruccin while deja de ejecutarse. La instruccin que sigue a la instruccin while se ejecuta en cuanto la expresin booleana de la instruccin while devuelve false.
La instruccin do
La instruccin while ejecuta sus instrucciones incrustadas cero o ms veces. Si la expresin booleana usada en la expresin while devuelve f a l s e , ninguna de las instrucciones incrustadas se ejecuta:
int MyVariable = 100; while(MyVariable < 10) { System.Console.WriteLine(MyVariable); }
138
Este cdigo no escribe nada en la consola porque la expresin booleana usada en la instruccin while, MyVariable < 10, devuelve f a l s e la primera vez que se ejecuta. Como la expresin booleana devuelve f a l s e inmediatamente, las instrucciones incrustadas nunca se ejecutan. Si quiere asegurarse de que las instrucciones incrustadas se ejecuten al menos una vez, puede usar la instruccin do . La instruccin do va seguida de instrucciones incrustadas, que a su vez van seguidas de la palabra clave while . Tras ella va la expresin booleana que controla el nmero de veces que se ejecuta el bucle:
int MyVariable = 0; do { System.Console.WriteLine(MyVariable); MyVariable++; } while(MyVariable < 10);
Las sentencias incrustadas siempre se ejecutan al menos una vez debido a que la expresin booleana se evala despus de que se ejecuten las instrucciones incrustadas, como se puede ver en el siguiente cdigo:
int MyVariable = 100; do { System.Console.WriteLine(MyVariable); MyVariable++; } while(MyVariable < 10);
La instruccin for
La instruccin for es la instruccin de iteracin ms potente. El cdigo de control de una instruccin for se divide en tres partes:
139
Un iniciador, que fija las condiciones iniciales de la instruccin del bucle for. Una condicin, que especifica la expresin booleana que mantiene ejecutndose la instruccin for. Un iterador, que especifica las instrucciones que se ejecutan al final de cada paso por las instrucciones incrustadas.
La instruccin for empieza con la palabra clave for, seguida por parntesis, que contienen las instrucciones iniciadora, de condicin y de iteracin, todas separadas por puntos y coma. Las instrucciones incrustadas siguen a los parntesis. Observe el siguiente bucle simple for:
int MyVariable; for (MyVariable = 0; MyVariable < 10; MyVariable++) { System.Console.WriteLine(MyVariable); }
El iniciador en este bucle for es la instruccin MyVariable = 0. El iniciador slo se ejecuta una vez en un bucle for y se ejecuta antes de que las instrucciones incrustadas se ejecuten por primera vez. La condicin de este bucle for es la instruccin MyVariable < 10 . La condicin en un bucle for debe ser una expresin booleana. Las instrucciones incrustadas de un bucle for se ejecutan mientras esta expresin booleana devuelve t r u e . Cuando la expresin devuelve f a l s e , las instrucciones incrustadas dejan de ejecutarse. El iterador de este bucle for es la instruccin MyVariable++ . El iterador se ejecuta despus de cada paso por las instrucciones incrustadas del bucle for. Si se pone toda esta informacin junta, se podr interpretar la instruccin como: "Fija el valor de M y V a r i a b l e igual a cero. Mientras el valor de MyVariable sea menor de 10, escribe el valor en la consola y luego aumenta el valor de MyVariable". Estas instrucciones escriben lo siguiente en la consola:
0 1 2 3 4 5 6 7 8 9
El iniciador, la condicin 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 instruccin. El siguiente cdigo es, en buena lgica, equivalente al cdigo anterior:
int MyVariable = 0; for(; MyVariable < 10; MyVariable++) { System.Console.WriteLine(MyVariable); }
Hay que tener cuidado cuando se omita la parte de la condicin en un bucle for. El siguiente cdigo es un ejemplo de los problemas que pueden surgir si no se incluyen condiciones:
int MyVariable; for (MyVariable = 0; ; MyVariable++) { System.Console.WriteLine(MyVariable); }
Este cdigo se ejecuta hasta que M y V a r i a b l e finalmente provoca un error porque contiene un nmero demasiado largo para ser almacenado. Esto ocurre porque ninguna condicin del bucle for llega a devolver f a l s e , lo que permite a la variable aumentar hasta superar su lmite. Las condiciones que faltan devuelven t r u e en un bucle for. Como la condicin en el cdigo anterior de ejemplo siempre es t r u e , nunca devuelve f a l s e y la instruccin for nunca deja de ejecutar su instruccin incrustada. Las expresiones iniciadoras, de condicin y de iteracin pueden contener varias instrucciones, separadas por comas. El siguiente cdigo es vlido:
int MyFirstVariable; int MySecondVariable; for(MyFirstVariable = 0, MySecondVariable = 0; MyFirstVariable < 10; MyFirstVariable++, MySecondVariable++) { System.Console.WriteLine(MyFirstVariable); System.Console.WriteLine(MySecondVariable); }
141
La instruccin foreach
Se puede usar la instruccin foreach para repetir varias veces los elementos de una coleccin. Las matrices de C# admiten la instruccin foreach y pueden usarse para trabajar fcilmente con cada elemento de la matriz. La instruccin foreach se usa escribiendo la palabra clave foreach seguida de parntesis. Estos parntesis deben contener la siguiente informacin: El tipo del elemento de la coleccin. Un nombre identificador para un elemento de la coleccin. La palabra clave in . El identificador de la coleccin.
Tras los parntesis se colocan las instrucciones incrustadas. El listado 5.1 muestra la instruccin foreach en accin. Crea una matriz entera de cinco elementos y luego usa la instruccin foreach para acudir a cada elemento de la matriz y escribir su valor en la consola.
Listado 5.1. Usando la instruccin f o r e a c h class Listing5_1 { public static void Main() { int [] MyArray; MyArray = new int [5]; MyArray[0] = 0; MyArray[1] = 1; MyArray[2] = 2; MyArray[3] = 3; MyArray[4] = 4; foreach(int ArrayElement in MyArray) System.Console.WriteLine(ArrayElement); } }
El identificador A r r a y E l e m e n t es una variable definida 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 trabajar con cada elemento de una matriz sin tener que conocer el tamao de la misma.
142
La instruccin break
Ya vio la instruccin break en la seccin dedicada a las instrucciones switch . C# tambin permite usar la instruccin break para salir del bloque de instrucciones en el que se encuentre. Normalmente, la instruccin break se usa para salir de un bloque de instrucciones iterativas:
int MyVariable = 0; while(MyVariable < 10) { System.Console.WriteLine(MyVariable); if(MyVariable == 5) break; MyVariable++; } System.Console.WriteLine("Out of the loop.");
El cdigo se interpreta: "Si el valor de MyVariable es 5, sal del bucle while ". Cuando se ejecuta la instruccin break, C# transfiere el control a la instruccin que sigue a las instrucciones incrustadas de la instruccin de iteracin. La instruccin break suele usarse con bloques de instrucciones switch, while, do, for y foreach.
La instruccin continue
La instruccin continue devuelve el control a la expresin booleana que controla una instruccin de iteracin, como se puede ver en el siguiente cdigo:
int MyVariable; for(MyVariable = 0; MyVariable < 10; MyVariable++) { if(MyVariable == 5) continue; System.Console.WriteLine(MyVariable); }
143
3 4 6 7 8 9
Este cdigo interpreta: "Si el valor de M y V a r i a b l e es 5, contina hasta la siguiente iteracin del bucle for sin ejecutar ninguna otra instruccin incrustada". Por eso no aparece el 5 en pantalla. Cuando el valor de M y V a r i a b l e es 5, el control regresa a la parte superior del bucle for y la llamada a W r i t e L i n e ( ) nunca se produce en esa iteracin del bucle for. Al igual que la instruccin break , la instruccin continue suele usarse en los bloques de instrucciones switch, while, do, for y foreach.
La instruccin goto
La instruccin goto transfiere sin condiciones el control a una instruccin etiquetada. Puede etiquetarse cualquier instruccin de C#. Las etiquetas de instrucciones son identificadores que preceden a una instruccin. Despus de una etiqueta de instruccin se colocan dos puntos. Un identificador de etiqueta sigue a la palabra clave goto y la instruccin goto transfiere el control a la instruccin designada por el identificador de etiqueta, como muestra el siguiente cdigo:
int MyVariable = 0; while(MyVariable < 10) { System.Console.WriteLine(MyVariable); if(MyVariable == 5) goto Done; MyVariable++; } Done: System.Console.WriteLine("Out of the loop.");
Cuando el valor de M y V a r i a b l e es 5, la instruccin goto se ejecuta y transfiere el control a la instruccin con la etiqueta Done . La instruccin goto siempre se ejecuta, independientemente de la instruccin de iteracin que pueda estar ejecutndose. Tambin se puede usar la palabra clave goto en conjuncin con las etiquetas de caso en una instruccin switch, en lugar de una instruccin break:
144
switch(MyVariable) { case 123: System.Console.WriteLine("MyVariable goto case 124; case 124: System.Console.WriteLine("MyVariable break; }
==
123");
==
124");
NOTA: Usar la instruccin goto en muchos sitios puede hacer el cdigo confuso e ilegible. Lo mejor es evitar usar una instruccin goto siempre que sea posible. Intente reestructurar el cdigo para no tener que recurrir al uso de una instruccin goto.
Resumen
C# dispone de varios medios de controlar la ejecucin del cdigo, dndole opciones para ejecutar un bloque de cdigo ms de una vez o, a veces, ninguna vez, basndose en el resultado de una expresin booleana. La instruccin if ejecuta el cdigo una vez, pero slo si la expresin booleana que la acompaa devuelve t r u e . La instruccin if puede incluir una clusula else, que ejecuta un bloque de cdigo si la expresin booleana devuelve false. La instruccin switch ejecuta uno de los muchos bloques de cdigo posibles. Cada bloque de cdigo viene precedido de una lista de instrucciones de caso.
145
C# evala la expresin de la instruccin switch y a continuacin busca una lista de instrucciones de caso cuyo valor coincida con la expresin evaluada en la instruccin switch. Las instrucciones while , do y for continan ejecutando el cdigo mientras la expresin booleana indicada sea t r u e . Cuando la expresin booleana devuelve f a l s e , las instrucciones incrustadas dejan de ejecutarse. Las instrucciones while y for se pueden definir para que sus expresiones booleanas devuelvan inmediatamente f a l s e , lo que quiere decir que sus instrucciones incrustadas nunca llegan a ejecutarse realmente. La instruccin do, sin embargo, siempre ejecuta sus instrucciones incrustadas al menos una vez. La instruccin foreach proporciona un buen modo de recorrer repetida y rpidamente los elementos de una matriz. Puede ordenar a un bucle foreach que recorra repetidamente los elementos de una matriz sin conocer el tamao de la matriz o los elementos que la forman. La instruccin foreach prepara un identificador especial formado por el valor del elemento de una matriz durante cada iteracin del bucle foreach. Las instrucciones break , continue y goto afectan al flujo normal de una instruccin de iteracin, como while o foreach. La instruccin break sale del bucle iterativo, incluso si la expresin booleana que controla la ejecucin del bucle sigue devolviendo t r u e . La instruccin continue devuelve el control a la parte superior del bucle iterativo sin ejecutar ninguna de las instrucciones incrustadas que la siguen. La instruccin goto siempre transfiere el control a la instruccin etiquetada. Puede acompaar sus operaciones matemticas con instrucciones checked o unchecked para especificar cmo quiere tratar los errores matemticos del cdigo C#.
146
Los mtodos son bloques de instrucciones que devuelven algn tipo de valor cuando se ejecutan. Pueden ser llamados mediante el nombre, y llamar a un mtodo hace que las instrucciones del mtodo se ejecuten. Ya hemos visto un mtodo: el mtodo M a i n ( ) . Aunque C# permite poner todo el cdigo en el mtodo Main(), probablemente quiera disear sus clases para definir ms de un mtodo. El uso de mtodos mantiene el cdigo legible porque las instrucciones se colocan en bloques ms pequeos, en lugar de en un gran bloque de cdigo. Los mtodos tambin permiten tomar instrucciones que pueden ser ejecutadas varias veces y colocarlas en un bloque de cdigo que puede ser llamado todas las veces que haga falta. En este captulo aprender a crear funciones que devuelven datos y que no los devuelven. Aprender a pasar parmetros a los mtodos y la mejor manera de estructurar un mtodo para hacer sus aplicaciones modulares.
La estructura de un mtodo
Como mnimo, un mtodo est compuesto de las siguientes partes: Tipo devuelto Nombre del mtodo
149
Lista de parmetros Cuerpo del mtodo NOTA: Todos los mtodos se encierran en una clase. Un mtodo no puede existir fuera de una clase. Los mtodos tienen otras partes opcionales, como las listas de atributos y los modificadores de mbito. Las siguientes secciones analizan los fundamentos de un mtodo.
Tipo devuelto
Un mtodo comienza definiendo el tipo de datos que devolver cuando se le llame. Por ejemplo, suponga que quiere escribir un mtodo que sume dos nmeros enteros y devuelva el resultado. En ese caso, escribir el tipo devuelto como int . C# permite escribir un mtodo que no devuelve nada. Por ejemplo, puede escribir un mtodo que simplemente escriba algn texto en la consola, pero que no calcule ningn dato que deba devolver al cdigo que llam al mtodo. En ese caso, se puede usar la palabra clave void para indicar al compilador de C# que el mtodo no devuelve ningn dato. Si se quiere devolver un valor de un mtodo se usa la palabra clave return para especificar el valor que debe devolverse. La palabra clave va seguida de una expresin que evala el tipo de valor que debe devolverse. Esta expresin puede ser un valor literal, una variable o una expresin ms compleja.
Lista de parmetros
Se puede llamar a mtodos con los parmetros que se usan para pasar los datos al mtodo. En el ejemplo anterior, en el que un mtodo suma dos nmeros enteros, se necesitara enviar al mtodo los valores de los dos nmeros enteros que se van a sumar. La lista de variables recibe el nombre de lista de parmetros del mtodo. La lista de parmetros del mtodo aparece entre parntesis y sigue al nombre del
150
mtodo. Cada parmetro de la lista de parmetros est separado por una coma e incluye el tipo del parmetro seguido por su nombre. Tambin se pueden prefijar los parmetros de la lista de parmetros con modificadores que especifican cmo se usan sus valores dentro del mtodo. Veremos estos modificadores ms adelante en este mismo captulo. Se pueden definir mtodos que no reciben ningn parmetro. Si se quiere utilizar uno de estos mtodos, basta con dejar vacos los parntesis. Ya hemos visto esto en los mtodos M a i n ( ) escritos. Tambin se puede colocar la palabra clave void entre los parntesis para especificar que el mtodo no acepta parmetros.
from CallMethod()!");
NOTA: Necesita las instrucciones de Main() que crean un nuevo objeto Listing6_1 antes de poder llamar a los mtodos del objeto.
151
Si el mtodo se define con una lista de parmetros, sus valores deben ser especificados en el momento de llamar al mtodo. Debe especificar los parmetros en el mismo orden en que son especificados en la lista de parmetros del mtodo, como muestra el listado 6.2.
Listado 6.2. Llamada a un mtodo con un parmetro class Listing6_2 { public static void Main() { int MyInteger; Listing6_2 MyObject; MyObject = new Listing6_2(); MyObject.CallMethod(2); MyInteger = 3; MyObject.CallMethod(MyInteger); } void CallMethod(int Integer) { System.Console.WriteLine(Integer); } }
Cuando compile y ejecute el listado 6.2 obtendr un resultado igual al de la figura 6.1.
c:\ C:\WINDOWS\System32\cmd.exe
C:\>Listing6_2.exe 2 3 C:\>
Esto es debido a que el mtodo Main() llama a CallMethod() dos veces: una con el valor 2 y otra con el valor 3. El cuerpo del mtodo CallMethod() escribe en la consola el valor suministrado.
152
Cuando suministramos un valor para el parmetro de un mtodo, podemos usar un valor literal, como el 2 del listado 6.2 o suministrar una variable y usar su valor, como en la variable MyInteger del listado 6.2. Cuando se llama a un mtodo, C# toma el valor especificado y asigna esos valores a los parmetros que se usan en el mtodo. Durante la primera llamada a CallMethod() en el listado 6.2, el literal 2 se usa como el parmetro del mtodo y el parmetro Integer del mtodo recibe el valor 2. Durante la segunda llamada a CallMethod() en el listado 6.2, la variable MyInteger se usa como el parmetro del mtodo y el parmetro Integer del mtodo recibe el valor de la variable MyInteger: 3. Los parmetros que se especifican cuando se llama a un mtodo deben concordar con los tipos especificados en la lista de parmetros. Si un parmetro de la lista de parmetros del mtodo especifica un tipo i n t , por ejemplo, los parmetros que le pase debern ser de tipo i n t o de un tipo que pueda ser convertido a i n t . Cualquier otro tipo produce un error al compilar el cdigo. C# es un lenguaje de tipos seguros, lo que significa que se comprueba la legalidad de los tipos de variables al compilar el cdigo C#. Por lo que respecta a los mtodos, esto significa que deben especificarse los tipos correctos cuando se especifican parmetros. El listado 6.3 muestra los tipos correctos durante la especificacin de parmetros:
Listado 6.3. Seguridad de tipos en listas de parmetros de mtodo class Listing6_3 { public static void Main() { Listing6_3 MyObject; MyObject = new Listing6_3(); MyObject.CallMethod("a string"); } void CallMethod(int Integer) { System.Console.WriteLine(Integer); } }
Este cdigo no se compila, como puede verse en la figura 6.2. El compilador de C# emite estos errores porque el mtodo CallMethod() se est ejecutando con un parmetro de cadena y la lista de parmetros de CallMethod() especifica que se debe usar un nmero entero como parmetro. Las cadenas no son nmeros enteros ni pueden ser convertidas a nmeros enteros y esta discordancia hace que el compilador de C# genere errores. Si el mtodo devuelve un valor, debe declararse una variable que contenga el valor devuelto. La variable usada para esta operacin se coloca antes del nombre
153
del mtodo y un signo igual separa el identificador de la variable y el nombre del mtodo, como se ve en el listado 6.4.
c:\ C:\WIINDOWS\System32\cmd.exe C:\>csc Class1.cs Compilador de Microsoft (R) Visual C# .NET versin 7.00.9466 para Microsoft (R) .NET Framework versin 1.0.3705 (C) Microsoft Microsoft Corporation 2001. Reservados todos los derechos. Class1.cs(8,3): error CS1502: La mejor coincidencia de mtodo sobrecargado para 'Listing6_3.CallMethod(int)' tiene algunos argumentos no vlidos Class1.cs(8,23): error CS1503: Argumento ' 1 ' : no se puede convertir de 'string' a 'int' C:\>
Figura 6.2. La llamada a un mtodo con un tipo de datos no vlido produce errores de compilacin. Listado 6.4. Devolucin de un valor de un mtodo class Listing6_4 { public static void Main() ( Listing6_4 MyObject; int ReturnValue; MyObject = new Listing6_4(); ReturnValue = MyObject.AddIntegers(3, 5 ) ; System.Console.WriteLine(ReturnValue); } int AddIntegers(int Integer1, int Integer2) { int Sum; Sum = Integer1 + Integer2; return Sum; } }
En este cdigo suceden varias cosas: Se declara un mtodo llamado AddIntegers(). El mtodo tiene dos parmetros en su lista de parmetros: un nmero entero llamado Integer1 y otro nmero entero llamado Integer2 .
154
El cuerpo del mtodo AddIntegers() suma los valores de los dos parmetros y asigna el resultado a una variable local llamada Sum. Se devuelve el valor de Sum. El mtodo M a i n ( ) llama al mtodo AddIntegers() con los valores 3 y 5. El valor devuelto del mtodo AddIntegers() 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 programa que aparece en el listado 6.4.
C:\ C:\WINDOWS\System32\cmd.exe C:\>Listing6_4 .exe 8 C:\>_
Tipos de parmetros
C# permite cuatro tipos de parmetros en una lista de parmetros: Parmetros de entrada Parmetros de salida Parmetros de referencia Matrices de parmetros
Parmetros de entrada
Los parmetros de entrada son parmetros cuyo valor es enviado al mtodo. Todos los parmetros que se han usado hasta ahora han sido parmetros de entra-
155
da. Los valores de estos parmetros de entrada se envan a la funcin, pero el cuerpo del mtodo no puede cambiar permanentemente sus valores. El listado 6.4 del anterior ejemplo define un mtodo con dos parmetros de entrada: Integer1 y Integer2 . Los valores de estos parmetros se introducen en el mtodo, que lee sus valores y hace su trabajo. Los parmetros de entrada se pasan a los mtodos por valor. Bsicamente, el mtodo ve una copia del valor del parmetro, pero no se le permite cambiar el valor proporcionado por la parte que realiza la llamada. En el listado 6.5 se puede ver un ejemplo.
Listado 6.5. Cmo modificar copias de parmetros de entrada class Listing6_5 { public static void Main() { int MyInteger; Listing6_5 MyObject; MyObject = new Listing6_5(); MyInteger = 3; MyObject.CallMethod(MyInteger) ; System.Console.WriteLine(MyInteger); } void CallMethod(int Integer1) { Integer1 = 6; System.Console.WriteLine(Integer1); } }
En el listado 6.5, el mtodo M a i n ( ) establece una variable entera llamada M y I n t e g e r y le asigna el valor de 3. A continuacin llama a MyMethod() con M y I n t e g e r como parmetro. El mtodo C a l l M e t h o d ( ) establece el valor del parmetro en 6 y luego escribe el valor en la consola. Cuando el mtodo C a l l M e t h o d ( ) se devuelve, el mtodo M a i n ( ) contina y escribe el valor de M y I n t e g e r . Si ejecuta este cdigo, el resultado debera parecerse al de la figura 6.4. Este resultado se produce porque el mtodo C a l l M e t h o d ( ) modifica su copia del parmetro de entrada, pero esa modificacin no afecta al valor del mtodo original proporcionado por M a i n ( ) . El valor de M y I n t e g e r sigue siendo 3 despus de que regrese el mtodo C a l l M e t h o d ( ) , debido a que C a l l M e t h o d ( ) no puede cambiar el valor del parmetro de entrada del elemento que hace la llamada. Slo puede cambiar el valor de su copia del valor.
Parmetros de salida
Los parmetros de salida son parmetros cuyos valores no se establecen cuando se llama al mtodo. En su lugar, el mtodo establece los valores y los devuelve al
156
elemento que hace la llamada mediante el parmetro de salida. Suponga, por ejemplo, que quiere escribir un mtodo que cuente el nmero de registros de una tabla de una base de datos. Suponga que tambin quiere especificar si la operacin se realiz satisfactoriamente (La operacin puede no realizarse si, por ejemplo, la tabla de bases de datos no est disponible). Por tanto, queremos que el mtodo devuelva dos instancias de informacin: Un contador de registros. Un indicador de xito de operacin.
C# slo permite a los mtodos devolver un valor. Qu hacer si queremos que devuelva dos instancias de informacin? La respuesta est en el concepto de parmetro de salida. Puede hacer que su mtodo devuelva el indicador de xito de la operacin como un valor booleano y especificar el recuento de registros como un parmetro de salida. El mtodo almacena el recuento de registros en una variable de salida, cuyo valor es recogido por el elemento que hizo la llamada. Los parmetros de salida se especifican en listas de parmetros con la palabra clave out . La palabra clave out debe preceder al tipo de parmetro en la lista de parmetros. Cuando se llama a un mtodo con un parmetro de salida, se debe declarar una variable que contenga ese valor, como se ve en el listado 6.6.
Listado 6.6. Cmo trabajar con parmetros de salida
class Listing6_6 { public static void Main() { int MyInteger;
157
Listing6_6 MyObject; MyObject = new Listing6_6(); MyObject.CallMethod(out MyInteger); System.Console.WriteLine(MyInteger); } void CallMethod(out int Integer1) { Integer1 = 7; } }
El listado 6.6 define un mtodo llamado CallMethod() , que define un parmetro de salida entero llamado Integer1 . El cuerpo del mtodo establece el valor del parmetro de salida en 7. El mtodo M a i n ( ) declara un entero llamado M y I n t e g e r y lo usa como parmetro de salida para el mtodo CallMethod(). Cuando CallMethod() se devuelve, el valor de MyInteger se escribe en la consola. La figura 6.5 contiene el resultado de estas aplicaciones de prueba de parmetros.
c:\ C:\WINDOWS\System32\cmd.exe
C:\>Listing6_6.exe 7 C:\>_
Debe usarse la palabra clave o u t dos veces por cada parmetro: una vez cuando se declara el parmetro y otra vez cuando se especifica el parmetro de salida al llamar al mtodo. Si se olvida la palabra clave o u t al llamar a un mtodo con un parmetro de salida, se obtienen los siguientes errores del compilador:
Listing6-6.cs (9, 6): error CS1502: La mejor coincidencia de mtodo sobrecargado para 'Listing6_6.CallMethod(out int)' tiene algunos argumentos no vlidos Listing6-6.cs (9, 26): error CS1503: Argumento '1': no se puede convertir de 'int' a 'out int'
158
Cualquier valor asignado a variables usadas como parmetros de salida antes de que se llame al mtodo se pierde. Los valores originales se sobrescriben con los valores que les asign el mtodo.
Parmetros de referencia
Los parmetros de referencia proporcionan valores por referencia. En otras palabras, el mtodo recibe una referencia a la variable especificada cuando se llama al mtodo. Piense en un parmetro de referencia como en una variable de entrada y de salida. El mtodo puede leer el valor original de la variable y tambin modificar el valor original como si fuera un parmetro de salida. Los parmetros de referencia se especifican en listas de parmetros con la palabra clave ref . La palabra clave ref debe preceder al tipo del parmetro en la lista de parmetros. Cuando se llama a un mtodo con un parmetro de referencia, se debe declarar una variable para que contenga el valor del parmetro de referencia, como se ve en el listado 6.7.
Listado 6.7. Cmo trabajar con parmetros de referencia class Listing6_7 { public static void Main() { int MyInteger; Listing6_7 MyObject; MyObject = new Listing6_7(); MyInteger = 3; System.Console.WriteLine(MyInteger); MyObject.CallMethod(ref MyInteger); System.Console.WriteLine(MyInteger); } void CallMethod(ref int Integer1) { Integer1 = 4; } }
El mtodo CallMethod() del listado 6.7 usa un parmetro de referencia llamado Integer1 . El cuerpo del mtodo establece el valor del parmetro de referencia en 4. El mtodo M a i n ( ) declara una variable entera llamada MyInteger y le asigna un valor de 3. Escribe el valor de MyInteger en la consola y luego lo usa como el parmetro del mtodo CallMethod(). Cuando CallMethod() retorna, el valor de MyInteger se escribe en la consola una segunda vez. Si ejecuta el cdigo del listado 6.7 debera obtener los valores que aparecen en la figura 6.6.
159
La segunda lnea lee 4 porque la sintaxis del parmetro de referencia permite al mtodo cambiar el valor de la variable original. ste es un cambio respecto al ejemplo de parmetro de entrada del listado 6.5. Debe usarse la palabra clave ref dos veces para cada parmetro: una vez cuando se declara el parmetro en la lista de parmetros y otra vez cuando se especifica el parmetro de referencia al llamar al mtodo.
Matrices de parmetros
Los mtodos suelen escribirse para recibir un nmero especfico de parmetros. Un mtodo con una lista de tres parmetros siempre espera ser llamada con tres parmetros, ni ms, ni menos. Sin embargo, a veces puede ocurrir que un mtodo no conozca cuntos parmetros debe aceptar al ser diseado. Puede escribir un mtodo que acepte una lista de cadenas que especifiquen los nombres de los registros que debern borrarse del disco. Cuntas cadenas debe permitir el mtodo? Para ser flexible, el mtodo debe disearse de manera que el invocador pueda especificar las cadenas que necesita. Esto hace que llamar al mtodo sea un poco ms flexible porque el elemento que realiza la llamada ahora puede decidir cuntas cadenas deben pasarse al mtodo. Sin embargo, cmo escribiremos la lista de parmetros d e l mtodo cuando dicho mtodo no conoce cuntos parmetros le sern pasados? Las matrices de parmetros resuelven este problema de diseo, ya que permiten especificar que el mtodo acepte un nmero variable de argumentos. La matriz de parmetros de la lista de parmetros se especifica usando la palabra clave de C# params , seguida del tipo de variable que deber proporcionar el llamador. La especificacin de tipos va seguida de corchetes, que a su vez van seguidos del identificador de la matriz de parmetros, como se ve en el listado 6.8.
160
Listado 6.8. Cmo trabajar con matrices de parmetros class Listing6_8 { public static void Main() { Listing6_8 MyObject; MyObject = new Listing6_8(); MyObject.CallMethod(1); MyObject.CallMethod(1, 2 ) ; MyObject.CallMethod(1, 2, 3 ) ; } void CallMethod(params int[] ParamArray) { System.Console.WriteLine("------------"); System.Console.WriteLine("CallMethod()"); System.Console.WriteLine("------------"); foreach(int ParamArrayElement in ParamArray) System.Console.WriteLine(ParamArrayElement); } }
En el listado 6.8 el mtodo CallMethod() est escrito para aceptar un nmero variable de enteros. El mtodo recibe los parmetros en forma de matriz de nmeros enteros. El cuerpo del mtodo usa la instruccin foreach para iterar la matriz de parmetros y escribe cada elemento en la consola. El mtodo M a i n ( ) llama al mtodo CallMethod() tres veces, cada vez con un nmero de argumentos diferente. Esto es posible slo porque CallMethod() se declara con una matriz de parmetros. La figura 6.7 indica que todos los parmetros fueron pasados intactos al mtodo.
C:\ C:\WINDOWS\System32\cmd.exe C:\>Lisnting6_8.exe -----------CallMethod() -----------1 -----------CallMethod() -----------1 2 -----------CallMethod() -----------1 2 3
Figura C:\>
161
Puede usar una matriz de parmetros en su lista de parmetros de mtodo. Puede combinar una matriz de parmetros con otros parmetros en la lista de parmetros de mtodo. Sin embargo, si usa una matriz de parmetros en una lista de parmetros de mtodo, debe especificarla como el ltimo parmetro de la lista. No se puede usar las palabras clave out o ref en una matriz de parmetros.
Sobrecarga de mtodos
C# permite definir varios mtodos con el mismo nombre en la misma clase, siempre que esos mtodos tengan listas de parmetros diferentes. Esta operacin se conoce como sobrecargar el nombre del mtodo. Observe el ejemplo en el listado 6.9.
Listado 6.9. Cmo trabajar con mtodos sobrecargados class Listing6_9 { public static void Main() { Listing6_9 MyObject; MyObject = new Listing6_9(); MyObject.Add(3, 4 ) ; MyObject.Add(3.5, 4.75); } void Add(int Integer1, int Integer2) { int Sum; System.Console.WriteLine("adding two integers"); Sum = Integer1 + Integer2; System.Console.WriteLine(Sum); } void Add(double Double1, double Double2) { double Sum; System.Console.WriteLine("adding two doubles"); Sum = Double1 + Double2; System.Console.WriteLine(Sum); } }
El listado 6.9 implementa dos mtodos Add(). Uno de ellos toma dos nmeros enteros como parmetros de entrada y el otro toma dos dobles. Como las dos implementaciones tienen diferentes listas de parmetros, C# permite que los dos
162
mtodos Add() coexistan en la misma clase. El mtodo M a i n ( ) llama al mtodo Add() dos veces: una vez con dos parmetros enteros y otra con dos valores de punto flotante. Como puede ver en la figura 6.8, los dos mtodos se ejecutan satisfactoriamente, procesando los datos correctos.
C:\ C:\WINDOWS\System32\cmd.exe C: \ >Listing6_9.exe adding two integers 7 adding two doubles 8,25 C:\>
Cuando el compilador de C# encuentra una llamada a un mtodo que tiene ms de una implementacin, examina los parmetros usados en la llamada y llama al mtodo con la lista de parmetros que mejor concuerde con los parmetros usados en la llamada. En la primera llamada a Add() se usan dos nmeros enteros. Entonces, el compilador de C# empareja esta llamada con la implementacin de Add() que toma los dos parmetros de entrada enteros, porque los parmetros de la llamada concuerdan con la lista de parmetros que tiene los nmeros enteros. En la segunda llamada a Add() se usan dos dobles. El compilador de C# entonces empareja esta llamada con la implementacin de Add() que toma los dos parmetros de entrada dobles, porque los parmetros de la llamada concuerdan con la lista de parmetros que tiene los dobles. No todos los mtodos sobrecargados necesitan usar el mismo nmero de parmetros en su lista de parmetros, ni todos los parmetros de la lista de parmetros tienen que ser del mismo tipo. El nico requisito que C# exige es que las funciones tengan diferentes listas de parmetros. Una versin de una funcin sobrecargada puede tener un entero en su lista de parmetros y la otra versin puede tener tipos de datos como string, long y char en su lista de parmetros.
Mtodos virtuales
Para proseguir con el tema de los mtodos virtuales, hay que comprender el concepto de herencia. La herencia basa una clase en otra ya existente, aadiendo
163
o quitando funcionalidad segn se necesite. En las siguientes secciones examinaremos cmo se crean y se usan los mtodos virtuales.
Mtodos sobrescritos
Para empezar esta seccin, construir un ejemplo de clase llamado Books . Esta clase contiene dos mtodos llamados T i t l e y R a t i n g . El mtodo T i t l e devuelve el nombre de un libro y el mtodo R a t i n g devuelve una cadena indicando el nmero de estrellas con que ha sido calificado el libro en cuestin. En el listado 6.10 se recoge el cdigo completo de su aplicacin. Escrbalo en su editor de texto preferido y complelo como hizo antes.
Listado 6.10. Cmo mostrar la informacin del ttulo y la puntuacin de un libro con las siguientes clases using System; namespace BookOverride { class Book { public string Title() { return "Programming Book"; } public string Rating() { return "5 Stars"; } } class Class1 { static void Main(string[] args) { Book bc = new Book(); Console.WriteLine(bc.Title()); Console.WriteLine(bc.Rating()); } } }
Antes de ejecutar este programa, repselo rpidamente. Como puede observar, una clase contiene el mtodo M a i n ( ) . Este mtodo es donde se inicializa una instancia de la clase B o o k O v e r r i d e , que contiene los mtodos T i t l e y Rating. Despus de inicializar una instancia, se llama a los mtodos T i t l e y R a t i n g y se escribe la salida en la consola. El resultado puede verse en la figura 6.9.
164
A continuacin, sobrescriba el mtodo T i t l e creando una clase basada en la clase Book. Para crear una clase basada en otra clase (y que por tanto permite sobrescribir mtodos), simplemente declare una clase de la forma habitual y ponga a continuacin del nombre de la clase dos puntos y el nombre de la clase en la que quiere que se base. Aada el cdigo del listado 6.11 a la aplicacin.
Listado 6.11. Cmo sobrescribir mtodos derivando la clase Book class Wiley : Book { new public string Title() { return "C# Bible"; } }
Este cdigo crea una clase W i l e y que deriva de la clase Book. Ahora puede crear un nuevo mtodo pblico llamado T i t l e . Como ya se ha asignado a este mtodo el mismo nombre que al definido en la clase Book, se sobrescribe el mtodo T i t l e , aunque sigue disponiendo de acceso a los otros miembros dentro de la clase Book.
Ahora que ha sobrescrito el mtodo T i t l e , debe cambiar el mtodo M a i n ( ) para usar su nueva clase. Cambie su mtodo M a i n ( ) como se muestra en el listado 6.12.
165
Listado 6.12. Cmo modificar el mtodo Main() para sobrecargar una clase static void Main(string[] args) { Wiley bc = new Wiley(); Console.WriteLine(bc.Title()); Console.WriteLine(bc.Rating()); }
En su mtodo Main(), cambie la variable bc para crear la nueva clase W i l e y . Como habr adivinado, al llamar al mtodo T i t l e , el ttulo del libro cambia de Programming Book a C# Bible . Fjese en que todava tiene acceso al mtodo R a t i n g , que fue definido originalmente en la clase Book. La sobrescritura de mtodos dentro de una clase base es una excelente manera de cambiar la funcionalidad especfica sin grandes problemas.
Resumen
C# permite escribir mtodos en sus clases de C#. Los mtodos pueden ayudar a dividir el cdigo en partes fciles de entender y pueden brindar un lugar nico en el que almacenar cdigo que pueda ser llamado varias veces. Las funciones pueden recibir parmetros. Los parmetros de entrada contienen valores que han sido pasados a los mtodos, pero sus valores no pueden cambiar. Los parmetros de salida tienen valores que les son asignados por un mtodo y el valor asignado es visible para el elemento que hace la llamada. Los parmetros de referencia contienen valores que pueden ser proporcionados dentro de la funcin y adems su valor puede ser modificado por el mtodo. Las matrices de parmetros permiten escribir mtodos que toman un nmero variable de argumentos. C# tambin permite sobrecargar mtodos. Los mtodos sobrecargados tienen el mismo nombre pero diferentes listas de parmetros. C# usa los parmetros proporcionados en la llamada para determinar a qu mtodo debe invocar cuando se ejecute el cdigo.
166
El punto tiene dos valores, la coordenada x y la coordenada y, que funcionan juntas para describir el punto.
169
Aunque puede escribir el cdigo C# de esta manera, es bastante pesado. Los dos valores deben poder usarse en cualquier cdigo que quiera trabajar con el punto. Si quiere que un mtodo trabaje con el punto, tendr que pasar los valores uno a uno:
void WorkWithPoint(int XCoordinate, int YCoordinate); void SetNewPoint(out int XCoordinate, out int YCoordinate);
La situacin resulta incluso ms complicada cuando varias variables trabajan juntas para 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 direccin, un nmero de telfono y un salario actual. Controlar todas estas variables por separado y asegurarse de que todas se usan como un grupo puede volverse complicado.
En el ejemplo anterior, los miembros de la estructura, X e Y, tienen el mismo tipo. Sin embargo, esto no es obligatorio. Las estructuras tambin pueden estar formadas por variables de distintos tipos. El ejemplo anterior del empleado puede presentar este aspecto:
struct Employee { public string FirstName; public string LastName; public string Address; public string City; public string State; public ushort ZIPCode; public decimal Salary; }
170
Como con todas las instrucciones de C#, slo puede declararse una estructura desde el interior de una clase. NOTA: C# no permite que aparezca ninguna instruccin fuera de una declaracin de clase.
Los valores iniciales de los miembros de la estructura siguen las reglas de inicializacin de valores descritas en un captulo anterior. Los valores se inicializan con alguna representacin del cero y de la cadena vaca. C# no permite inicializar miembros de estructuras en el momento de declararse. Observe el error en el siguiente cdigo:
struct Point { public int X = 100; public int Y = 200; }
Puede usar un mtodo especial llamado constructor para inicializar miembros de estructuras con valores distintos de cero. Ms adelante, en este mismo captulo, se examinarn los constructores.
Esta declaracin declara una variable llamada M y P o i n t cuyo tipo es el de la estructura P o i n t . Se puede usar esta variable igual que cualquier otra variable, incluso dentro de expresiones y como parmetro de un mtodo. El acceso a cada miembro de la estructura resulta tan sencillo como escribir el nombre del identificador de la variable de la estructura, un punto y a continuacin el miembro de la estructura. El listado 7.1 muestra cmo se puede usar una estructura en el cdigo.
171
Listado 7.1. Acceso a miembros de la estructura class Listing7_1 { struct Point { public int X; public int Y; } public static void Main() { Point MyPoint; MyPoint.X = 100; MyPoint.Y = 200 ; System.Console.WriteLine(MyPoint.X); System.Console.WriteLine(MyPoint.Y); } }
Se puede asignar una variable de estructura a otra, siempre que las estructuras sean del mismo tipo. Cuando se asigna una variable de estructura a otra, C# asigna el valor de la variable de estructura que aparece antes del signo igual a los valores correspondientes de la estructura que aparece despus del signo igual, como se puede observar en el listado 7.2.
Listado 7.2. Asignacin de una variable de estructura a otra class Listing7_2 { struct Point { public int X; public int Y; } public static void Main() { Point MyFirstPoint; Point MySecondPoint; MyFirstPoint.X = 100; MyFirstPoint.Y = 100; MySecondPoint.X = 200; MySecondPoint.Y = 200;
172
El cdigo anterior asigna el valor 100 a los miembros de MyFirstPoint y el valor de 200 a los miembros de M y S e c o n d P o i n t . Los valores de MyFirstPoint se escriben en la consola y luego los valores de la variable MyFirstPoint se copian en los valores de la variable MySecondPoint. Tras la asignacin, los valores de MyFirstPoint se vuelven a escribir en la consola. Si compila y ejecuta este cdigo, obtendr el resultado ilustrado en la figura 7.1.
C:\ C:\WINDOWS\System32\cmd.exe C:\>Listing7_2.exe 100 100 200 200 C:\>
Figura 7.1. Asignacin de una estructura a otra Todos los valores de una estructura se sobrescriben en una asignacin con los valores de la variable de estructura indicada despus del signo igual.
173
Una estructura puede definir ms de un constructor, siempre que los constructores tengan diferentes listas de parmetros. El listado 7.3 muestra una estructura Point con dos constructores.
Listado 7.3. Constructores de estructuras
class Listing7_3 { struct Point { public int X; public int Y; public Point(int InitialX) { X = InitialX; Y = 1000; } public Point(int InitialX, { X = InitialX; Y = InitialY; ( } public static void Main() { Point MyFirstPoint = new Point(); int InitialY)
174
Point MySecondPoint = new Point (100) ; Point MyThirdPoint = new Point(250, 475); System.Console.WriteLine(MyFirstPoint.X); System.Console.WriteLine(MyFirstPoint.Y); System.Console.WriteLine(MySecondPoint.X); System.Console.WriteLine(MySecondPoint.Y); System.Console.WriteLine(MyThirdPoint.X); System.Console.WriteLine(MyThirdPoint.Y); } }
La figura 7.2 ilustra el resultado de compilar y ejecutar el cdigo del listado 7.3.
c:\ C:\WINDOWS\System32\cmd.exe C:\>Listing7-3.exe 0 0 100 1000 250 475 C:\>_
Tenga en cuenta los siguientes conceptos del listado 7.3: La estructura P o i n t declara dos constructores. Uno recibe como argumento un solo nmero entero y el otro recibe dos nmeros enteros. Ambos llevan antepuesta la palabra clave p u b l i c , por lo que su cdigo es accesible al resto del cdigo de la clase. El constructor con un parmetro entero asigna al miembro X de la estructura el valor del argumento entero y asigna al miembro Y de la estructura el valor 1.000. El constructor con dos parmetros enteros asigna al miembro X de la estructura el valor del primer argumento entero y asigna al miembro Y de la estructura el valor del segundo argumento entero.
175
El cdigo declara tres variables de tipo Point. Cada una de ellas llama a uno de los constructores P o i n t . La declaracin de M y F i r s t P o i n t llama al constructor sin argumentos. ste es el constructor por defecto que C# define para cada estructura. La declaracin de M y S e c o n d P o i n t llama al constructor que tiene un argumento, y la declaracin de MyThirdPoint llama al constructor con dos argumentos.
Preste mucha atencin a la sintaxis del listado 7.3, que invoca a un constructor de estructura. Si se quiere invocar a un constructor en una estructura, se debe emplear la palabra clave new seguida del nombre de la estructura y de los parmetros del constructor entre parntesis. El valor de esa expresin se asigna a la variable que se est declarando. Observe la siguiente declaracin:
Point MyThirdPoint = new Point(250, 475);
Esta declaracin indica: "Crea una nueva estructura Point usando el constructor que tiene dos enteros. Asigna su valor a la variable M y T h i r d P o i n t " . Debido a las reglas de asignacin de estructuras anteriormente descritas, los miembros de la variable M y T h i r d P o i n t reciben los valores de los miembros de la nueva estructura. No es necesario hacer nada ms con la nueva estructura creada cuando se llam a new. El entorno comn de ejecucin (CLR) detecta que la estructura ya no se usa y se deshace de ella mediante el mecanismo de recoleccin de elementos no utilizados. En el listado 7.3 tambin aparece la sintaxis del constructor sin parmetros:
Point MyFirstPoint = new Point();
As se indica al compilador de C# que se quiere inicializar la estructura de la forma habitual. Se deben asignar valores a todos los miembros de una estructura antes de usarla, bien invocando a su constructor sin parmetros o asignando explcitamente todos los campos de un valor. Observe el listado 7.4.
Listado 7.4. Si se usa una estructura antes de inicializarla se producen errores de compilacin class Listing7_4 { struct Point { public int X; public int Y; } public static void Main() { Point MyPoint; System.Console.WriteLine(MyPoint.X);
176
System.Console.WriteLine(MyPoint.Y); } }
El cdigo anterior es errneo y, al compilarlo, el compilador 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.Point.X' siempre tendr el valor predeterminado 0 warning CS0649: El campo 'Listing7_4.Point.Y' siempre tendr el valor predeterminado 0 no asignado no asignado nunca se asigna y nunca se asigna y
Los mensajes de error avisan de que las llamadas a WriteLine() usan datos miembros en la estructura, pero que a esos datos miembros todava no se les ha asignado valor. La variable MyPoint no ha sido inicializada con una llamada al constructor sin parmetros ni tampoco se han asignado valores explcitamente a sus miembros. C# no invoca al constructor sin parmetros a menos que se escriba la llamada en el cdigo. ste es otro ejemplo de cmo el compilador de C# protege el cdigo para evitar que se comporte de forma impredecible. Todas las variables deben inicializarse antes de ser usadas.
177
if ((X == 0) && (Y == 0)) return true; else return false; } } public static void Main() { Point MyFirstPoint = new Point(100, 200); Point MySecondPoint = new Point(); if(MyFirstPoint.IsAtOrigin() == true) System.Console.WriteLine("MyFirstPoint oriqin."); else System.Console.WriteLine("MyFirstPoint origin."); if (MySecondPoint.IsAtOrigin() == true) System.Console.WriteLine("MySecondPoint origin."); System.Console.WriteLine("MySecondPoint origin."); }
is at the
is not at the
is at the
is not at the
La estructura P o i n t del listado 7.5 declara un mtodo llamado I s A t O r i g i n . El cdigo de ese mtodo comprueba los valores de los campos de la estructura y devuelve t r u e si las coordenadas del punto son (0, 0), y f a l s e en cualquier otro caso. El mtodo M a i n ( ) declara dos variables de tipo P o i n t : 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 explcito y 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 constructor por defecto sin parmetros. En ese momento el mtodo M a i n ( ) llama al mtodo I s A t O r i g i n con los dos puntos y escribe un mensaje basado en el valor devuelto por el mtodo. Si se compila y ejecuta el cdigo del listado 7.5. se obtiene el siguiente resultado en la consola:
MyFirstPoint is not at the origin. MySecondPoint is at the origin.
Hay que asegurarse de prefijar los mtodos con la palabra clave public si se quiere que sean accesibles por todo el resto del cdigo de la clase.
178
consideran variables; por tanto, no designan espacio de almacenamiento. Debido a esto, no pueden ser pasadas como parmetros ref o out. El listado 7.6 incluye una propiedad dentro de la estructura Point.
Listado 7.6. Definicin de una propiedad en el interior de una estructura
class Listing7_6 { struct Point { private int x; public int X { get { return x; } set { x = value; } } } public static void Main() { int RetValue; Point MyPoint = new Point(); MyPoint.X = 10; RetValue = MyPoint.X; System.Console.WriteLine(RetValue); } }
Este cdigo asigna un valor al miembro X de la estructura P o i n t y luego devuelve este valor a la variable R e t V a l u e . El resultado del listado 7.6 se ilustra en la figura 7.3. El uso de propiedades es una forma excelente de leer, escribir y calcular datos dentro de una estructura. No hace falta incluir mtodos voluminosos para que realicen los clculos, y se puede definir cmo y cundo pueden actuar los descriptores de acceso get y set.
179
El listado 7.7, que declara una estructura llamada MyStruct que contiene una cadena y un ndice, lo demuestra.
C:\ C:\WINDOWS.System32\cmd.exe C:\>Listing7_6.exe 10 C:\>
Figura 7.3. Definicin de una propiedad en el interior de una estructura Listado 7.7. Cmo incluir un indizador en una estructura class Listing7_7 { struct MyStruct ( public string []data; public string this [int index] { get { return data[index]; } set { data[index] = value; } } } public static void Main() { int x ; MyStruct ms = new MyStruct(); ms.data = new string[5]; ms[0] = "Brian D Patterson"; ms[1] = "Aimee J Patterson"; ms[2] = "Breanna C Mounts"; ms[3] = "Haileigh E Mounts"; ms[4] = "Brian W Patterson";
180
Como se puede ver, este ejemplo crea un nuevo objeto M y S t r u c t y asigna a los miembros data el valor 5, lo que indica que se usan cinco copias de esta estructura. Para hacer referencia a cada copia de esta estructura se usa un nmero indizador (de 0 a 5) y se almacenan los nombres dentro de la estructura. Para asegurarse de que todos lo datos permanecen intactos, se aplica un simple bucle a los posibles nmeros de ndice y se escribe el resultado en la consola. En la figura 7.4 se ilustra el resultado del listado 7.7.
c:\ C:\WINDOWS\System32\cmd.exe C:\>Listing7-7.exe Brian D P a t t e r s o n Aimee J P a t t e r s o n B r e a n n a C Mounts H a i l e i g h E Mounts Brian W Patterson C:\>_
Figura 7.4. Para devolver fcilmente los datos se incluye un indizador dentro de la estructura
Un indizador en una estructura puede ser muy til cuando se trabaja con grandes cantidades de datos del mismo tipo. Por ejemplo, si se va a leer la informacin de una direccin desde una base de datos, ste es un excelente lugar para almacenarla. Todos los campos se mantienen mientras se proporciona un mecanismo para acceder fcilmente a cada dato de los registros.
181
Tambin se puede incluir una interfaz dentro de una estructura. El listado 7.8 muestra cmo implementar correctamente una interfaz.
Listado 7.8. Cmo implementar una interfaz en una estructura class Listing7_8 { interface IInterface { void Method(); } struct MyStruct : IInterface { public void Method() { System.Console.WriteLine("Structure } }
Method");
Este cdigo crea una interfaz llamada I I n t e r f a c e . Esta interfaz contiene la definicin de un mtodo llamado 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 mtodo, que simplemente escribe una lnea de texto en la consola, se incluye en la estructura. En la figura 7.5 se ilustra el resultado del programa. Para demostrar lo importante que es la interfaz, si elimina las cuatro lneas que componen el mtodo Method en la estructura M y S t r u c t y vuelve a compilar el programa, obtendr el siguiente mensaje de error:
Class1.cs(8, 9 ) : error CS0535: 'Listing7_8.MyStruct' no implementa el miembro de interfaz Listing7_8.IInterface.Method()'
El compilador de C# determin que no implementamos todos los mtodos estipulados por la interfaz. Como no se implement el mtodo correcto, el programa no se pudo compilar, indicando de esta forma que no se cumplen todas las reglas.
182
tabla 7.1 enumera las palabras clave con valor variable y los nombres de las estructuras .NET que actualmente los implementan.
c:\ C:\WINDOWS\System32\cmd.exe C:\>Listing7-8.exe Structure Method C:\>
Figura 7.5. Cmo implementar un interfaz en una estructura Tabla 7.1. Nombres de estructuras .NET para tipos de valor
Palabra clave de C#
sbyte byte short ushort int uint long ulong char float double bool decimal
Este esquema es parte de lo que hace que el cdigo C# sea compatible con otros lenguajes .NET. Los valores C# se asignan a las estructuras .NET que pue-
183
den usar cualquier lenguaje .NET, porque el CLR puede usar cualquier estructura .NET. La asignacin de las palabras clave de C# con estructuras .NET tambin permite que las estructuras usen tcnicas como la sobrecarga de operadores para definir el comportamiento del valor cuando se usan en una expresin con un operador. Se analizar la sobrecarga de operadores cuando se traten las clases C#. Si lo desea, puede usar los nombres reales de estructuras .NET en lugar de las palabras clave de C#. El listado 7.9 muestra el aspecto que tendra el listado 7.5 si se escribiera usando los nombres de estructuras .NET.
Listado 7.9. Cmo usar los nombres de tipos de estructuras .NET class Listing7_9 { struct Point { public System.Int32 X ; public System.Int32 Y; public Point(System.Int32 InitialX, { X = InitialX; Y = InitialY; } public System.Boolean IsAtOrigin() { if((X == 0) && (Y == 0)) return true; else return false; } } public static void Main() { Point MyFirstPoint = new Point (100, 200); Point MySecondPoint = new Point(); if (MyFirstPoint.IsAtOrigin() == true) System.Console.WriteLine("MyFirstPoint is at the origin."); else System.Console.WriteLine("MyFirstPoint is not at the origin." ) ; if(MySecondPoint.IsAtOrigin() == true) System.Console.WriteLine("MySecondPoint is at the origin.") ; else System.Console.WriteLine("MySecondPoint is not at the origin."); } } System.Int32 InitialY)
184
Resumen
Las estructuras permiten agrupar un conjunto de variables con un solo nombre. Las variables pueden declararse usando el identificador de estructura. Las estructuras se declaran usando la palabra clave de C# struct . Todas las estructuras de C# tienen un nombre y una lista de datos miembros. C# no pone ningn lmite al nmero de datos miembros que puede contener una estructura. Se accede a los miembros de estructuras mediante la notacin S t r u c t N a m e . MemberName. Los miembros se pueden usar en cualquier parte donde est permitido usar su tipo de datos, incluyendo expresiones y parmetros de mtodos. Las estructuras pueden implementar tanto mtodos como variables. Los miembros de estructuras se invocan usando la notacin S t r u c t N a m e . M e t h o d N a m e y se usan igual que los nombres de mtodos de clase. Las estructuras tambin implementan mtodos especiales llamados constructores, que inicializan la estructura con un estado conocido antes de usar dicha estructura. Los tipos de valores de C# son asignados a estructuras definidas por el CLR de .NET. Esto es lo que permite que otros cdigos de .NET usen los datos. Todas las variables son compatibles con el CLR de .NET porque las variables se definen usando estructuras compatibles con el CLR
185
Parte II
187
189
En los aos 90, la programacin procedural dio paso a lenguajes como Smalltalk y Simula, que introdujeron el concepto de objeto. Los inventores de estos lenguajes se dieron cuenta de que el ser humano no expresa ideas en trminos de bloques de cdigo que actan sobre un grupo de variables; en su lugar, expresan ideas en trminos de objetos. Los objetos son entidades que tienen un conjunto de valores definidos (el estado del objeto) y un conjunto de operaciones que pueden ejecutarse sobre ese objeto (los comportamientos del objeto). Por ejemplo, imagine un cohete espacial. Un cohete espacial tiene estados, como la cantidad de combustible o el nmero de pasajeros a bordo, y comportamientos, como "despegar" y "aterrizar". Adems, 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 programacin procedural son visibles todos los procedimientos ni todas las variables. Igual que en C#, los lenguajes procedurales tienen reglas de mbito que controlan la visibilidad del cdigo y de los datos. Por lo general podemos hacer visibles los procedimientos y los datos (a los que nos referiremos en este captulo como elementos) en el procedimiento, archivo fuente, aplicacin o nivel externo. El nombre de cada mbito es autoexplicativo. Un elemento visible en el nivel de procedimiento slo 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 aplicacin, el elemento es visible desde cualquier parte de cdigo en la misma aplicacin. En el nivel externo, el elemento es visible desde cualquier parte de cdigo en cualquier aplicacin. El punto principal es que, en programacin procedural, la interaccin entre datos y cdigo est controlada por los detalles de implementacin, 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 proteccin suele acarrear fallos difciles 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 cmo lo har usando un vocabulario familiar a los usuarios sin preparacin tcnica. El software se estructura usando 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, durante el diseo de software ATM, se usan clases como CuentaBancaria , Cliente , Presentacin y similares. Esto reduce el trabajo necesario para traducir una situacin del mundo real al modelo de software y facilita la comunicacin con la gente ajena al software y que est interesada en el producto final. Este modo ms sencillo de disear software ha conducido a la aparicin de un estndar para describir el diseo del software orientado a objetos. Este lenguaje es el Lenguaje unificado de modelado o UML. La segunda ventaja del software orientado a objetos se demuestra durante la implementacin. El hecho de que ahora podemos tener mbitos de nivel de clases, permite ocultar variables en las definiciones de clase. Cada objeto tendr su propio conjunto de variables y estas variables por lo general solamente sern accesibles mediante las operaciones definidas por la clase. Por ejemplo, las variables que contienen un estado del objeto CuentaBancaria slo sern accesibles llamando a las operaciones R e t i r a d a ( ) o D e p s i t o ( ) asociadas a ese objeto. Un objeto CuentaBancaria (o cualquier otro objeto) no tiene acceso a otro estado privado del objeto CuentaBancaria, como el balance. A este principio se le llama encapsulacin. El desarrollo de software orientado a objetos se fue haciendo ms popular a medida que los programadores adoptaban este nuevo modo de disear software. C# es un lenguaje orientado a objetos y su diseo garantiza que los programadores de C# sigan los conceptos correctos de la programacin orientada 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 lenguajes, como C y Pascal, se les llama lenguajes procedurales o no orientados a objetos porque no disponen de compatibilidad integrada que permita crear objetos. Existe un tercer tipo de lenguajes hbridos, como C++, en los que se puede elegir si usar o no objetos. Bjarne Stroustrup, el inventor de C++, decidi no obligar a los programadores de C++ a usar objetos porque C++ tambin era una versin mejorada del lenguaje de programacin C. La compatibilidad con el cdigo C existente ayudaba a que C++ se convirtiera en un lenguaje importante. En este captulo se estudian los conceptos que componen un lenguaje orientado a objetos, empezando por sus componentes esenciales (las clases y objetos), hasta los trminos ms avanzados (abstraccin, tipos de datos abstractos, encapsulacin, herencia y polimorfismo). Se tratarn los conceptos bsicos y se procurar evitar los detalles especficos sobre cmo estn implementados estos conceptos en C#. Estos detalles especficos se tratarn en captulos posteriores.
191
Clases y objetos
En primer lugar, esta seccin vuelve a tratar la diferencia entre un objeto y una clase. Este libro usa mucho estos dos trminos y es importante distinguir entre ellos. Una clase es una coleccin de cdigo y de variables. Las clases gestionan el estado, en forma de las variables que contienen, y comportamientos, en forma de los mtodos que contienen. Sin embargo, una clase slo es una plantilla. Nunca se crea una clase en el cdigo. En su lugar, se crean objetos. Por ejemplo. CuentaBancaria es una clase con una variable que contiene el saldo de la cuenta y los mtodos Retirada(), Depsito() y MostrarSaldo(). 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 propio estado. El estado de un objeto puede ser diferente del estado de otro objeto de la misma clase. Imagine una clase que define a una persona. Una clase persona va a tener estados (quizs, una cadena que representa el nombre propio de la persona) y comportamientos, mediante mtodos como IrATrabajar() , Comer() e IrADormir() . Se pueden crear dos objetos de la clase persona, cada uno con un estado diferente, como se muestra en la figura 8.1. La figura 8.1 muestra la clase persona y dos objetos persona: uno con el nombre propio "Alice" y otro con el nombre propio "Bob". El estado de cada objeto se almacena en un conjunto de variables diferentes. Vuelva a leer la frase anterior. Esta frase contiene un punto esencial para comprender el funcionamiento de la programacin orientada a objetos. Un lenguaje admite objetos cuando no se necesita crear un cdigo especial para disponer de un conjunto de variables cada vez que se crea un objeto diferente. NOTA: Si un lenguaje admite la gestin automtica del estado dentro de objetos pero carece de las otras caractersticas descritas en esta seccin, suele recibir el nombre de lenguaje basado en objetos. Visual Basic 6 admite objetos, pero no admite la herencia de implementacin; por tanto, no se le considerar como un autntico lenguaje orientado a objetos. Autnticos lenguajes orientados a objetos son SmallTalk, Java y C#.
192
Las siguientes secciones definen cada uno de estos trminos con detalle.
Person +FirstName : String +GoToWork() : void +Eat() : void +GoToSleep() : void
Abstraccin
Es importante darse cuenta de que el objetivo de la programacin no es reproducir todos los aspectos posibles del mundo real de un concepto dado. Por ejemplo, cuando se programa una clase Person , no se intenta modelar todo lo que se conoce sobre una persona. En su lugar, se trabaja dentro del contexto de la aplicacin especfica. Slo se modelan los elementos que son necesarios para esa aplicacin. Algunas caractersticas de una persona, como la nacionalidad, pueden existir en el mundo real, pero se omiten si no son necesarias para la aplicacin en cuestin. Una persona en una aplicacin bancaria estar interesada en aspectos diferentes de los de, por ejemplo, una persona en un juego de accin. Este concepto recibe el nombre de abstraccin y es una tcnica necesaria para el manejo de los conceptos infinitamente complejos del mundo real. Por tanto, cuando se haga preguntas sobre objetos y clases, tenga siempre en cuenta que debe hacerse estas preguntas en el contexto de una aplicacin especfica.
193
bles independientes. El mundo real est compuesto de conjuntos de datos relacionados. El estado de una persona para una aplicacin determinada puede consistir en, por ejemplo, nombre, apellidos y edad. Cuando se quiere crear una persona en un programa, lo que se quiere crear es un conjunto de estas variables. Un tipo abstracto de datos permite presentar tres variables (dos cadenas y un nmero entero) como una unidad y trabajar cmodamente con esta unidad para contener el estado de una persona, como se ve en este ejemplo:
struct Person { public String FirstName; public String LastName; public int Age; }
Cuando se asigna un tipo de datos a una variable del cdigo, se puede usar un tipo de datos primitivo o un tipo de datos abstracto. Los tipos de datos primitivos son tipos que C# reconoce en cuanto se instala. Tipos como i n t , l o n g , c h a r y string son tipos de datos primitivos de C#. Los tipos de datos abstractos son tipos que C# no admite cuando se instala. Antes de poder usar un tipo de datos abstracto hay que declararlo en el cdigo. Los tipos de datos abstractos se definen en el cdigo en lugar de hacerlo en el compilador de C#. Por ejemplo, imagine la estructura (o clase) P e r s o n . Si escribe cdigo C# que usa una estructura (o clase) P e r s o n sin escribir cdigo que le indique al compilador de C# a qu se parece una estructura (o clase) P e r s o n y cmo se comporta, se obtiene un error de compilacin. Observe el siguiente cdigo:
class MyClass { static void Main() { Person MyPerson; Person.FirstName = "Malgoska"; } }
El problema de este cdigo es que el tipo de datos P e r s o n no es un tipo de datos primitivo y no est definido por el lenguaje C#. Como no es un tipo primitivo, el compilador de C# considera que se trata de un tipo de datos abstracto y revisa el cdigo buscando la declaracin de un tipo de datos P e r s o n . Sin embargo, como el compilador de C# no puede encontrar informacin sobre el tipo abstrac-
194
to de datos P e r s o n genera un error. Tras definir un tipo abstracto de datos, puede usarse en el cdigo C# exactamente igual que si fuera un tipo de datos primitivo. Las estructuras y las clases de C# son ejemplos de tipos abstractos de datos. Una vez que se ha definido una estructura (o clase), se pueden usar las variables de ese tipo dentro de otra estructura (o clase). La estructura LearningUnit, por ejemplo, contiene dos variables Person:
struct LearningUnit { public Person Tutor; public Person Student; }
Encapsulacin
Mediante la encapsulacin, los datos se ocultan, o se encapsulan, dentro de una clase y la clase implementa un diseo que permite que otras partes del cdigo accedan a esos datos de forma eficiente. Imagine la encapsulacin como un envoltorio protector que rodea a los datos de las clases de C#. Cmo ejemplo de encapsulacin, observe la estructura P o i n t con la que trabaj en un captulo anterior.
struct Point { public int X; public int Y; }
Los datos miembros de esta estructura son pblicos, lo que permite que cualquier parte de cdigo que acceda a la estructura acceda tambin a los datos miembros. Como cualquier parte de cdigo puede acceder a los datos miembros, el cdigo puede asignar a los valores de datos miembros cualquier valor que pueda representarse en un valor int. No obstante, puede surgir un problema al permitir que los clientes asignen los valores de los datos miembros directamente. Supongamos que se usa la estructura P o i n t para representar una pantalla de ordenador con una resolucin de 800 x 600. En ese caso, slo tiene sentido permitir al cdigo que asigne a X valores entre 0 y 800, y a Y valores entre 0 y 600. No obstante, con el acceso pblico a los datos m i e m b r o s , no hay nada que impida al cdigo asignar a X el valor 32.000 y a Y el valor 38.000. El compilador de C# lo permite porque esos valores son posibles en un entero. El problema es que no tiene sentido permitir valores tan elevados. La encapsulacin resuelve este problema. Bsicamente, la solucin est en marcar los datos miembros como privados, para que el cdigo no pueda acceder a los datos miembros directamente. A continuacin puede escribir mtodos en una clase de punto como SetX() y S e t Y ( ) . Los mtodos S e t X ( ) y SetY()
195
pueden asignar los valores y tambin pueden contener cdigo que genere un error si se trata de llamar a S e t X ( ) o a S e t Y ( ) con parmetros con valores demasiado grandes. La figura 8.2 muestra el posible aspecto de una clase Point.
Point -X : int -Y : int +SetX() : bool +SetY() : bool +GetX() : int +GetY() : int
NOTA: El signo menos delante de los datos miembros es una notacin UML que indica que los miembros tienen una visibilidad privada. El signo ms delante de los datos miembros es una notacin UML que indica que los miembros tienen una visibilidad pblica. La tcnica consistente en marcar como privados los datos miembros resuelve el problema de que el cdigo establezca sus valores directamente. Si los datos miembros son privados, slo la propia clase puede verlos y cualquier otro cdigo que intente acceder a los datos miembros genera un error del compilador. En lugar de acceder directamente a los datos miembros, la clase declara mtodos pblicos llamados S e t X ( ) y S e t Y ( ) . El cdigo que quiera asignar los valores de los puntos X e Y llama a estos mtodos pblicos. Estos mtodos pueden aceptar el nuevo valor de las coordenadas y un parmetro, pero tambin pueden comprobar los nuevos valores para asegurarse de que estn dentro de los lmites adecuados. Si el nuevo valor est fuera de los lmites, el mtodo devuelve un error. Si el nuevo valor est dentro de los lmites, el mtodo puede establecer un nuevo valor. El siguiente pseudocdigo muestra cmo se puede implementar el mtodo SetX():
bool SetX(int NewXValue) { if (NewXValue is out of range) return false; X = NewXValue; return true; }
Este cdigo ha encapsulado el dato miembro de la coordenada X y permite a los invocadores asignar su valor a la vez que impide que le asignen un valor no vlido. Cmo los valores de las coordenadas X e Y en este diseo son privados, las otras partes de cdigo no pueden examinar sus valores actuales. La accesibilidad
196
privada en la programacin orientada a objetos evita que las partes que realizan la llamada lean el valor actual o guarden el nuevo valor. Para exponer estas variables privadas, se pueden implementar mtodos como G e t X ( ) y G e t Y ( ) que devuelven el valor actual de las coordenadas. En este diseo, la clase encapsula los valores de X e Y aunque permite que otras partes del cdigo lean y escriban sus valores. La encapsulacin proporciona otra ventaja: el mtodo que realiza el acceso impide que se asignen valores absurdos a los datos miembros.
Herencia
Algunas clases, como la clase P o i n t , se disean partiendo de cero. El estado y los comportamientos de la clase se definen en ella misma. Sin embargo, otras clases toman prestada su definicin de otra clase. En lugar de escribir otra clase de nuevo, se pueden tomar el estado y los comportamientos de otra clase y usarlos como punto de partida para la nueva clase. A la accin de definir una clase usando otra clase como punto de partida se le llama herencia.
Herencia simple
Supongamos que estamos escribiendo cdigo usando la clase P o i n t y nos damos cuenta de que necesitamos trabajar con puntos tridimensionales en el mismo cdigo. La clase P o i n t que ya hemos definido modela un punto en dos dimensiones y no podemos usarlo para definir un punto tridimensional. Decidimos que hace falta escribir un punto tridimensional llamado P o i n t 3 D . Se puede disear la clase de una de estas dos formas: Se puede escribir la clase Point3D partiendo de cero, definiendo datos miembros llamados X, Y y Z, y escribiendo mtodos que lean y escriban los datos miembros. Se puede derivar de la clase Point, que ya implementa los miembros X e Y. Heredar de la clase P o i n t proporciona todo lo necesario para trabajar con los miembros X e Y, por lo que todo lo que hay que hacer en la clase P o i n t 3 D es implementar el miembro Z. La figura 8.3 muestra el aspecto que podra tener la clase Point3D en UML. NOTA: La notacin UML que indica que los miembros tienen visibilidad protegida es el smbolo de la libra delante de los datos miembros. Visibilidad protegida significa que la visibilidad es pblica para las clases derivadas y privadas para todas las dems 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 de herencia tambin es conocido
197
como derivar una clase de otra. Parte del estado y del comportamiento de la clase Point3D se derivan de la clase Point. En este caso, la clase usada como punto de partida recibe el nombre de clase base y la nueva clase es la clase derivada. En la figura 8.3 la clase base es Point y la clase derivada es Point3D.
Point #X : int #Y : int +SetX() : bool +SetY() : bool +GetX() : int +GetY() : int
Figura 8.3. La clase Point3D hereda los mtodos y las variables de la clase Point.
Derivar una clase de otra permite que automticamente los datos miembro pblicos (y protegidos) y los mtodos pblicos (y protegidos) de la clase base estn disponibles para la clase derivada. Como los mtodos GetX(), G e t Y ( ) , S e t X ( ) y S e t Y ( ) estn marcados como pblicos en la clase base, estn automticamente disponibles para las clases derivadas. Esto significa que la clase P o i n t 3 D dispone de los mtodos pblicos G e t X ( ) , G e t Y ( ) , S e t X ( ) y S e t Y ( ) , ya que se derivaron de la clase base P o i n t . Una seccin de cdigo puede crear un objeto de tipo P o i n t 3 D y llamar a los mtodos GetX(), GetY(), S e t X ( ) y S e t Y ( ) , aunque los mtodos no estn implementados explcitamente en esa clase. Se derivaron de la clase base P o i n t y pueden ser usados en la clase derivada Point3D.
Herencia mltiple
Algunos lenguajes orientados a objetos tambin permiten la herencias mltiple, lo que permite que una clase se derive de ms de una clase base. C# slo permite herencias simples. Esta restriccin se debe a que el CLR de .NET no admite clases con varias clases base, principalmente porque otros lenguajes .NET, como Visual Basic, no admiten por s mismos herencia mltiple. Los lenguajes que admiten herencia mltiple, como C++, tambin han evidenciado la dificultad de usar correctamente la herencia mltiple.
198
NOTA: Si se quiere usar la herencia simple (por ejemplo, para que una clase RadioReloj herede las clases Despertador y Radio, se puede obtener un comportamiento muy parecido al deseado mediante la contencin. La contencin incrusta una variable miembro de la clase de la que se quiere derivar. En este caso, esta tcnica solicita aadir una variable Despertador y una variable Radio para la clase RadioReloj y delega la funcionalidad en la variable miembro adecuada. Esta tcnica tambin se puede utilizar en herencias simples, pero es el nico medio que hay para simular herencia mltiple en .NET (a menos que Microsoft nos sorprenda aadiendo herencia mltiple en una versin posterior).
Polimorfismo
La herencia permite que una clase derivada redefina el comportamiento especificado para una clase base. Supongamos que creamos una clase base A n i m a l . Hacemos esta clase base abstracta porque queremos codificar animales genricos siempre que sea posible, pero tambin crear animales especficos, como un gato y un perro. El siguiente fragmento de cdigo muestra cmo declarar la clase con su mtodo abstracto.
Abstract class Animal { public abstract void MakeNoise(); }
Ahora se pueden derivar animales especficos, como C a t y Dog, a partir de la clase base abstracta A n i m a l :
class Cat : Animal { public override void MakeNoise() { Console.WriteLine("Meow!"); } } class Dog : Animal { public override void MakeNoise() { Console.WriteLine("Woof!"); } }
Observe que cada clase tiene su propia implementacin del mtodo M a k e N o i s e ( ) . Ahora la situacin es la indicada para el polimorfismo. Como se aprecia en la figura 8.4, tenemos una clase base con un mtodo que est anula-
199
do en dos (o ms) clases derivadas. El polimorfismo es la capacidad que tiene un lenguaje orientado a objetos de llamar correctamente al mtodo sobrescrito en funcin de qu clase lo est llamando. Esto suele producirse cuando se almacena una coleccin de objetos derivados. El siguiente fragmento de cdigo crea una coleccin de animales, que recibe el apropiado nombre de zoo. A continuacin aade un perro y dos gatos a este zoo.
ArrayList Zoo; Zoo = new ArrayList(3); Cat Sasha, Koshka; Sasha = new Cat(); Koshka = new Cat(); Dog Milou; Milou = new Dog();
Figura 8.4. Este ejemplo de herencia muestra una clase base y dos clases derivadas.
La coleccin zoo es una coleccin polimrfica porque todas sus clases derivan de la clase abstracta A n i m a l . Ahora se puede iterar la coleccin y hacer que cada animal emita el sonido correcto:
foreach (Animal a in Zoo) { a.MakeNoise(); }
200
Qu est pasando? Como C# permite el polimorfismo, en tiempo de ejecucin el programa es lo suficientemente inteligente como para llamar a la versin perro de M a k e N o i s e cuando se obtiene un perro del zoo y a la versin gato de MakeNoise cuando se obtiene un gato del zoo. El listado 8.1 muestra el cdigo C# completo que explica el polimorfismo.
Listado 8.1. Las clases Cat y Dog evidencia el polimorfismo using System; using System.Collections; namespace PolyMorphism { abstract class Animal { public abstract void MakeNoise(); } class Cat : Animal { public override void MakeNoise() { Console.WriteLine("Meow!"); } } class Dog : Animal { public override void MakeNoise() { Console.WriteLine("Woof!"); } } class PolyMorphism { static int Main(string[] args) { ArrayList Zoo; Zoo = new ArrayList(3); Cat Sasha, Koshka; Sasha = new Cat(); Koshka = new Cat(); Dog Milou; Milou = new Dog(); Zoo.Add(Milou); Zoo.Add(Koshka); Zoo.Add(Sasha);
201
foreach (Animal a in Zoo) { a.MakeNoise(); } // espera a que el usuario acepte los resultados Console.WriteLine("Hit Enter to terminate..."); Console.Read(); return 0; } } }
Resumen
C# es un lenguaje orientado a objetos y los conceptos que se usan en los lenguajes orientados a objetos tambin se aplican a C#. Los tipos abstractos de datos son tipos de datos que se definen en el propio cdigo. Los tipos abstractos de datos no forman parte de C#, a diferencia de los tipos de datos primitivos. Se pueden usar tipos abstractos de datos en el cdigo de la misma forma que los tipos de datos primitivos, pero slo despus de haberlos definido en el cdigo. La encapsulacin es el acto de disear clases que proporcionen un conjunto completo de funcionalidad a los datos de las clases sin exponer directamente esos datos. Si se quiere escribir cdigo que evite que otras partes del cdigo proporcionen valores no vlidos a la clase, la encapsulacin permite "ocultar" los datos miembro al hacerlos privados, creando mtodos que accedan a ellos, los cules a su vez se declaran como mtodos pblicos. Otros fragmentos de cdigo pueden llamar a los mtodos, que pueden comprobar los valores y emitir un error si los valores no son apropiados. La herencia permite definir una clase por medio de otra. Las clases derivadas heredan de la clase base. Las clases derivadas automticamente heredan el estado y los comportamientos de la clase base. Se puede usar la herencia para aadir nuevas funciones a clases ya existentes sin necesidad de rescribir desde cero una clase completamente nueva. Algunos lenguajes orientados a objetos permiten tanto herencias simples como herencias mltiples, aunque C# slo permite la herencia simple. El polimorfismo permite tratar de forma uniforme a una coleccin de clases derivadas de una sola clase base. Las clases derivadas se recuperan como una clase base y C# automticamente llama al mtodo correcto en la clase derivada. Este captulo trata los conceptos orientados a objetos en general, sin estudiar cmo se usan estos conceptos en el cdigo C#. El resto de los captulos de esta parte del libro explican cmo se implementan estos conceptos en C#.
202
9 Clases
de C#
El diseo de C# est muy influido por los conceptos del desarrollo de software orientado a objetos. Como la clase es la parte fundamental del software orientado a objetos, no es de sorprender que las clases sean el concepto ms importante de C#. Las clases en el mundo del desarrollo de software orientado a objetos contienen cdigo y datos. En C#, los datos se implementan usando datos miembros y el cdigo se implementa usando mtodos. Los datos miembros son cualquier elemento al que se pueda pasar datos dentro y fuera de la clase. Los dos principales tipos de datos miembros son los campos y las propiedades. En el cdigo C# se pueden escribir tantos datos miembros y mtodos como se desee, pero todos deben incluirse en una o varias clases. El compilador de C# emite un error si se intenta definir alguna variable o se implementa algn mtodo fuera de una definicin de clase. Este captulo trata de los fundamentos de la construccin de clases. Explica cmo crear constructores y destructores, aadir mtodos y miembros, adems de cmo usar las clases despus de crearlos.
205
C# class y no con struct. Aunque ya hemos visto una definicin de clase en anteriores captulos, vamos a revisar el diseo de una declaracin de clase: Modificadores de clase opcionales La palabra clave class Un identificador que da nombre a la clase Informacin opcional de la clase base El cuerpo de la clase Un punto y coma opcional
Las llaves delimitan el cuerpo de la clase. Los mtodos y variables de clase se colocan entre las llaves.
El mtodo Main
Cada aplicacin de C# debe contener un mtodo llamado Main. Esto es el punto de entrada para que la aplicacin se ejecute. Se puede colocar este mtodo dentro de cualquier clase del proyecto, porque el compilador es lo bastante inteligente como para buscarlo cuando lo necesite. El mtodo Main debe cumplir dos requisitos especiales para que la aplicacin funcione correctamente. En primer lugar, el mtodo debe declararse como public . Esto asegura que se pueda acceder al mtodo. En segundo lugar, el mtodo debe declararse como static . La palabra clave static asegura que slo se puede abrir una copia del mtodo a la vez. Teniendo en cuenta estas reglas, observemos este cdigo:
class Class1 { public static void Main() { } }
Como puede apreciarse, este ejemplo contiene una clase llamada C l a s s 1 . Esta clase contiene el mtodo Main de la aplicacin. Es en este mtodo Main donde se coloca todo el cdigo necesario para ejecutar su aplicacin. Aunque es correcto colocar este mtodo en la misma clase y el mismo archivo que el resto del cdigo de la aplicacin, conviene crear una clase y un archivo separados para el
206
mtodo Main. Esta operacin sirve de ayuda a los dems programadores que quieran trabajar con este cdigo.
Aqu, el mtodo Main est contenido en una clase tpica. Observe que el mtodo Main tiene un parmetro definido, una matriz de cadenas que ser almacenada en la variable a r g s . Usa el comando f o r e a c h para recorrer todas las cadenas almacenadas de la matriz a r g s y a continuacin se escriben esas cadenas en la consola. NOTA: Si se est usando Visual Studio .NET para programar C#, la matriz de cadenas se aade automticamente cuando se crea una aplicacin de consola. Si se ejecuta la aplicacin anterior sin parmetros, no ocurrir nada. Si se ejecuta una aplicacin como la siguiente:
Sampleap.exe parameter1 parameter2
Los argumentos de lnea de comando son un modo excelente de proporcionar modificadores a la aplicacin; por ejemplo, para activar un archivo de registro mientras se ejecuta la aplicacin.
207
de comandos por lotes, si el programa se ejecut con xito o fall. Para ello, basta con asignar al mtodo Main un valor de devolucin i n t en lugar de v o i d . Cuando el mtodo Main est listo para finalizar, slo hay que usar la palabra clave r e t u r n y un valor que devolver, como se puede apreciar en el siguiente ejemplo:
class Class1 { public static int Main(string[] args) { if (args[0] == "fail") return 1; return 0; } }
Esta aplicacin acepta un parmetro f a i l o cualquier otra palabra (quizs. s u c c e s s ) . Si la palabra f a i l se pasa a esta aplicacin como parmetro, el programa devuelve el valor 1, lo que indica un fallo en el programa. En caso contrario, el programa devuelve 0, lo que indica que el programa finaliz con normalidad. Se puede comprobar el funcionamiento del programa simplemente haciendo que un archivo por lotes ejecute la aplicacin y que luego realice algunas acciones dependiendo del cdigo devuelto. El siguiente cdigo es un simple archivo por lotes que realiza esta funcin:
@echo off retval.exe success
goto answer'errorlevel' :answer0 echo Program had return code 0 goto end :answer1 echo Program had return code 1 goto end :end echo Done!
(Success)
(Failure)
En el cdigo anterior se puede apreciar que se llama al programa con un parmetro s u c c e s s . Al ejecutar este programa por lotes, aparece el siguiente mensaje:
Program had return code 0 (Success)
Si se edita este programa por lotes para que pase la palabra f a i l como parmetro, entonces aparecer un mensaje que confirma que el programa termin con un cdigo de salida 1.
208
El cuerpo de la clase
El cuerpo de la clase puede incluir instrucciones cuya funcin se incluya en una de estas categoras. Constantes Campos Mtodos Propiedades Eventos Indizadores Operadores Constructores Destructores Tipos
El compilador de C# acepta perfectamente este cdigo, pero puede ser un poco difcil de leer, especialmente para alguien que lea cdigo por primera 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 cdigo:
if(Ratio == Pi) System.Console.WriteLine("Shape is a circle");
El uso de constantes presenta otras ventajas: Se les da valor a las constantes cuando se las declara y se usa su nombre, no su valor, en el cdigo. Si por alguna razn es necesario cambiar el valor de la constante, slo hace falta cambiarlo en un sitio: donde se declara la constante. Si se escribe en las instrucciones de C# el valor real, un cambio en el valor significara que habra que revisar todo el cdigo y cambiar ese valor.
209
Las constantes indican especficamente al compilador de C# que, a diferencia de las variables normales, el valor de la constante no puede cambiar. El compilador de C# emite un error si algn cdigo intenta cambiar el valor de una constante. La estructura de la declaracin de una constante es la siguiente: La palabra clave const Un tipo de dato para la constante Un identificador Un signo igual El valor de la constante
NOTA: Este captulo usa la palabra clave public para indicar que los elementos del cuerpo de clase estn disponibles para el resto del cdigo de la aplicacin. Posteriormente se examinan algunas alternativas a la palabra clave public que pueden usarse cuando se hereda una clase de otra. C# permite colocar varias definiciones de constantes en la misma lnea, siempre que todas las constantes tengan el mismo tipo. Se pueden separar las definiciones de constantes con una coma, como en el siguiente ejemplo:
class MyClass { public const public { } }
210
estado de la clase. Se puede acceder a los campos definidos en una clase mediante cualquier mtodo definido en la misma clase. Un campo se define exactamente igual que cualquier variable. Los campos tienen un tipo y un identificador. Tambin se puede definir un campo sin un valor inicial:
class MyClass { public int Field1; public static void Main() { } }
Para indicar que los campos son de slo lectura escriba antes de la declaracin la palabra clave readonly . Si usa la palabra clave readonly, deber escribirla justo antes del tipo del campo:
class MyClass { public readonly int Field1; public static void Main() { } }
Se puede establecer el valor de un campo de slo lectura al declarar el campo o en el constructor de la clase. Su valor no puede establecerse en ningn otro momento. Cualquier intento de cambiar el valor de un campo de slo lectura es detectado por el compilador de C# y devuelve un error:
error CS0191: No se puede asignar un campo de slo lectura (excepto en un constructor o inicializador de variable)
Los campos de slo 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 una constante y un campo de slo lectura es que el valor de las constantes debe establecerse en tiempo de compilacin; en otras palabras, deben recibir su valor
211
cuando se escribe el cdigo. Los campos de slo lectura permiten establecer el valor del campo en tiempo de ejecucin. Como se puede asignar el valor de un campo de slo lectura en un constructor de clase, se puede determinar su valor en tiempo de ejecucin y establecerlo cuando el cdigo se est ejecutando. Supongamos, por ejemplo, que estamos escribiendo una clase de C# que gestiona la lectura de un grupo de archivos de una base de datos. Quizs queramos que la clase publique un valor que especifique el nmero de archivos que hay en la base de datos. Una constante no es la eleccin correcta para este valor porque el valor de una constante debe establecerse cuando se escribe el cdigo, y no podemos saber cuntos archivos contiene la clase hasta que se ejecute el cdigo. Podemos poner estos datos en un campo cuyo valor pueda establecerse despus de que la clase haya empezado a ejecutarse y se pueda determinar el nmero de archivos. Como el nmero de archivos no cambia durante la existencia de la clase, quizs queramos sealar el campo como de slo lectura para que los otros fragmentos de cdigo no puedan cambiar su valor.
Al usar una estructura como sta, los clientes pueden usar la sintaxis structurename.field para trabajar con uno de los campos de la estructura:
Point MyPoint = new Point(); MyPoint.X = 100;
Los clientes pueden escribir fcilmente este cdigo. Los clientes pueden acceder al campo directamente y usar su valor en una expresin.
212
El problema de este enfoque es que los clientes pueden establecer un campo que C# permite, pero que no es lgico para el cdigo. Por ejemplo, si est gestionando una estructura P o i n t que describe un punto en una pantalla de 640 x 480 pxeles, el valor lgico ms alto para X debera ser 639 (suponiendo que los valores legales de X estn entre 0 y 639). Sin embargo, como se especifica que la coordenada X es un tipo i n t , los clientes pueden establecer como valor cualquier valor autorizado dentro de los lmites de los tipos enteros:
MyPoint.X = -500; MyPoint.X = 9000;
El compilador de C# acepta este cdigo porque los valores que se asignan a X estn dentro de los lmites vlidos para los valores enteros. Una solucin a este problema es hacer que los valores sean privados, con lo que seran inaccesibles para otros fragmentos de cdigo, y luego aadir un mtodo pblico a la estructura que establezca los valores de X:
struct Point { private int X; private int Y; public bool SetX(int NewXValue) { if((NewXValue < 0) || (NewXValue > 639)) return false; X = NewXValue; return true; } }
La ventaja de este enfoque es que obliga a los clientes que quieran asignar un valor a X a llamar al mtodo para realizar la tarea:
Point MyPoint = new Point(); MyPoint.SetX(100);
La ventaja del mtodo es que se puede escribir cdigo que valide el nuevo valor antes de que se almacene realmente en el campo, y el cdigo del mtodo puede rechazar el nuevo valor si, por lgica, no es adecuado. As pues, los clientes llaman al mtodo para establecer un nuevo valor. Aunque este enfoque funciona, llamar a un mtodo para asignar un valor requiere un poco ms de cdigo que asignar un valor directamente. Para el cdigo es ms natural asignar un valor a un campo que llamar a un mtodo para que lo asigne. En el mejor de los casos, querremos lo mejor de los dos elementos: que los clientes puedan leer y escribir directamente los valores de los campos usando
213
instrucciones de asignacin simples, pero tambin querremos que el cdigo intervenga por anticipado y haga todo lo necesario para obtener el valor de un campo o validar el nuevo valor antes de que se asigne. Afortunadamente, C# ofrece esta posibilidad con un concepto de clase llamado propiedad. Las propiedades son miembros identificados que proporcionan acceso al estado de un objeto. Las propiedades tienen un tipo y un identificador, y tienen uno o dos fragmentos de cdigo asociados a ellas: una base de cdigo get y una base de cdigo set. Estas bases de cdigo reciben el nombre de descriptores de acceso. Cuando un cliente accede a una propiedad se ejecuta el descriptor de acceso get de la propiedad. Cuando el cliente establece un nuevo valor para la propiedad, se ejecuta el descriptor de acceso set de la propiedad. Para mostrar cmo funcionan las propiedades, el listado 9.1 usa una clase P o i n t que expone los valores de X e Y como propiedades.
Listado 9.1. Valores Point como propiedades de clase class Point { private int XCoordinate; private int YCoordinate; public int X { get { return XCoordinate; } set { if((value >= 0) && (value < 640)) XCoordinate = value; } } public int Y { get { return YCoordinate; } set { if ((value >= 0) && (value < 480)) YCoordinate = value; } } public static void Main() { Point MyPoint = new Point();
214
MyPoint.X = 100; MyPoint.Y = 200; System.Console.WriteLine(MyPoint.X); System.Console.WriteLine(MyPoint.Y); MyPoint.X = 600; MyPoint.Y = 600; System.Console.WriteLine(MyPoint.X); System.Console.WriteLine(MyPoint.Y); } }
Este cdigo declara una clase P o i n t con los valores X e Y como propiedades. El mtodo M a i n ( ) crea un nuevo objeto de la clase P o i n t y accede a las propiedades de la clase. La clase P o i n t define dos campos privados que contienen los valores de las coordenadas del punto. Como estos campos son privados, el cdigo que se encuentra fuera de la clase P o i n t no puede acceder a sus valores. La clase tambin define dos propiedades pblicas que permiten que otros fragmentos de cdigo trabajen con los valores de las coordenadas del punto. Las propiedades son pblicas y pueden ser usadas por otras partes del cdigo. La propiedad X es la propiedad pblica que gestiona el valor del campo privado X C o o r d i n a t e , y la propiedad Y es la propiedad pblica que gestiona el valor del campo privado Y C o o r d i n a t e . Las dos propiedades tienen un descriptor de acceso g e t y otro s e t . La figura 9.1 muestra el resultado del listado 9.1.
C:\ C:\WINDOWS\System32\cmd.exe C:\>Listing9-1.exe 100 200 600 200 C:\>_
Figura 9.1. Las propiedades de clase ayudan a almacenar los valores del punto.
215
tual de X c o o r d i n a t e y el descriptor de acceso de la propiedad Y devuelve el valor actual de Ycoordinate. Cada descriptor de acceso g e t debe devolver un valor que coincida o pueda ser convertido implcitamente al tipo de la propiedad. Si el descriptor de acceso no devuelve un valor, el compilador de C# emite el siguiente mensaje de error:
error CS0161: 'MyClass.Property.get': no todas las rutas de cdigo devuelven un valor
El compilador de C# determina que la instruccin est asignando un nuevo valor a la propiedad X del objeto P o i n t . Ejecuta el descriptor de acceso set de la clase para realizar la asignacin. Como se est asignando a la propiedad el valor 100, el valor de la palabra clave value del descriptor de acceso set ser 100. Los descriptores de acceso s e t del listado 9.1 asignan el valor al campo correspondiente, pero slo si el valor est dentro de los lmites vlidos. En C# no est permitido devolver valores de los descriptores de acceso. NOTA: Como C# no permite que los descriptores de acceso set devuelvan un valor, no se puede devolver un valor que especifique si la asignacin fue o no satisfactoria. Sin embargo, se pueden usar excepciones para informar de cualquier error. Estas excepciones se estudian en un captulo posterior.
216
Si hace falta implementar una propiedad de slo escritura, se especifica una propiedad con un descriptor de acceso set pero sin descriptor de acceso get:
int X { set { if((value >= 0) && (value < 640)) XCoordinate = value; } }
217
Listado 9.2. Una clase con un conjunto de valores de cadena class Rainbow { public int GetNumberOfColors() { return 7; } public bool GetColor(int ColorIndex, out string ColorName) { bool ReturnValue; ReturnValue = true; switch(ColorIndex) { case 0: ColorName = "Red"; break; case 1: ColorName = "Orange"; break; case 2: ColorName = "Yellow"; break; case 3: ColorName = "Green"; break; case 4: ColorName = "Blue"; break; case 5: ColorName = "Indigo"; break; case 6: ColorName = "Violet"; break; default: ColorName = ""; ReturnValue = false; break; } return ReturnValue; } public static void Main() { int ColorCount; int ColorIndex; Rainbow MyRainbow = new Rainbow(); ColorCount = MyRainbow.GetNumberOfColors();
218
string ColorName; bool Success; for(ColorIndex = 0; ColorIndex < ColorCount; ColorIndex++) { Success = MyRainbow.GetColor(ColorIndex, out ColorName); if(Success == true) System.Console.WriteLine(ColorName); } } }
La clase Rainbow del listado 9.2 tiene dos mtodos pblicos: GetColorCount(), que devuelve el nmero de colores de la clase. GetColor(), que, basndose en un nmero de color, devuelve el nombre de uno de los colores de la clase.
El mtodo M a i n ( ) crea un nuevo objeto de la clase Rainbow y pide al objeto el nmero de colores que contiene. A continuacin se coloca en un bucle for y solicita el nombre de cada color. El nombre del color aparece en la consola. El resultado de esta aplicacin puede verse en la figura 9.2.
c:\ C:\WINDOWS\System32\cmd.exe C:\>Listinng9-2.exe Red Orange Yellow Green Blue Indigo Violet C:\>_
La clase mantiene una coleccin de valores del mismo tipo de valor, lo que es muy parecido a una matriz. De hecho, esta clase Rainbow tambin puede implementarse 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 recuperar un nombre de color en particular:
ColorName = Rainbow[Color Index];
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 para acceder al elemento de la clase, se usa un fragmento de cdigo llamado descriptor de acceso de indizador. Teniendo esto en cuenta, se puede rescribir la clase Rainbow para que permita a los elementos que la llaman acceder a los nombres de los colores usando corchetes en su sintaxis. Se elimina el mtodo G e t C o l o r ( ) y se sustituye por un indizador, y se sustituye el mtodo G e t C o l o r C o u n t ( ) por una propiedad de slo lectura llamada Count, como se muestra en el listado 9.3.
Listado 9.3. Clase Rainbow con un indizador class Rainbow { public int Count { get { return 7; } } public string this[int ColorIndex] { get { switch(ColorIndex) { case 0: return "Red"; break; case 1: return "Orange"; break; case 2: return "Yellow"; break; case 3: return "Green"; break; case 4: return "Blue"; break; case 5: return "Indigo"; break; case 6: return "Violet"; break; default: return ""; } } } public static void Main() { int ColorIndex;
220
Rainbow MyRainbow = new Rainbow(); string ColorName; for (ColorIndex = 0; ColorIndex < MyRainbow.Count; ColorIndex++) { ColorName = MyRainbow[ColorIndex] ; System.Console.WriteLine(ColorName); } } }
Los indizadores se parecen a las propiedades porque tienen descriptores de acceso get y set. Si fuera necesario, se puede omitir cualquiera de estos descriptores de acceso. El indizador del listado 9.3 no tiene el descriptor de acceso set, lo que significa que la clase se puede leer, pero no se puede escribir en ella. Los indizadores se estructuran segn los siguientes elementos: Un tipo que indica el tipo de datos que devuelve el descriptor de acceso. La palabra clave this. Un corchete de apertura. Una lista de parmetros, estructurada igual que una lista de parmetros de un mtodo. Un corchete de cierre. Un cuerpo de cdigo, entre llaves.
El indizador del listado 9.4 recibe un argumento entero y devuelve una cadena, de forma parecida al mtodo GetColor() del listado 9.2.
Listado 9.4. Un argumento entero que devuelve una cadena string ColorName; for (ColorIndex = 0; ColorIndex < MyRainbow.Count; ColorIndex++) { ColorName = MyRainbow[ColorIndex]; System.Console.WriteLine(ColorName); }
El nuevo cdigo usa los corchetes de la sintaxis del elemento de matriz para obtener el nombre de un color del objeto MyRainbow. Esto hace que el compilador de C# llame al cdigo indizador de la clase, que pasa el valor de C o l o r I n d e x como parmetro. El indizador devuelve una cadena y la cadena se escribe en la consola.
221
Una clase puede implementar ms de un indizador, siempre que los indizadores tengan diferentes listas de parmetros. Las listas de parmetros de los indizadores pueden tener ms de un parmetro y pueden ser de cualquier tipo que se pueda usar en el cdigo. No es necesario que los indizadores usen valores enteros como indizadores. Se podra haber implementado igualmente en la clase Rainbow un indizador que aceptase un valor double:
public string this[double ColorIndex]
NOTA: Como en las propiedades, C# no permite que los descriptores de acceso set devuelvan valores. Sin embargo, pueden usarse excepciones para informar de cualquier error.
public Point()
222
public static void Main() { Point MyFirstPoint = new Point(100, 200); Point MySecondPoint = new Point(); ( }
La clase P o i n t del listado 9.5 tiene dos campos pblicos: X e Y. Tambin implementa dos constructores. Uno de ellos no usa ningn parmetro y el otro usa dos parmetros. Los constructores de las clases de C# son muy parecidas a las estructuras 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 constructores de estructura y los constructores de clase estriba en que los constructores de clase pueden implementar un constructor sin parmetros, pero un constructor de estructura no puede hacerlo. Si se define una clase sin constructores, C# proporciona un constructor por defecto. Este constructor por defecto asigna a todos los campos de la clase sus valores por defecto. Si se inicializa con el signo igual cualquier campo de una clase, ste se inicializar antes de que se ejecute el constructor:
class Point { public int X = 100; public int Y; public Point(int InitialY) { Y = InitialY + X; } }
En esta clase P o i n t , una declaracin de asignacin inicializa el campo X, y el constructor inicializa el campo Y. Al compilar este cdigo, el compilador de C# se asegura de que el campo X se inicialice en primer lugar.
223
la clase. Puede pensar en los destructores como en lo contrario de los constructores: 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 para el programador. Los destructores son opcionales. Es perfectamente vlido escribir una clase de C# sin un destructor (y hasta ahora, es lo que hemos estado haciendo en los ejemplos). Si se escribe un destructor, slo se puede escribir uno. NOTA: A diferencia de los constructores, no se puede definir ms de un destructor para una clase. Los destructores se disponen de la siguiente forma: El smbolo virgulilla (~). El identificador del destructor, que debe corresponder al nombre de la clase. Un conjunto de parntesis.
El listado 9.6 actualiza la clase Point del listado 9.5 con un destructor:
Listado 9.6. Una clase P o i n t con un destructor class Point { public int X; public int Y; public Point() { X = 0; Y = 0; } public Point (int InitialX, int InitialY) { X = InitialX; Y = InitialY; } ~Point() { X = 0; Y = 0; ) public static void Main() {
224
Point MyFirstPoint = new Point (100, 200); Point MySecondPoint = new Point(); } }
Los destructores no devuelven valores, ni tampoco pueden aceptar parmetros. Si se intenta llamar a un destructor en el cdigo, el compilador 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 escribimos un mtodo y declaramos un objeto P o i n t como una variable local del mtodo. Cuando se llama al mtodo, se crea el objeto P o i n t y el mtodo puede trabajar con l. Cuando el mtodo llega al final de su bloque de cdigo, el objeto P o i n t ya no volver a usarse. En lenguajes como C++, esto hace que se llame al destructor de la clase cuando acaba el mtodo. En C# esto no tiene por qu ocurrir. De hecho, no se puede llamar a un destructor de clase. Recuerde que el CLR implementa una utilidad llamada recolector de objetos no utilizados que destruye objetos que ya no se usan en el cdigo. La recoleccin puede tener lugar mucho despus de que el objeto deje de ser accesible. Observe el mtodo Main() en el listado 9.7:
Listado 9.7. Demostracin del uso del recolector de objetos no utilizados mediante la estructura Point public static void Main() { Point MyFirstPoint = new Point(100, 200); Point MySecondPoint = new Point(); }
El mtodo Main() crea dos objetos locales P o i n t . Cuando el mtodo Main() termina su ejecucin, los objetos locales P o i n t ya no pueden volver a ser usados y el CLR los registra como objetos que pueden ser destruidos cuando se ejecute el recolector de objetos no utilizados. Sin embargo, el recolector de objetos no utilizados no siempre se ejecuta inmediatamente, lo que significa que no siempre se llama al destructor 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, suponga 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 abra el archivo y un destructor que cierre el archivo:
class File {
225
El destructor de la clase F i l e cierra el archivo, pero en realidad el destructor no se ejecuta hasta que el recolector de objetos no utilizados no se activa. Esto significa que el archivo puede todava estar abierto mucho despus de que la variable M y F i l e sea inaccesible. Para asegurarnos de que el archivo se cierra lo antes posible, aadimos a la clase File un mtodo Close() que cierra el archivo cuando se le llama.
226
expresin debe hacer referencia a un campo de clase. Observe, por ejemplo, la clase Point en el listado 9.8.
Listado 9.8. Campos y parmetros con el mismo nombre class Point { public int X; public int Y; Point(int X, int Y) { X = X; Y = Y; } public static void Main() { } }
Este cdigo no tiene el comportamiento esperado porque los identificadores X e Y se emplean en dos ocasiones: como identificadores de campo y en la lista de parmetros del constructor. El cdigo debe diferenciar el identificador de campo X del identificador de la lista de parmetros X. Si el cdigo es ambiguo, el compilador de C# interpreta que las referencias a X e Y en las declaraciones del constructor se refieren a los parmetros, y el cdigo establece los parmetros con el valor que ya contienen. Se puede usar la palabra clave this para diferenciar el identificador de campo del identificador de parmetro. El listado 9.9 muestra el cdigo corregido, usando la palabra clave this como prefijo del campo de nombre.
Listado 9.9. Cmo usar this con campos class Point { public int X; public int Y; Point(int X, int Y) { this.X = X; this.Y = Y; } public static void Main() { } }
227
El modificador static
Cuando se define un campo o un mtodo en una clase, cada objeto de esa clase creado por el cdigo tiene su propia copia de valores de campos y mtodos. Mediante la palabra clave static, se puede invalidar este comportamiento, lo que permite que varios objetos de la misma clase compartan valores de campos y mtodos.
En este ejemplo se crean dos objetos de la clase P o i n t . El primer objeto asigna a su copia de las coordenadas x e y los valores 100 y 200 respectivamente, y el segundo objeto asigna a su copia de las coordenadas x e y los valores 150 y 250 respectivamente. Cada objeto guarda su propia copia de las coordenadas x e y. Si se coloca el modificador static antes de una definicin de campo se est indicando que todos los objetos de la misma clase compartirn el mismo valor. Si un objeto asigna un valor esttico, todos los dems objetos de esa misma clase compartirn ese mismo valor. Observe el listado 9.10.
Listado 9.10. Campos estticos class Point { public static int XCoordinate; public static int YCoordinate; public int X { get { return XCoordinate; } } public int Y { get
228
{ return YCoordinate; } } public static void Main() { Point MyPoint = new Point(); System.Console.WriteLine("Before"); System.Console.WriteLine("======"); System.Console.WriteLine(MyPoint.X); System.Console.WriteLine(MyPoint.Y); Point.XCoordinate = 100; Point.YCoordinate = 200; System.Console.WriteLine("After"); System.Console.WriteLine("====="); System.Console.WriteLine(MyPoint.X); System.Console.WriteLine(MyPoint.Y); } }
La clase P o i n t del listado 9.10 tiene dos campos estticos enteros llamados X C o o r d i n a t e e Y C o o r d i n a t e . Tambin tiene dos propiedades de slo lectura, llamadas X e Y, que devuelven los valores de las variables estticas. El mtodo M a i n ( ) crea un nuevo objeto P o i n t y escribe sus coordenadas en la consola. A continuacin cambia los valores de los campos estticos y vuelve a escribir las coordenadas del objeto Point. El resultado puede verse en la figura 9.3.
C:\ C:\WINDOWS\System32\cmd.exe C:\>Listing9-8.exe Before ====== 0 0 After ===== 100 200 C:\>_
Hay que tener en cuenta que los valores de las coordenadas 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 P o i n t comparte campos estticos con todos los dems objetos P o i n t y cuando los campos estticos de la clase P o i n t cambian, todos los objetos de esa clase se ven afectados. Los campos estticos que se usan en expresiones no estn prefijados con un identificador de objeto, sino con el nombre de la clase que contiene los campos estticos. La siguiente instruccin es un error porque MyPoint hace referencia a un objeto y XCoordinate hace referencia a un campo esttico:
MyPoint.XCoordinate = 100;
Este cdigo hace que se produzca el siguiente error del compilador de C#:
error CS0176: No se puede obtener acceso al miembro esttico 'Point.XCoordinate' con una referencia de instancia; utilice un nombre de tipo en su lugar
En general hay que intentar que todas las constantes sean estticas, de modo que slo haya una copia del valor de la constante en memoria a la vez.
230
clave static reciben el nombre de mtodos de instancia. Los mtodos estticos estn incluidos en una clase, pero no pertenecen a ningn objeto especfico. Igual que los campos estticos y las constantes estticas, todos los objetos de una clase comparten una copia de un mtodo esttico. Los mtodos estticos no pueden hacer referencia a ninguna parte de un objeto que no est tambin marcado como esttico, como puede apreciarse en el listado 9.11.
Listado 9.11. Mtodos estticos que llaman a un mtodo de instancia de clase class Listing9_9 { public static void Main() { CallMethod(); } void CallMethod() { System.Console.WriteLine("Hello } }
from CallMethod()");
El error del cdigo del listado 9.11 es que hay un mtodo esttico, M a i n ( ) , que intenta llamar a un mtodo de instancia, C a l l M e t h o d ( ) . Esto no est permitido porque los mtodos de instancia son parte de una instancia de objeto y los mtodos estticos no. Para corregir el cdigo, el mtodo esttico M a i n ( ) debe crear otro objeto de la clase y llamar al mtodo de instancia desde el nuevo objeto, como muestra el listado 9.12.
Listado 9.12. Mtodos estticos que llaman a un mtodo de instancia de clase class Listing9_10 { public static void Main() { Listing9_10 MyObject = new Listing9_10(); MyObject.CallMethod(); } void { CallMethod()
231
System.Console.WriteLine("Hello } }
from CallMethod()");
Figura 9.4. Ejemplo de una llamada a un mtodo esttico desde dentro de la misma clase
Como todos los elementos de las clases estticas, los mtodos estticos aparecen slo una vez en memoria, por lo que se debe marcar el mtodo M a i n ( ) como static. Cuando el cdigo .NET se carga en memoria, el CLR empieza a ejecutar el mtodo M a i n ( ) . Recuerde que slo puede haber un mtodo M a i n ( ) en memoria a la vez. Si una clase tiene varios mtodos M a i n ( ) , el CLR no sabra qu mtodo M a i n ( ) ejecutar cuando el cdigo lo necesite. Usar la palabra clave static en el mtodo M a i n ( ) hace que slo haya disponible en memoria una copia del mtodo Main(). NOTA: Mediante el uso de parmetros de lneas de comandos en el compilador de C#, es posible incluir ms de un mtodo M a i n ( ) dentro de una aplicacin. Esto puede ser muy til cuando se quiere probar ms de un mtodo para depurar el cdigo.
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 cambia en todo el cdigo. El uso de constantes hace que el cdigo sea ms legible porque se pueden usar los nombres de las constantes en lugar de los valores literales. Los campos contienen el estado de las clases. Son variables que estn asociadas a un objeto. Los mtodos contienen el comportamiento de la clase. Son fragmentos de cdigo con nombre que realizan una accin determinada para la clase. Las propiedades permiten que los fragmentos que hacen llamadas tengan acceso al estado de la clase. Los fragmentos que hacen la llamada acceden a las propiedades con la misma sintaxis object.identifier que se emplea para acceder a los campos. La ventaja de las propiedades sobre los campos es que se puede escribir cdigo que se ejecuta cuando se consultan o se asignan los valores de la propiedad. Esto permite escribir cdigo de validacin para evitar que se asignen nuevos valores a las propiedades o para calcular dinmicamente el valor de una propiedad que est siendo consultada. Se pueden implementar propiedades de lectura y escritura, de slo lectura o de slo escritura. Los eventos permiten que la clase informe a las aplicaciones que hacen la llamada cuando se produzcan determinadas acciones en su interior. Las aplicaciones que hacen la llamada pueden suscribirse a los eventos de clase y recibir avisos cuando se produzcan dichos eventos. Los indizadores permiten que se acceda a la clase como si fuera una matriz. Las aplicaciones que hacen la llamada pueden usar la sintaxis de elemento de matriz de corchetes para ejecutar el cdigo descriptor de acceso del indizador de la clase. Los indizadores se usan cuando una clase contiene una coleccin de valores y es ms lgico considerarla una matriz de elementos. Las clases pueden redefinir los operadores, como se vio en un captulo anterior. Los operadores pueden ayudar a determinar cmo se comporta una clase cuando se usa en una expresin con un operador. Los constructores son mtodos especiales que son ejecutados cuando se crean objetos en la clase. Se puede definir ms de un constructor, cada uno con una lista de parmetros diferente. Tambin se puede definir una clase sin constructores. En ese caso, el compilador de C# genera un constructor por defecto que inicializa todos los campos del objeto con el valor cero. Los destructores son mtodos especiales que son ejecutados cuando se destruyen objetos de la clase. Una clase slo puede tener un destructor. Debido a la interaccin con el cdigo .NET y el CLR, los destructores se ejecutan cuando el recolector de objetos no utilizados recoge un objeto, no cuando el cdigo ya no puede acceder al identificador del objeto.
233
Las clases pueden definir tipos propios y estos tipos pueden contener definiciones de estructuras e incluso definiciones de otras clases. Una vez definidos estos tipos, la clase puede usarlos de la misma forma que los tipos enteros en C#.
La palabra clave this hace referencia a la instancia actual de un objeto. Se usa como prefijo para diferenciar a un identificador de campo de un identificador de parmetro con el mismo nombre. La palabra clave static advierte al compilador de C# de que todos los objetos de la clase comparten una sola copia de un campo o de un objeto. Por defecto, cada campo y cada mtodo de una clase de C# mantiene su propia copia de los valores del campo en memoria. Los elementos de la clase que no usan la palabra clave static reciben el nombre de mtodos de instancia. Los elementos de clase que usan la palabra clave static reciben el nombre de mtodos estticos.
234
C# define el comportamiento de los operadores cuando se usan en una expresin que contiene tipos de datos integrados en C#. Por ejemplo, C# define el comportamiento del operador suma calculando la suma de dos operados y ofreciendo la suma como el valor de la expresin. En C# se puede definir el comportamiento de la mayora de los operadores estndar para que puedan usarse en estructuras y clases propias. Se pueden escribir mtodos especiales que definan el comportamiento de la clase cuando aparezca en una expresin que usa un operador de C#. Esto permite que las clases se puedan emplear en expresiones que parecera ms lgico que escribieran otras partes del cdigo. Supongamos, por ejemplo, que estamos escribiendo una clase que gestiona un conjunto de archivos de una base de datos. Si algn otro fragmento de cdigo tiene dos objetos de esa clase, querr poder escribir una expresin que sume los archivos y los almacene en un tercer objeto. Esto parece una operacin de suma y parece lgico que otros fragmentos del cdigo tengan partes como la siguiente:
Records Records1; Records Records2; Records Records3; Records3 = Records1 + Records2;
237
La clase R e c o r d s puede incluir un mtodo que especifique cuntos objetos de la clase actuarn de una determinada forma cuando se usen en expresiones con el operador de suma. Estos mtodos reciben el nombre de implementaciones de operadores definidas por el usuario, y la operacin orientada a objetos para definir el comportamiento de los operadores de una clase recibe el nombre de sobrecarga del operador. Se emplea la palabra "sobrecarga" porque el cuerpo del cdigo sobrecarga el significado del mismo operador y hace que se comporte de forma diferente dependiendo del contexto en el que se use el operador. Todos los mtodos de sobrecarga de operadores deben declarase con las palabras clave static y public .
Retomemos el ejemplo de la clase P o i n t utilizada en un captulo anterior. Supongamos que queremos aadir un operador unario ms 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.
Listado 10.1. Cmo sobrecargar el operador unario ms class Point { public int X; public int Y; public static Point operator + (Point RValue) { Point NewPoint = new Point(); if(RValue.X < 0) NewPoint.X = -(RValue.X); else NewPoint.X = RValue.X; if (RValue.Y < 0) NewPoint.Y = -(RValue.Y); else NewPoint.Y = RValue.Y; return NewPoint; } public static void Main() { Point MyPoint = new Point(); MyPoint.X = -100; MyPoint.Y = 200; System.Console.WriteLine(MyPoint.X); System.Console.WriteLine(MyPoint.Y); MyPoint = +MyPoint; System.Console.WriteLine(MyPoint.X); System.Console.WriteLine(MyPoint.Y); } }
El mtodo 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 continuacin aplica el operador unario ms al objeto y vuelve a asignar el resultado al mismo punto. Por ltimo, escribe las coordenadas x e y en la consola. En la figura 10.1 se puede ver el resultado del listado 10.1. Las coordenadas del punto han cambiado de (-100, 200) a (100, 200). El cdigo con el operador sobrecargado se ejecuta cuando se llega a la siguiente instruccin:
MyPoint = +MyPoint;
239
200
Cuando se llega a esta instruccin, se ejecuta la sobrecarga del operador unario ms para la clase P o i n t . La expresin situada a la derecha del signo igual se usar como el parmetro del mtodo. NOTA: La expresin situada a la derecha de un operador de asignacin suele ser denominada rvalue , que es la abreviatura de "valor derecho". La expresin a la izquierda del operador de asignacin suele ser denominada lvalue, que es la abreviatura de "valor izquierdo". El uso de RValue para nombrar el mtodo de sobrecarga de operadores determina que se est pasando el rvalue de la asignacin. Esto es slo una convencin de designacin y no un requisito. Si lo desea, puede asignar otro nombre a los parmetros usando cualquier identificador vlido permitido por C#. Este mtodo crea un nuevo objeto P o i n t y a continuacin examina las coordenadas del rvalue proporcionado. Si alguno de los parmetros es negativo, sus valores se cambian de signo, volvindose por tanto valores positivos, y estos nuevos valores positivos se asignan al nuevo punto. Los valores que no son negativos se asignan al nuevo punto sin ninguna conversin. A continuacin el mtodo devuelve el nuevo punto. El valor devuelto por el operador se usa como lvalue para la declaracin original. El tipo de retorno de las sobrecargas del operador para el unario ms, el unario menos, la negacin o los operadores de complemento bit a bit no tienen el mismo tipo que el rvalue. Puede ser cualquier tipo de C# que sea adecuado para el operador.
240
Tras definir el nuevo operador P o i n t , simplemente se define la accin que debe realizar cuando se presente con una variable del tipo P o i n t . El listado 10.2 declara la coordenada x como -100 y la coordenada y como 200. Estos valores se escriben en la consola para una verificacin visual y luego se usa el operador sobrecargado. Despus de que la aplicacin de ejemplo haya realizado la resta de la clase P o i n t , los valores resultantes se escriben en la ventana de la consola para indicar que el comportamiento ha sido el esperado. La figura 10.2 muestra el resultado del listado 10.2. Hasta ahora, en este captulo hemos estudiado el unario ms y el unario menos. Estos operadores efectan operaciones sobre un valor dado (por eso se lla-
241
man "unarios"). Los dems operadores matemticos bsicos que pueden usarse sobre un valor se sobrecargan de la misma manera.
C:\ C:\WINDOWS\System32\cmd.exe C:\>Listing10-2.exe -100 200 -100 -200 C:\>_
En la siguiente seccin se describe un operador de otro tipo, el operador de complemento bit a bit.
242
El resultado de una operacin de complemento bit a bit no se conoce con exactitud hasta que se ven los resultados hexadecimales de la operacin. El listado 10.3 genera el complemento de los valores enteros 5 y 6. El resultado de esta operacin (que aparece en la figura 10.3) es -6 y -7 respectivamente. Cuando se observan los valores hexadecimales de los valores de entrada y salida, es fcil deducir lo que est ocurriendo en realidad.
c:\ C:\WINDOWS\System32\cmd.exe C:\>Listing10-3.exe 5 6 -6 -7 C:\>_
Tabla 10.1. Valores de entrada y salida para operaciones de complemento bit a bit
Antes de sobrecargar un operador es necesario entender perfectamente cmo funciona. En caso contrario podra obtener resultados inesperados.
243
Por ejemplo, observe el listado 10.4. Esta clase modifica la clase P o i n t para sobrecargar el operador de incremento prefijo. El operador se sobrecarga para aumentar en una unidad las coordenadas x e y.
Listado 10.4. Cmo sobrecargar el incremento prefijo class Point { public int X; public int Y; public static Point operator ++ (Point RValue) { Point NewPoint = new Point(); NewPoint.X = RValue.X + 1 ; NewPoint.Y = RValue.Y + 1; return NewPoint; } public static void Main() { Point MyPoint = new Point(); MyPoint.X = 100 ; MyPoint.Y = 200; System.Console.WriteLine(MyPoint.X); System.Console.WriteLine(MyPoint.Y); MyPoint = ++MyPoint; System.Console.WriteLine(MyPoint.X); System.Console.WriteLine(MyPoint.Y); } }
244
Figura 10.4. Resultado de la ejecucin del cdigo compilado del listado 10.4
class Point { public int X; public int Y; public static Point operator -- (Point RValue) { Point NewPoint = new Point(); NewPoint.X = RValue.X - 1; NewPoint.Y = RValue.Y - 1; return NewPoint; } public static void Main() { Point MyPoint = new Point();
245
MyPoint.X = 100; MyPoint.Y = 200; System.Console.WriteLine(MyPoint.X); System.Console.WriteLine(MyPoint.Y); MyPoint = --MyPoint; System.Console.WriteLine(MyPoint.X); System.Console.WriteLine(MyPoint.Y); } }
De nuevo, se pasa la coordenada x con el valor 100 y la coordenada y con el valor de 200. La figura 10.5 contiene el resultado de este programa despus de que la sobrecarga del operador de decremento haya restado una unidad de x y de y.
c:\ C:\WINDOWS\System32\cmd.exe C:\>Listing10-5.exe 100 200 99 199 C:\>_
Cuando se sobrecargan operadores siempre hay que estar preparado para lo peor. Siempre hay alguna posibilidad de que los datos que se estn pasando sean incorrectos y nos encontraremos con que la funcin sobrecargada no puede gestionar los datos. Los anteriores ejemplos no mostraban ninguna de las excepciones que pueden aparecer cuando se pasan valores errneos o inesperados. Es recomendable experimentar con las funciones e intentar forzar errores.
246
El operador que se sobrecarga Una lista de parmetros que especifica un solo parmetro del tipo de la clase o estructura que contiene el mtodo del operador sobrecargado
El listado 10.6 es un buen ejemplo. Modifica la clase p o i n t para que devuelva true si el punto est en el origen o false en caso contrario.
Listado 10.6. Sobrecarga de los operadores t r u e y f a l s e class Point { public int X; public int Y; public static bool operator true (Point RValue) { if ((RValue.X == 0) && (RValue.Y == 0)) return true; return false; } public static bool operator false (Point RValue) { if ((RValue.X == 0) && (RValue.Y == 0)) return false; return true; } public static void Main() { Point MyPoint = new Point(); MyPoint.X = 100; MyPoint.Y = 200; if (MyPoint) System.Console.WriteLine("The point is at the origin."); else System.Console.WriteLine("The point is not at the origin."); } }
La sobrecarga de los operadores t r u e y f a l s e permite que los objetos de la clase P o i n t puedan usarse como expresiones booleanas, como en la instruccin if. Debido a que el objeto M y P o i n t no est en el origen, el objeto se evala como false, tal y como aparece en la figura 10.6. Si se sobrecarga el operador t r u e o el operador f a l s e en una clase o estructura, ambos deben ser sobrecargados. Si se sobrecarga uno de los dos pero no el otro, el compilador de C# emite un mensaje de error como el siguiente:
247
error CS0216: El operador 'Point.operator true(Point)' requiere que tambin se defina un operador coincidente 'false'
c:\ C:\WINDOWS\System32\cmd.exe C:\>Listing10-6.exe The point is not at the origin. C:\>
248
Si quiere sobrecargar cualquiera de los operadores binarios de una clase o estructura, hay que definir un mtodo con las siguientes caractersticas: Un tipo devuelto deseado La palabra clave operator El operador que se sobrecarga Una lista de parmetros que especifica dos parmetros, al menos uno de los cules debe ser del tipo de la clase o estructura que contiene el mtodo del operador sobrecargado
La sobrecarga de operadores binarios brinda mucha flexibilidad. Pueden usarse diferentes parmetros para los dos parmetros de la lista, lo que significa que se puede aplicar el operador a dos valores de diferentes tipos si se desea. Tambin se puede usar cualquier tipo disponible como valor devuelto por el operador sobrecargado. Si se quiere sumar un objeto y un valor de coma flotante para obtener un resultado booleano, se puede escribir un mtodo sobrecargado como el siguiente:
static public bool operator + (Point MyPoint, float FloatValue)
Se pueden definir varias sobrecargas para el mismo operador, pero slo si las listas de parmetros usan tipos diferentes:
static public bool operator + (Point MyPoint, float FloatValue) static public bool operator + (Point MyPoint, int IntValue) static public bool operator + (Point MyPoint, uint UIntValue)
El listado 10.7 aade los operadores de igualdad y de desigualdad a la clase P o i n t . El operador devuelve resultados booleanos que devuelven true si los dos objetos P o i n t tienen las mismas coordenadas; en caso contrario, devuelven false.
Listado 10.7. Sobrecarga de los operadores de igualdad y desigualdad class Point { public int X; public int Y; public static bool operator == (Point Point1, Point Point2) { if(Point1.X != Point2.X) return false; if (Point1.Y != Point2.Y)
249
return false; return true; } public override bool Equals(object o) { return true; } public override int GetHashCode() { return 0; } public static bool operator != (Point Point1, Point Point2) { if(Pointl.X != Point2.X) return true; if (Point2.Y != Point2.Y) return true; return false; } public static void Main() { Point MyFirstPoint = new Point(); Point MySecondPoint = new Point(); Point MyThirdPoint = new Point(); MyFirstPoint.X = 100; MyFirstPoint.Y = 200; MySecondPoint.X = 500; MySecondPoint.Y = 750; MyThirdPoint.X = 100; MyThirdPoint.Y = 200; if(MyFirstPoint == MySecondPoint) System.Console.WriteLine("MyFirstPoint and MySecondPoint are at the same coordinates."); else System.Console.WriteLine("MyFirstPoint and MySecondPoint are not at the same coordinates."); if(MyFirstPoint == MyThirdPoint) System.Console.WriteLine("MyFirstPoint are at the same coordinates."); else System.Console.WriteLine("MyFirstPoint are not at the same coordinates.") ; } }
and
MyThirdPoint
and
MyThirdPoint
250
El mtodo Main() define tres puntos: MyFirstPoint, con coordenadas (100, 200) MySecondPoint, con coordenadas (500, 750) MyThirdPoint, con coordenadas (100, 200)
A continuacin el mtodo usa el operador de igualdad para determinar si los puntos MyFirstPoint y MySecondPoint hacen referencia a la mismas coordenadas. Seguidamente usa el operador de igualdad para determinar si los puntos MyFirstPoint y MyThirdPoint hacen referencia a las mismas coordenadas. En la figura 10.7 se muestra el resultado que se obtiene si se compila y ejecuta el cdigo del listado 10.7.
C:\ C:\WINDOWS\System32\cmd.exe C:\>Listing10-7.exe MyFirstPoint and MySecondPoint are not at the same coordinates. MyFirstPoint and MyThirdPoint are at the same coordinates. C:\>_
Los siguientes pares de operadores deben ser sobrecargados conjuntamente: Igualdad y desigualdad Menor y mayor que Menor o igual que y mayor o igual que
Si se sobrecarga uno de esto pares pero no el otro, el compilador de C# emite un mensaje como el siguiente:
error CS0216: El operador 'Point.operator ==(Point, Point)' requiere que tambin se defina un operador coincidente '!='
251
de C# debe tratar la conversin como implcita o explcita. Si quiere definir un nuevo operador de conversin en una clase o estructura, debe definir un mtodo con las siguientes caractersticas: La palabra clave implicit si la conversin va a considerarse una conversin implcita o la palabra clave explicit si la conversin va a considerarse una conversin explcita La palabra clave operator Un tipo que especifica el tipo al que se va a hacer la conversin Una lista de parmetros que especifica el tipo original de la conversin
El listado 10.8 define una conversin implcita de un objeto de la clase P o i n t a d o u b l e . El tipo doble especifica la distancia desde el origen hasta el punto, usando el teorema de Pitgoras.
Listado 10.8. Cmo definir una conversin implcita class Point { public int X; public int Y; public static implicit operator double (Point RValue) { double Distance; double Sum; Sum = (RValue.X * RValue.X) + (RValue.Y * RValue.Y); Distance = System.Math.Sqrt(Sum); return Distance; } public static void Main() { double Distance; Point MyPoint = new Point(); MyPoint.X = 100; MyPoint.Y = 200; Distance = MyPoint; System.Console.WriteLine(Distance); } }
NOTA: .NET Framework define el mtodo System.Math.Sqrt() que calcula la raz cuadrada del parmetro proporcionado. El mtodo es esttico, por lo que se le puede llamar aunque no se tenga un objeto del tipo System.Math para llamarlo.
252
El mtodo M a i n ( ) declara un objeto de tipo P o i n t y asigna a sus coordenadas los valores (100, 200). A continuacin asigna el objeto a una variable de tipo d o u b l e , lo que est permitido porque la clase P o i n t define un operador de conversin que convierte un objeto P o i n t a doble. Como el operador de conversin est definido como una conversin implcita, no es necesaria una conversin explcita. A continuacin el mtodo M a i n ( ) escribe el valor de double convertido en la consola. La figura 10.8 muestra el resultado del listado 10.8.
c:\ C:\WINDOWS\System32\cmd.exe C:\>Listing10-8.exe 223,606797749979 C:\>_
Resumen
C# permite personalizar el comportamiento de varios de los operadores integrados. Las clases y las estructuras pueden incluir mtodos llamados mtodos de
253
sobrecarga de operador que definen el comportamiento de un operador cuando aparece en una expresin con la clase o estructura. Para sobrecargar los operadores unario ms, unario menos, de negacin o de complemento bit a bit en una clase o estructura, hay que definir un mtodo con un tipo devuelto deseado, el operador que se est sobrecargando, y un solo parmetro del tipo de la clase o estructura que contiene el mtodo de operador sobrecargado. Para sobrecargar los operadores de incremento o decremento prefijo en una clase o estructura, hay que definir un mtodo con un tipo de devolucin que especifique el tipo de clase o estructura que contiene el mtodo del operador sobrecargado. Tambin es necesario definir el operador que se est sobrecargando y un solo parmetro del tipo de clase o estructura que contiene el mtodo del operador sobrecargado. Para sobrecargar los operadores t r u e o f a l s e en una clase o estructura, hay que definir un mtodo con un tipo de devolucin booleano y un solo parmetro del tipo de la clase o estructura que contiene el mtodo de operador sobrecargado. Para sobrecargar cualquiera de los operadores binarios en una clase o estructura, hay que definir un mtodo con un tipo de devolucin, el operador que se est sobrecargando y dos parmetros. Al menos uno de los dos parmetros debe ser del tipo de clase o estructura que contiene el mtodo del operador sobrecargado. Tambin se pueden definir nuevas conversiones para las clases o estructuras. Se especifica si la conversin se considerara un operador implcito o explcito. El mtodo del operador de conversin especifica el tipo de la variable que se convierte y el tipo al que debe ser convertida.
254
11 Herencia de clase
Los programas ms simples de C# pueden usar una o dos clases. Sin embargo, probablemente se necesiten varias clases en los programas ms grandes. Muchas de estas clases pueden tener campos o mtodos similares y sera lgico compartir el cdigo comn entre el conjunto de clases. C# incluye el concepto orientado a objetos de herencia, que permite que una clase adopte cdigo 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 software orientado a objetos para reutilizar el cdigo ms comn. Observe, por ejemplo, los cuadros de lista de seleccin mltiple y de seleccin simple de Windows. Estos cuadros de lista tienen diferentes funcionalidades: uno permite que se seleccionen varios elementos y el otro lo impide, pero tambin tienen muchas similitudes. Tienen el mismo aspecto, se comportan de la misma forma cuando el usuario se desplaza por la lista y usan el mismo color para marcar un elemento seleccionado. Si hubiera que escribir estos dos cuadros de lista como clases de C#, se podran escribir por separado, sin que uno conozca la existencia del otro. Sin embargo, eso sera un desperdicio. La mayor parte del cdigo seguramente sea idntico. Sera ms lgico escribir una clase que contuviera el cdigo comn y disponer de clases derivadas de la clase de cdigo comn y que implementasen las funcionalidades diferentes. Se puede escribir una clase llamada ListBox para que, por
257
ejemplo, contenga el cdigo comn y, a continuacin, escribir una clase de C# llamada SingleSelectionListBox que herede de ListBox y proporcione el cdigo nico al cuadro de lista de seleccin simple. Tambin se puede escribir una clase de C# llamada MultipleSelectionListBox que tambin herede de ListBox pero que proporcione el cdigo nico al cuadro de lista de seleccin simple. Otra de las ventajas consiste en que, si encuentra un error en el cuadro de lista, se le puede seguir fcilmente la pista hasta el error en el cdigo comn. Si se puede reparar el error en el cdigo comn, al volver a compilar el programa se repararn esos errores en las clases de cuadros de lista de seleccin mltiple y seleccin simple. Basta reparar un error para solucionar el problema en las dos clases. En terminologa orientada a objetos, se habla de herencia en trminos 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
y las clases SingleSelectionListBox y MultipleSelectionListBox
Por defecto, el compilador de C# da al ejecutable resultante el nombre del primer archivo fuente. La anterior lnea de comandos produce un ejecutable llamado file1.exe. Se puede usar el argumento /out para cambiar el nombre del archivo:
csc /out:myapp.exe file1.cs file2.cs file3.cs
Esta lnea de comandos del compilador genera un ejecutable llamado myapp.exe. NOTA: Una y slo una de sus clases debe especificar un mtodo esttico Main() .
258
Ahora supongamos que queremos trabajar con puntos en un espacio tridimensional, pero manteniendo la clase P o i n t 2 D . La herencia nos permite crear una nueva clase que mantiene todo el cdigo de la clase P o i n t 2 D y le aade una coordenada Z. Para nombrar 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 continuacin, se incluye un ejemplo de cmo se derivara la clase Point3D a partir de la clase Point2D:
class Point3D : Point2D { public int Z; // cdigo para la clase Point3D }
Dependiendo de las reglas de mbito de la clase base, todos los campos y propiedades de la clase base ( P o i n t 2 D ) pueden ser empleadas en la clase derivada ( P o i n t 3 D ) . Por ejemplo, cuando una clase se deriva de una clase base, el cdigo de la clase derivada puede acceder a los campos y propiedades de la clase base si el mbito lo permite. Al declarar una clase derivada es posible indicar una sola clase base. Algunos lenguajes orientados a objetos, como C++, permiten especificar ms de una clase base para una clase derivada. Este concepto recibe el nombre de herencia mltiple. C# admite la herencia simple, pero no la mltiple. En el apartado dedicado a la contencin se explica una tcnica para simular herencia mltiple en C#. El listado 11.1 ensea a usar las clases Point3D y Point2D juntas.
Listado 11.1. Cmo derivar Point3D a partir de Point2D class Point2D { public int X; public int Y; } class Point3D : Point2D
259
{ public int Z; } class MyMainClass { public static void Main() { Point2D My2DPoint = new Point2D(); Point3D My3DPoint = new Point3D(); My2DPoint.X = 100; My2DPoint.Y = 200; My3DPoint.X = 150; My3DPoint.Y = 250; My3DPoint.Z = 350; } }
El mtodo M a i n ( ) crea un objeto P o i n t 2 D y otro P o i n t 3 D . El objeto P o i n t 3 D tiene campos para las coordenadas X, Y y Z, aunque la declaracin de P o i n t 3 D slo declare un campo llamado Z. Los campos X e Y se heredan de la clase base P o i n t 2 D y pueden ser usados exactamente igual que si se hubieran declarado directamente en la clase Point3D.
mbito
Al disear la estructura de la herencia de clases, puede decidir qu miembros de la clase base no deben ser visibles para las clases derivadas o para los dems programadores. Por ejemplo, se puede escribir un mtodo en una clase base que ayude a calcular un valor. Si ese clculo no es de utilidad en una clase derivada, se puede evitar que la clase derivada llame al mtodo. En terminologa de la programacin, la visibilidad de una variable o mtodo se conoce como su mbito. Algunas variables o mtodos pueden ser declaradas como de mbito pblico, otras pueden ser declaradas como de mbito privado y otras pueden estar entre estos dos casos. C# define cinco palabras clave que permiten definir el mbito de cualquier miembro (variable o mtodo) de una clase. El mbito de un miembro afecta a su visibilidad para las clases derivadas y el cdigo que crea las instancias de la clase. Estas palabras clave, resaltadas en la siguiente lista, se colocan antes de cualquier otra palabra clave en una declaracin de miembro. Los miembros marcados como public son visibles para las clases derivadas y para el cdigo que crea los objetos de la clase. Hasta ahora hemos usado public.
260
Los miembros marcados como private slo son visibles para la clase en la que se definen. Los miembros privados no son accesibles desde las clases derivadas ni desde el cdigo que crea los objetos de la clase. Los miembros marcados como protected slo son visibles para la clase en la que se definen o desde las clases derivadas de esa clase. No se puede acceder a los miembros protegidos desde el cdigo que crea los objetos de su clase. Los miembros marcados como internal son visibles para cualquier cdigo en el mismo archivo binario, pero no son visibles para otros archivos binarios. Recuerde que .NET Framework acepta el concepto de ensamblados, que son bibliotecas de cdigo ya compiladas que pueden ser usadas por aplicaciones externas. Si se escribe una clase en C# y se compila la clase para obtener un ensamblado, cualquier fragmento de cdigo del ensamblado podr acceder a los miembros de clase internal. Sin embargo, si otro fragmento de cdigo usa ese ensamblado, no tendr acceso al miembro, aunque derive una clase de la clase del ensamblado. Los miembros marcados como protected internal son visibles para todo el cdigo incluido en el mismo archivo binario y para 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 cdigo del ensamblado puede acceder a los miembros de clase internos. Si otro fragmento de cdigo externo usa el ensamblado y deriva una clase de la clase del ensamblado, el miembro interno protegido ser accesible para la clase derivada. Sin embargo, el cdigo que trabaja con los objetos de la clase base no tendr acceso al miembro.
C# permite especificar un miembro de clase sin especificar ninguna palabra clave de mbito. Si se declara un miembro de clase sin especificar ninguna palabra clave de mbito, al miembro se le asigna por defecto accesibilidad privada. Los miembros que se declaran sin usar ninguna palabra clave de mbito, pueden usarse en otras partes de la clase, pero no pueden ser usados por clases derivadas ni por cdigo que use objetos de la clase.
261
Listado 11.2. Cmo reutilizar identificadores de clase base class Point2D { public int X; public int Y; } class Point3D { public int public int public int } : Point2D X; Y; Z;
class MyMainClass { public static void Main() { Point2D My2DPoint = new Point2D(); Point3D My3DPoint = new Point3D(); My2DPoint.X = 100; My2DPoint.Y = 200; My3DPoint.X = 150; My3DPoint.Y = 250; My3DPoint.Z = 350; } }
La clase derivada P o i n t 3 D define los campos X e Y que coinciden con los identificadores usados en la clase base P o i n t 2 D . El compilador de C# emite las siguientes advertencias cuando se compila este cdigo:
warning CS0108: La 'Point3D.X' porque warning CS0108: La ' Point3D.Y' porque palabra clave new es necesaria en oculta el miembro heredado 'Point2D.X' palabra clave new es necesaria en oculta el miembro heredado 'Point2D.Y'
El compilador de C# emite avisos porque los identificadores de la clase derivada ocultan las definiciones que usan el mismo identificador en la clase base. Si se quieren reutilizar los nombres, pero que el compilador no emita avisos, se puede usar el operador new al reutilizar los identificadores en la clase derivada. El cdigo del listado 11.3 compila sin emitir avisos.
Listado 11.3. Cmo usar new para reutilizar identificadores de clase class Point2D { public int X; public int Y;
262
} class Point3D { new public new public public int } : Point2D int X; int Y; Z;
class MyMainClass { public static void Main() { Point2D My2DPoint = new Point2D(); Point3D My3DPoint = new Point3D(); My2DPoint.X = 100; My2DPoint.Y = 200; My3DPoint.X = 150; My3DPoint.Y = 250; My3DPoint.Z = 350; } }
263
implementado. La operacin de reimplementar un mtodo de clase base en una clase derivada recibe el nombre de reemplazar el mtodo de clase base. Al reemplazar un mtodo de clase base en C# hay que tener en cuenta los dos requisitos: El mtodo de clase base debe declararse con la palabra clave virtual . El mtodo de la clase derivada debe declararse con la palabra clave override .
Los mtodos de clase base que usan la palabra clave virtual reciben el nombre de mtodos virtuales y los de clase base que usan la palabra clave override reciben el nombre de mtodos de reemplazo. El listado 11.4 demuestra cmo puede implementarse el mtodo PrintToConsole() para las clase Point2D y Point3D.
Listado 11.4. Cmo reemplazar mtodos virtuales class Point2D { public int X; public int Y; public virtual void PrintToConsole() { System.Console.WriteLine("({0}, {1})", } } class Point3D : Point2D { public int Z; public override void PrintToConsole() { System.Console.WriteLine("({0}, {1}, } } class MyMainClass { public static void Main() { Point2D My2DPoint = new Point2D(); Point3D My3DPoint = new Point3D(); My2DPoint.X = 100; My2DPoint.Y = 200; My3DPoint.X = 150; My3DPoint.Y = 250; My3DPoint.Z = 350;
X,
Y);
{2})", X, Y,
Z);
264
My2DPoint.PrintToConsole(); My3DPoint.PrintToConsole(); } }
NOTA: La sintaxis de las llamadas WriteLine() hechas en el listado 11.4 es diferente de la sintaxis usada con anterioridad. Los nmeros entre llaves de la cadena son comodines. Los valores de los otros parmetros se escriben en la consola en lugar del comodn. El comodn {0} es reemplazado por el valor del primer parmetro, el comodn {1} es reemplazado por el valor del segundo parmetro y as sucesivamente. El listado 11.4 escribe esto en la consola:
(100, 200) (150, 250, 350)
No se puede reemplazar un mtodo de clase base a menos que use la palabra clave virtual . Si intenta hacerlo sin usar la palabra clave, el compilador de C# emite el siguiente error:
error CS0506: 'Point3D.PrintToConsole()' : no se puede reemplazar el miembro heredado 'Point2D.PrintToConsole()' porque no est marcado como virtual, abstract u override
Sin embargo, est permitido reemplazar un mtodo override . Si, por alguna extraa razn, se quiere implementar una clase P o i n t 4 D y derivarla de Point3D, es posible reemplazar el mtodo de Point3D PrintToConsole().
Polimorfismo
El concepto de reemplazo de mtodos lleva al concepto de polimorfismo. Cuando reemplazamos un mtodo, queremos llamar al mtodo apropiado desde cualquier mtodo que llame a este mtodo reemplazado. El listado 11.5 presenta este concepto en accin. Se ha aadido a P o i n t 2 D un mtodo UsePrintToConsole() que llama al mtodo virtual PrintToConsole() . P o i n t 3 D hereda este mtodo de P o i n t 2 D . Cuando se llama a PrintToConsole() en esta funcin, se quiere llamar a la versin que pertenece a la clase apropiada. En otras palabras, en el mtodo UsePrintToConsole() que pertenece a la clase P o i n t 2 D , se pretende llamar al mtodo PrintToConsole() que pertenece a la clase P o i n t 2 D . En el mtodo UsePrintToConsole() que pertenece a la clase P o i n t 3 D , se pretende llamar al mtodo reemplazado PrintToConsole() que pertenece a la clase P o i n t 3 D . Como el mtodo PrintToConsole() fue declarado como
265
un mtodo virtual, la deteccin de la versin que debe ejecutarse tiene lugar automticamente. El listado 11.5 escribe lo siguiente en la consola:
(100, 200) (150, 250, 350) Listado 11.5. Polimorfismo class Point2D { public int X; public int Y; public virtual void PrintToConsole() { System.Console.WriteLine("({0}, {1})", } public void UsePrintToConsole() { PrintToconsole(); } } class Point3D : Point2D { publiC int Z; public override void PrintToConsole() { System.Console.WriteLine("({0}, {1}, {2})", X, Y, } } class MyMainClass { public static void Main() { Point2D My2DPoint = new Point2D(); Point3D My3DPoint = new Point3D(); My2DPoint.X = 100; My2DPoint.Y = 200; My3DPoint.X = 150; My3DPoint.Y = 250; My3DPoint.Z = 350; My2DPoint.UsePrintToConsole(); My3DPoint.UsePrintToConsole(); } }
X,
Y);
Z);
266
Mtodos abstractos
Algunas clases base pueden no ser capaces de proporcionar la implementacin de un mtodo, pero puede que queramos que las clases derivadas proporcionen una implementacin. Supongamos, por ejemplo, que estamos escribiendo en C# una aplicacin de geometra y escribimos clases llamadas S q u a r e y C i r c l e . Decidiremos las funciones comunes que usar cada forma de la aplicacin y por lo tanto implementamos una clase base llamada S h a p e y derivamos las clases Square y Circle de Shape:
Class Shape { } class Circle : Shape { } class Square : Shape { }
Ahora supongamos que decidimos que todas las formas deben ser capaces de calcular su rea, de modo que escribimos un mtodo llamado G e t A r e a ( ) . El problema de escribir ese cdigo en la clase base es que la clase base no tiene suficiente informacin para calcular un rea. Cada forma calcula su rea usando una frmula diferente. Lo que podemos hacer es definir un mtodo abstracto en la clase base Shape. Los mtodos abstractos no proporcionan una implementacin propia, sino que proporcionan una firma de mtodo que las clases derivadas deben implementar. Los mtodos abstractos dicen "Yo no s implementar este mtodo, pero mi clase derivada lo har, de modo que asegrese de que la implementa con los parmetros y el cdigo devuelto que yo especifico". Los siguientes fragmentos demuestran cmo declarar un mtodo abstracto en la clase Shape.
abstract class Shape { public abstract double GetArea(); }
NOTA: Las clases abstractas usan la palabra clave abstract. No tienen cuerpo de mtodo; en su lugar hay un punto y coma despus de la lista de parmetros. Las clases abstractas tambin son, por definicin, mtodos virtuales y debe usarse la palabra clave override para reemplazarlos por clases derivadas:
267
class Square : Shape { public override double GetArea() { // implemente el clculo del rea } }
Las clases que contienen al menos un mtodo abstracto reciben el nombre de clases abstractas y deben incluir la palabra clave abstract antes de la palabra clave de la clase. Si no se incluye la palabra clave abstract al definir la clase se obtendr un error del compilador de C#:
error CS0513: 'Shape.GetArea()' es abstract pero est incluida en la clase nonabstract 'Shape'
El compilador de C# no permite crear objetos a partir de clases abstractas. Si se intenta el compilador de C# emite un error:
error CS0144: No se puede crear una instancia de la clase o interfaz abstracta 'Shape'
Las clases abstractas suelen usarse para crear una clase base comn a un conjunto de clases. Esto permite usar el polimorfismo al almacenar clases derivadas en algn tipo de coleccin, como se vio en un captulo anterior.
Las propiedades virtual y override y los indizadores funcionan como las propiedades virtual y override. Las propiedades y los indizadores pueden marcarse como virtuales en una clase base y como reemplazados en una clase derivada. Las clases base pueden definir propiedades e indizadores, que no tienen implementacin propia. Las clases base que contienen al menos una propiedad abstracta o un indizador deben ser marcadas como si fueran una clase abstracta. Las propiedades abstractas y los indizadores deben ser reemplazados en una clase derivada.
268
X,
Y);
269
My2DPoint.PrintToConsole(); My3DPoint.PrintToConsole(); } }
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 constructor de la clase P o i n t 3 D admite tres parmetros. Los primeros dos parmetros se pasan al constructor de la clase base usando la palabra clave base y el tercero se usa para establecer el valor del campo Z de la clase derivada.
Tambin se puede invocar a mtodos de clase base con esta otra sintaxis:
base.PrintToConsole();
Clases selladas
Si no quiere que se derive cdigo de una determinada clase, puede marcar la clase con la palabra clave sealed . No se puede derivar una clase de una clase sellada. Se puede especificar una clase sellada escribiendo la palabra clave sealed antes de la palabra clave class, como en este ejemplo:
sealed class MySealedClass
Si se intenta derivar una clase de una clase sellada, el compilador de C# emite un error:
error CSU509: 'Point3D' sealed 'Point2D' : no se puede heredar de la clase
Contencin y delegacin
Si la herencia es una relacin ES-UN, la contencin es una relacin TIENEUN. Un gato de Birmania ES UN gato (por lo que puede heredar la clase
270
DeBirmania de la clase genrica Gato); pero un Coche TIENE 4 ruedas (por lo que la clase Coche puede tener cuatro objetos Rueda). El aspecto ms interesante de la contencin es que se puede emplear como sustituto de la herencia. El principal inconveniente de usar la contencin en lugar de la herencia es que se pierden las ventajas del polimorfismo. Sin embargo, se obtienen los beneficios de la reutilizacin de cdigo. En C# hay dos casos comunes en los que prcticamente slo se puede emplear la contencin y no la herencia: cuando se trabaja con herencia mltiple y cuando se trabaja con clases selladas. A continuacin se incluye un ejemplo que muestra cmo funciona esta tcnica. Adems, ver trabajar al polimorfismo. Supongamos que tenemos una clase A l a r m C l o c k y una clase R a d i o como las que aparecen en el siguiente fragmento y queremos crear una clase C l o c k R a d i o que combine estas dos clases. Si C# admitiese herencia mltip l e , se podra hacer que C l o c k R a d i o heredase de las clases A l a r m C l o c k y R a d i o . A continuacin se podra aadir una variable booleana r a d i o A l a r m que determine si se activa la alarma o la radio y que reemplace S o u n d A l a r m ( ) para usar esta variable. Por desgracia, C# no admite herencia mltiple. Por suerte, se puede emplear la contencin en lugar de la herencia y obtener todas las ventajas de la reutilizacin de cdigo. Observe paso a paso cmo funciona:
class Radio { protected bool
on_off;
public void On() { if (!on_off) Console.WriteLine("Radio is now on!"); on_off = true; } publiC void Off() { if (on_off) Console.WriteLine("Radio is now off!"); on_off = false; } } class AlarmClock { private int currentTime; private int alarmTime; private { Console.WriteLine("Buzz!"); } public void Run() { void SoundAlarm()
271
for (int currTime = 0; currTime < 43200; currTime++) { SetCurrentTime(currTime); if (GetCurrentTime() == GetAlarmTime()) { Console.WriteLine("Current Time = {0}!", currentTime); SoundAlarm(); break; } } } public int GetCurrentTime() { return currentTime; } public void SetCurrentTime(int aTime) { currentTime = aTime; } public int GetAlarmTime() { return alarmTime; ) public void SetAlarmTime(int aTime) { alarmTime = aTime; } }
Como queremos reemplazar el mtodo S o u n d A l a r m ( ) de A l a r m C l o c k , es recomendable hacer que C l o c k R a d i o herede de A l a r m C l o c k . Esto requiere un pequeo cambio en la implementacin de A l a r m C l o c k . Sin embargo, a cambio se consiguen todos los beneficios del polimorfismo. Una vez que se ha seleccionado una clase base, no podemos heredar de R a d i o . En lugar de heredar, crearemos una variable miembro privada de tipo Radio dentro de la clase C l o c k R a d i o . Creamos el miembro privado en el constructor C l o c k R a d i o y delegamos el trabajo de los mtodos RadioOn() y R a d i o O f f ( ) en este miembro privado. Cada vez que la implementacin de la clase R a d i o cambie (por ejemplo, para reparar algn error), la clase AlarmClock incorporar automticamente estos cambios. Un inconveniente del enfoque contencin/delegacin es que para aadir una nueva funcionalidad de la clase contenida (por ejemplo, aadir nuevos mtodos para ajustar el volumen) es necesario hacer cambios en la clase contenida para delegar esta nueva funcionalidad en el miembro privado.
class ClockRadio : AlarmClock {
272
miembro...
public ClockRadio() { radio = new Radio(); // Establecer el valor de otras variables } //---------- Delegar en public void RadioOn() { radio.On(); } public void RadioOff() { radio.Off(); } } Radio ----------
miembro...
Ya hemos implementado completamente la funcionalidad de la radio mediante el patrn contencin/delegacin. Es hora de aadir la funcionalidad AlarmClock. En primer lugar, se aade una variable privada r a d i o A l a r m que determina si debe sonar la radio o si debe sonar el timbre cuando se dispare la alarma:
class ClockRadio : AlarmClock { private bool radioAlarm; // Declarar otras variables miembro... public ClockRadio() { radioAlarm = false; // Establecer el valor de otras variables miembro... } //---------- Nueva funcionalidad ClockRadio---------public void SetRadioAlarm(bool useRadio) { radioAlarm = useRadio; } }
Como queremos reemplazar la funcin S o u n d A l a r m ( ) de A l a r m C l o c k , necesitamos cambiar la declaracin del mtodo S o u n d A l a r m ( ) para que sea protegida. Adems, como queremos que la funcin R u n ( ) tenga un comportamiento polimrfico, tendremos que hacer este mtodo virtual:
class AlarmClock { private int currentTime; private int alarmTime;
273
Reemplazar S o u n d A l a r m ( ) en A l a r m C l o c k es sencillo. Dependiendo de los valores de r a d i o A l a r m , se enciende la radio o se llama al mtodo SoundAlarm() de la clase base que hace sonar el timbre, como sigue:
ClockRadio : AlarmClock { private Radio radio; private bool radioAlarm; //---------- AlarmClock Reemplazado ---------protected override void SoundAlarm() { if (radioAlarm) { RadioOn(); } else { base.SoundAlarm(); } } // Otros mtodos... }
Y en esto consiste bsicamente! Algo muy interesante est ocurriendo dentro del mtodo Run() de la clase A l a r m C l o c k (que aparece en el siguiente fragmento de cdigo): el comportamiento polimrfico al que aludamos. La clase C l o c k R a d i o hereda este mtodo de su clase base y no lo reemplaza. Por tanto, este mtodo Run() puede ser ejecutado desde un objeto de A l a r m C l o c k o un objeto de R a d i o C l o c k . Como declaramos S o u n d A l a r m ( ) de modo que fuese virtual, C# es lo suficientemente inteligente como para llamar al S o u n d A l a r m ( ) apropiado dependiendo de qu clase est invocando al mtodo Run().
class AlarmClock { private int currentTime; private int alarmTime; public void Run() { for (int currTime = 0; currTime < 4 32 00; currTime++)
274
{ SetCurrentTime(currTime); if (GetCurrentTime() == GetAlarmTime()) { Console.WriteLine("Current Time = {0}!", SoundAlarm(); break; } } } // Otros mtodos... }
currentTime);
Este ejemplo resalta uno de los puntos fuertes de la herencia: el polimorfismo. Adems, cuando se aaden nuevos mtodos pblicos (o protegidos) a la clase base, stos quedan automticamente disponibles en la clase derivada. El listado 11.7 es el listado completo con un mtodo m a i n ( ) de ejemplo para que pueda experimentar con el.
Listado 11.7. La herencia mltiple puede ser simulada usando la contencin using System; namespace Containment { class Radio { protected bool on_off; public void On() { if (!on_off) Console.WriteLine("Radio is now on!"); on_off = true; } public void Off() { if (on_off) Console.WriteLine("Radio is now off!"); on_off = false; } } class AlarmClock { private int currentTime; private int alarmTime; protected virtual void SoundAlarm() { Console.WriteLine("Buzz!"); }
275
public void Run() { for (int currTime = 0; currTime < 43200; currTime++) { SetCurrentTime(currTime); if (GetCurrentTime() == GetAlarmTime()) { Console.WriteLine("Current Time = {0}!", currentTime); SoundAlarm(); break; } } } public int GetCurrentTime() { return currentTime; } public void SetCurrentTime(int aTime) { currentTime = aTime; } public int GetAlarmTime() { return alarmTime; } public void SetAlarmTime(int aTime) { alarmTime = aTime; } } class ClockRadio : AlarmClock { private Radio radio; private bool radioAlarm; public { radio = new Radio(); radioAlarm = false; } //---------- Delegar en public void RadioOn() { radio.On(); } Radio ---------ClockRadio()
276
public void RadioOff() { radio.Off(); } //---------- AlarmClock Reemplazado --------protected override void SoundAlarm() { if (radioAlarm) { RadioOn(); } else ( base.SoundAlarm(); } } //---------- Nueva funcionalidad de ClockRadio ---------public void SetRadioAlarm(bool useRadio) { radioAlarm = useRadio; } } class ContInh { static int Main(string[] args) { ClockRadio clockRadio;
clockRadio = new ClockRadio() ; clockRadio.SetRadioAlarm(true); clockRadio.SetAlarmTime(100); clockRadio.Run(); // esperar a que el usuario compruebe los resultados Console.WriteLine("Hit Enter to terminate..."); Console.Read(); return 0; } } }
277
una clase base para ella, el compilador de C# la deriva de object sin ningn aviso. Supongamos que escribimos una declaracin de clase de C# sin una declaracin de clase, como en este ejemplo:
class Point2D
La palabra clave object puede usarse como si fuera un alias del identificador System.Object:
class Point2D : object
Si se deriva desde una clase base, hay que tener en cuenta que la clase base se deriva desde object o desde otra clase base que herede de object. Al final, la jerarqua de las clases base siempre incluye la clase .NET object. Gracias a las reglas de herencia de C#, la funcionalidad de la clase .NET object est disponible para todas las clases de C#. La clase .NET object contiene los siguientes mtodos: public virtual bool Equals(object o b j ) : Compara dos objetos y devuelve t r u e si son iguales y f a l s e en caso contrario. Este mtodo est marcado como virtual, lo que significa que se puede reemplazar en las clases de C#. Quizs quiera reemplazar este mtodo para comparar el estado de dos objetos de la clase. Si los objetos tienen los mismos valores para los campos, puede devolver t r u e ; si los valores son diferentes puede devolver f a l s e . public virtual int GetHashCode() : Calcula un cdigo hash para el objeto. Este mtodo est 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 mtodo para generar un cdigo hash que ayude en las consultas y clasificaciones, y las clases pueden reemplazar este mtodo para que genere un cdigo hash que tenga sentido para la clase.
NOTA: El cdigo hash es una clave nica para el objeto especificado. public Type GetType(): Devuelve un objeto de una clase .NET llamada Type que proporciona informacin sobre la clase actual. Este mtodo no est marcado como virtual, lo que significa que no se puede reemplazar en las clases de C#. public virtual string ToString() : Devuelve una representacin de cadena del objeto. Este mtodo est marcado como virtual, lo que
278
significa que se puede reemplazar en las clases de C#. Un mtodo T o S t r i n g ( ) es invocado cuando algn mtodo .NET como System.Console.WriteLine() necesita convertir una variable en una cadena. Se puede reemplazar este mtodo para que devuelva una cadena ms apropiada para representar el estado de una clase determinada. Quizs quiera, por ejemplo, aadir el signo adecuado a cada divisa junto a la representacin de cadena de una clase llamada Money. protected virtual void Finalize() : Puede ser llamado (o puede no serlo) cuando el recolector de objetos no utilizados del entorno comn de ejecucin destruye el objeto. Este mtodo est marcado como virtual, lo que significa que se puede reemplazar en las clases de C#. Tambin est marcado como protegido, lo que significa que slo puede ser llamado desde dentro de la clase o desde una clase derivada y no puede ser llamado desde fuera de una jerarqua de clase. La implementacin del objeto Finalize() de .NET no hace nada, pero puede implementarlo si quiere. Tambin puede escribir un destructor para su clase, lo que produce el mismo efecto (pero tenga cuidado al usarlo). De hecho, el compilador de C# convierte el cdigo destructor en un mtodo reemplazado Finalize(). protected object MemberwiseClone(): Crea una copia idntica del objeto, asigna a la copia el mismo estado que el objeto original y devuelve el objeto copiado. Este mtodo no est marcado como virtual, lo que significa que no se puede reemplazar en las clases de C#. Tambin est marcado como protegido, lo que significa que slo puede ser llamado desde dentro de la clase o desde una clase derivada y no puede ser llamado desde fuera de una jerarqua de clase.
Las estructuras de C# no pueden tener clases bases definidas explcitamente, pero se derivan implcitamente de la clase base object . Todo el comportamiento de la clase object est disponible para las estructuras y las clases de C#.
Cmo usar boxing y unboxing para convertir a tipo object y desde el tipo object
Como todas las clases y estructuras derivan en ltima instancia del tipo object de .NET, ste suele usarse a menudo en listas de parmetros cuando el mtodo necesita ser flexible respecto a los datos que recibe. Observe, por ejemplo, el mtodo System.Console.WriteLine() usado en este libro. Este mismo mtodo ha sido usado para escribir cadenas, enteros y tipos dobles en la consola sin usar ningn operador de conversin explcita. En el listado 11.4 se escribe una cadena con comodines y los comodines son sustituidos por los valores de los parmetros que se le proporcionan. Cmo funciona
279
en realidad? Cmo sabe System.Console.WriteLine() qu tipos le van a pasar? La respuesta es que no puede saberlo. Microsoft construy el mtodo System.Console.WriteLine() mucho antes de que trabajsemos con el listado 11.4, por lo que no poda saber qu tipos de datos le pasara. Microsoft implement un mtodo System.Console.WriteLine() con la siguiente forma:
public static void WriteLine(string format, params object[] arg);
El primer parmetro es la cadena que se va a generar y el segundo parmetro es una matriz de parmetros que contiene una cantidad de elementos que se calcula cuando se compila el cdigo. Pero cul es el tipo de la matriz de parmetros? La matriz de parmetros es del tipo o b j e c t . Observe esta llamada a
WriteLine():
System.Console.WriteLine("({0}, {1})", X, Y);
El compilador de C# convierte los parmetros X e Y en una matriz de parmetros y llama a WriteLine(). Los parmetros X e Y son de tipo entero, lo que, como ya ha visto, es un alias de una estructura llamada System.Int32 . Como las estructuras de C# heredan del tipo de object de .NET, estas variables heredan del tipo object y pueden ser usadas en una matriz de parmetros. Los literales, que se han estudiado con anterioridad, son algo ms complicados. En lugar de usar objetos, puede igualmente escribir el siguiente cdigo:
System.Console.WriteLine("({0}, {1})", 100, 200);
Este cdigo tambin funciona correctamente. Cmo sabe C# cmo convertir un valor literal en un objeto para que pueda ser usado en una llamada de mtodo que necesite un objeto? La respuesta est en una tcnica llamada boxing. La tcnica de boxing permite que cualquier tipo de valor, incluso un literal, se pueda convertir en un objeto. Cuando el compilador de C# encuentra un tipo de valor para el que se necesita un tipo de referencia, crea una variable de objeto temporal y le asigna el valor del tipo de valor. Esta tcnica "encierra" el valor en un objeto. Observe nuevamente la anterior llamada WriteLine():
System.Console.WriteLine("({0}, {1})", 100, 200);
El compilador de C# encuentra los literales y los encierra en objetos. Los objetos se envan a la llamada del mtodo en la matriz de parmetros y a continuacin se eliminan los objetos temporales. Observe las siguientes instrucciones:
int MyValue = 123; object MyObject = MyValue;
280
C# tambin permite la tcnica de unboxing, que es simplemente el proceso opuesto al boxing. El unboxing 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 mtodos de la clase object pueden llamarlos. El siguiente cdigo funciona gracias a la tcnica de boxing del compilador de C#:
string MyString; MyString = 123.ToString();
El compilador de C# aplica la operacin boxing al valor literal 123, transformndola en un objeto y llama al mtodo ToString() sobre ese objeto.
Resumen
En la terminologa de la programacin de software orientada a objetos, la herencia se usa para 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 mltiple, que s es admitida por algunos lenguajes orientados a objetos para permitir que una clase pueda derivarse de ms de una clase base. Las clases base de C# se especifican al declarar una clase. El identificador de clase base sigue al nombre de la clase derivada cuando se declara la clase. C# permite que los miembros de clase pertenezcan a un atributo de mbito. El mbito de un miembro determina su accesibilidad para las clases derivadas y los fragmentos de cdigo que trabajan con los objetos de la clase. Los miembros de clase marcados como public son visibles para las clases derivadas y para el cdigo que crea los objetos de la clase. Los miembros de clase marcados como private slo son visibles para la clase en la que estn definidos o desde clases derivadas de la clase. Los miembros de clase marcados como internal son visibles para todo el cdigo en su mismo archivo binario, pero no son visibles fuera de los archivos binarios. Los miembros de clase marcados como protected internal son visibles para cualquier cdigo en su mismo archivo binario y para las clases externas que se deriven de la clase. Los miembros de clase que no tienen ninguna palabra clave de mbito son, por defecto, privados. Los mtodos y las propiedades de clases base pueden implementarse de nuevo en clases derivadas para proporcionar nuevas implementaciones. Los mtodos y propiedades virtuales, que estn marcados con la palabra clave de C# virtual , pueden implementarse nuevamente en clases derivadas, siempre que la nueva implementacin mantenga el mismo identificador, tipo devuelto y lista de parmetros. Las nuevas implementaciones de mtodos virtuales reciben el nombre de reemplazadas y deben estar marcadas con la palabra clave de C#
override.
281
Los mtodos abstractos son mtodos de clases base que no pueden ser implementados. Los mtodos abstractos no suelen implementarse en clases base porque la clase base no tiene suficiente informacin como para ofrecer una implementacin completa. Las clases que contienen al menos un mtodo abstracto reciben el nombre de clases abstractas y deben usar la palabra clave abstract en la declaracin de la clase. C# dispone de la palabra clave base que permite que las clases derivadas accedan a los miembros de una clase base. Se puede anteponer a los identificadores de miembros la palabra clave base y se puede usar esta palabra clave para llamar a un constructor de clase base desde un constructor de clase derivada. Por defecto, se puede usar cualquier clase de C# como clase base y cualquier clase puede derivarse de cualquier otra clase. Puede evitar este comportamiento marcando una clase de C# con la palabra clave sealed . Las clases selladas no pueden usarse como clase base y el compilador de C# no permite que se deriven clases a partir de clases selladas. Todas las clases de C# y, de hecho, cualquier clase implementada en un lenguaje .NET, deriva en ltima instancia de la clase .NET System.Object . La palabra clave de C# object es otro nombre para el identificador de clase System.Object. La clase System.Object contiene algunos mtodos que pueden usar las clases derivadas y muchos de estos mtodos pueden ser reemplazados. La clase System.Object proporciona funcionalidad para definir igualdad de objetos, clculo de cdigo hash, representaciones de cadenas, finalizacin de cdigo y clonacin de objetos. El tipo object puede ser usado como un mtodo o variable de nombre de tipo, y cualquier variable de C# puede usarse como un objeto object. C# convierte automticamente algunos tipos de valor, como tipos de valor y literales numricos y objetos de tipo o b j e c t , mediante las tcnicas de boxing y unboxing. La tcnica de Boxing encierra un valor en un objeto y la tcnica de unboxing devuelve el valor del objeto a su tipo de valor original.
282
Parte III
C# avanzado
283
Ahora supongamos que empaqueta las clases en un ensamblado .NET y distribuye ese ensamblado para que sea usado por otras aplicaciones. Adems, suponga que alguien consigue su ensamblado y lo integra en su aplicacin. Qu ocurrir si la misma aplicacin tambin hace uso de otro ensamblado escrito por otra persona y que tambin contiene una clase llamada R e c o r d s e t ? Cuando el cdigo de la aplicacin crea un nuevo objeto R e c o r d s e t , qu clase se usa para crear el objeto? La nuestra o la clase del otro ensamblado? Este problema puede resolverse mediante el concepto de C# de los espacios de nombres. Los espacios
285
de nombres organizan las clases mediante un grupo con un nombre, y el nombre del espacio de nombres puede ser usado para diferenciar dos clases con el mismo nombre. El cdigo C# debe usar espacios de nombres para posteriormente ayudar a identificar nuestras clases mediante un grupo comn, especialmente si est planeando construir un ensamblado para que sea usado por otros programadores. Los espacios de nombres pueden incluso ser tiles en las aplicaciones de C# que construyamos, porque de este modo pueden usar ensamblados externos que usen nombres de clase iguales a los nuestros.
Este fragmento de cdigo declara una clase llamada M y F i r s t C l a s s en un espacio de nombres llamado M y C l a s s e s . Otro programador tambin podra escribir otra clase llamada M y F i r s t C l a s s , pero mientras el otro programador use un espacio de nombres diferente, el compilador de C# encontrar la clase adecuada que debe usar para una instruccin particular. Es posible declarar espacios de nombres dentro de otros espacios de nombres. Basta con encerrar la declaracin del segundo espacio de nombres en el interior de la primera declaracin:
namespace MyClasses { namespace MyInnerNamespace { class MyFirstClass { } } }
TRUCO: es aconsejable anidar los espacios de nombres cuando se va a ofrecer ms de un producto distinto en forma de clase. Por ejemplo, la compaa Widget Corporation ofrece un producto de compresin y algunas
286
rutinas de emulacin de terminales. Estos espacios de nombres seran Widget.Compression y Widget.Emulation , que agrupan los productos de la compaa pero tambin los mantiene separados mediante el espacio de nombres Widget. Si no quiere anidar los espacios de nombres de esta manera, puede conseguir el mismo efecto declarando las dos declaraciones de espacio de nombres en la misma instruccin y separndolas con un punto, como se indica a continuacin:
namespace MyClasses.MyInnerNamespace { class MyFirstClass { } }
Los siguientes tipos de declaraciones pueden aparecer en un espacio de nombres: Clases Estructuras Interfaces Enumeraciones Delegados
Cualquier declaracin de un tipo que no est en esta lista produce errores del compilador cuando se intenta compilar la aplicacin.
287
class MyFirstClass { } }
El segundo archivo fuente puede contener la declaracin de la segunda clase y puede usar el mismo nombre de espacio de nombres:
namespace MyAssembly { class MySecondClass { } }
Cuando los dos archivos fuente se construyen en un solo ensamblado, el compilador de C# crea un ensamblado con un solo espacio de nombres, MyAssembly, con dos clases en el espacio de nombres. Esto tiene una ventaja para el programador en caso de que quiera separar algunas funcionalidades en distintos archivos o, simplemente, si quiere reducir al mnimo la longitud de cada archivo fuente.
Esta sintaxis ayuda a distinguir entre las clases de diferentes cdigos base con el mismo nombre. El compilador de C# ya tiene suficiente informacin para encontrar la clase correcta, porque tambin sabe a qu espacio de nombres debe dirigirse para encontrar las clases que estamos buscando. Cuando se trabaja con clases declaradas en espacios de nombres anidados, deben aparecer todos los nombres de espacios de nombres cuando se hace referencia a esa clase:
Namespace1.Namespace2.MyClass MyObject = new Namespace1.Namespace2.MyClass();
288
public TestClass() { System.Console.WriteLine("Hello Namespace1.TestClass!"); } } } namespace Namespace2 { class TestClass { public TestClass() { System.Console.WriteLine("Hello Namespace2.TestClass!"); } } }
from
from
class MainClass { public static void Main() { Namespace1.TestClass Object1 = new Namespace1.TestClass(); Namespace2.TestClass Object2 = new Namespace2.TestClass(); } }
El cdigo 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 nombres diferente y el constructor de cada clase escribe un mensaje en la consola. Los mensajes son ligeramente diferentes, de modo que se puede saber cul es el mensaje que emite cada clase. El mtodo M a i n ( ) del listado 12.1 crea dos objetos: uno de tipo N a m e s p a c e 1 . T e s t C l a s s y 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 cdigo 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 declaracin de espacio de nombres. Esto es perfectamente vlido en C#. No es necesario encerrar las clases en declaraciones de espacio de nombres. Sin embargo, las clases que no estn encerradas en espacios de nombres no pueden usar el mismo nombre en otra clase definida sin un espacio de nombres. Si necesita usar una clase que est declarada en un espacio de nombres, debe usar el nombre de su espacio de nombres 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 mtodo Main () del listado 12.1 intenta crear un objeto de la clase TestClass:
TestClass Object1 = new TestClass();
289
c:\ C:\WINDOWS\System32\cmd.exe C:\>Listing12-1.exe. Hello from Namespace1.TestClass! Hello from Namespace2.TestClass! C:\>
El compilador de C# no puede encontrar una clase llamada T e s t C l a s s definida fuera de un espacio de nombres 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 'MainClass' (falta una referencia de ensamblado?)
Si revisa los ejemplos de los captulos anteriores, comprobar que sta es la sintaxis que hemos estado usando en todas nuestras llamadas a W r i t e L i n e ( ) , como muestra el siguiente ejemplo:
System.Console.WriteLine("Hello from C#!");
El mtodo W r i t e L i n e ( ) est en una clase llamada C o n s o l e y la clase Console est definida en un espacio de nombres de .NET llamado System.
290
de usar la palabra clave using para crear un alias para el identificador de clase completo y, cuando se haya establecido el alias, puede usarlo en lugar del identificador de clase completo. Puede crear un alias mediante una instruccin que tenga la siguiente estructura: La palabra clave using El nombre del alias Un signo igual El nombre de la clase completa con el identificador de espacio de nombres Un punto y coma de fin de instruccin
El listado 12.2 se aade al listado 12.1 creando alias para los nombres de clase y acortando as sus equivalentes. El mtodo M a i n ( ) utiliza los nombres acortados para trabajar con los objetos de las clases.
Listado 12.2. Cmo crear alias de los nombres de clase using Class1 = Namespace1.TestClass; using Class2 = Namespace2.TestClass; namespace Namespace1 { class TestClass { public TestClass() { System.Console.WriteLine("Hello Namespace1.TestClass!"); } } } namespace Namespace2 { class TestClass { public TestClass() { System.Console.WriteLine("Hello Namespace2.TestClass!"); } } } class MainClass { public static void Main() {
from
from
291
El listado 12.2 escribe los mismos mensajes que el anterior ejemplo. Puede ver estos resultados en la figura 12.2.
c:\ C:\WINDOWS\System32\cmd.exe C:\>Listing12-2.exe Hello from Namespace1.TestClass! Hello from Namespace2.TestClass! C:\>
Las instrucciones using deben incluirse en el cdigo fuente antes de que se declaren los espacios de nombres. Si aparecen despus de las declaraciones de espacio de nombres, recibir el siguiente mensaje de error del compilador de C#:
error CS1529: Una clusula using debe ir delante de todos los elementos restantes del espacio de nombres
En captulos anteriores ya vimos que las palabras clave de C# que definen los tipos de variable son en realidad estructuras definidas por .NET Framework. Observe de nuevo la tabla 7.1 y preste atencin a lo siguiente: Las estructuras de tipo de valor residen en el espacio de nombres System de .NET. La palabra clave using se usa para crear alias de los nombres de estructuras de .NET con las palabras clave de C# equivalentes. Puede imaginar cmo se implementa la tabla 7.1 en .NET Framework mediante instrucciones de C# como las siguientes:
using sbyte = System.SByte; using byte = System.Byte; using short = System.Int16; // ... ms declaraciones ...
292
Puede crear alias de nombres de espacio de nombres as como de clases, como demuestra el listado 12.3.
Listado 12.3. Creacin de alias de espacios de nombres using N1 = Namespace1; using N2 = Namespace2; namespace Namespace1 { class TestClass { public TestClass() { System.Console.WriteLine("Hello Namespace1.TestClass!"); } } } namespace Namespace2 { class TestClass { public TestClass() { System.Console.WriteLine("Hello Namespace2.TestClass!"); } } }
from
from
class MainClass { public static void Main() { N1.TestClass Object1 = new N1.TestClass(); N2.TestClass Object2 = new N2.TestClass(); } }
293
Anteponer cada nombre de clase con nombres de espacios de nombres como S y s t e m es tedioso, especialmente si hay que hacerlo muchas veces. Afortunadamente, se puede usar la palabra clave using para reducir el tiempo de codificacin. Al usar la palabra clave using con un nombre de espacio de nombres, se advierte al compilador de C# que se quiere hacer referencia a clases en el espacio de nombres designado sin anteponer a los nombres de clase el nombre de espacio de nombres. Observe, por ejemplo, la siguiente instruccin:
using System;
Esto recibe el nombre de directiva de espacio de nombres. Las directivas de espacio de nombres avisan al compilador de C# de que el cdigo usar clases del espacio de nombres y que las clases no llevarn antepuesto el nombre del espacio de nombres. El compilador de C# se encarga de encontrar la definicin de cada clase en cada espacio de nombres al que se hace referencia en una directiva de espacio de nombres. El listado 12.4 es una modificacin del listado 12.2; incluye una instruccin using que hace referencia al espacio de nombres System de .NET.
Listado 12.4. Cmo usar una directiva de espacio de nombres using System; using Class1 = Namespace1.TestClass; using Class2 = Namespace2.TestClass; namespace Namespace1 { class TestClass { public TestClass () { Console.WriteLine("Hello } } } namespace Namespace2 { class TestClass { public TestClass()
from Namespace1.TestClass!");
294
{ Console.WriteLine("Hello } } } class MainClass { public static void Main() { Class1 Object1 = new Class1(); Class2 Object2 = new Class2(); } } from Namespace2.TestClass!");
La directiva de espacio de nombres S y s t e m del listado 12.4 permite que el cdigo haga referencia a la clase C o n s o l e sin que se le anteponga el espacio de nombres System.
295
El espacio de nombres System.Data contiene clases que componen la arquitectura de acceso a datos de ADO.NET. La arquitectura ADO.NET permite construir componentes que pueden gestionar datos de varias fuentes de datos en modo desconectado o conectado. El espacio de nombres System.Diagnostics contiene clases que ayudan a detectar errores en aplicaciones de .NET y supervisar la ejecucin del cdigo. El espacio de nombres System.Diagnostics tambin contiene clases que permiten supervisar la actuacin de la aplicacin mediante contadores de rendimiento y registros de eventos. Aunque la funcionalidad no se considera realmente un diagnstico, este espacio de nombres tambin permite iniciar y detener procesos. El espacio de nombres System.Drawing contiene clases que implementan funcionalidad de dibujo de la Interfaz del dispositivo grfico (GDI). Este espacio de nombres no est disponible por defecto; hay que crear una referencia a l desde el men Proyecto . El espacio de nombres System.IO contiene clases que pueden leer y escribir flujos de datos y archivos de disco. Las clases contenidas en este espacio de nombres pueden gestionar la entrada y salida de archivos, ya sea sncrona o asncrona. El espacio de nombres System.Messaging contiene clases que trabajan con colas de mensajes. Este espacio de nombres no est disponible por defecto: hay que crear una referencia a l desde el men Proyecto. El espacio de nombres System.Net contiene clases que proporcionan un contenedor de clases para los muchos protocolos que se utilizan actualmente en las redes. Este espacio de nombres consta de clases para gestionar peticiones de DNS, HTTP y peticiones de FTP. Adems de las clases generales de acceso a redes, tambin hay muchas clases de seguridad de redes que tratan los diferentes aspectos de la seguridad, desde acceso a sitios Web hasta acceso a nivel de sockets. El espacio de nombres System.Reflection contiene clases que proporcionan una vista de tipos, mtodos y campos disponibles para una aplicacin de .NET. Incluso es posible crear e invocar tipos dinmicamente en tiempo de ejecucin usando las clases del espacio de nombres
System.Reflection.
El espacio de nombres System.Resources proporciona clases que permiten a los programadores crear, almacenar y administrar recursos especficos de las referencias culturales que se utilizan en las aplicaciones.
El espacio de nombres System.Runtime no es muy til por s mismo. Sin embargo, dispone de docenas de clases que proporcionan una enorme
296
funcionalidad. Por ejemplo, System.Runtime.InteropServices permite el acceso a objetos COM y a los API nativos desde .NET. El espacio de nombres System.Security contiene clases que permiten el acceso a la estructura subyacente de seguridad de .NET Framework. El espacio de nombres de seguridad es el punto de partida para otros espacios de nombres ms avanzados de muchos servicios de cifrado. Estos servicios incluyen el cifrado y descifrado de datos, generacin de hash y generacin de nmeros aleatorios. El espacio de nombres System.Text contiene clases que permiten trabajar con codificaciones de caracteres ASCII, Unicode, UTF-7 y UTF-8. El espacio de nombres System.Threading contiene clases que permiten implementar varios subprocesos del sistema operativo en las aplicaciones .NET, creando as una autntica aplicacin multiproceso. El espacio de nombres System.Timers contiene clases que permiten desencadenar un evento en un intervalo de tiempo determinado o en unos plazos ms complejos. Estos temporizadores se basan en el servidor. Un temporizador basado en un servidor tiene la capacidad de moverse entre los subprocesos para iniciar el evento, lo que proporciona una flexibilidad mayor que el temporizador tpico de Windows. El espacio de nombres System.Web contiene clases que implementan el protocolo de transmisin de hipertexto (HTTP) que utilizan los clientes Web para acceder a pginas de Internet. Este espacio de nombres no est disponible por defecto; hay que crear una referencia a l desde el men Proyecto. El espacio de nombres System.Windows.Forms contiene clases para crear aplicaciones completas para Windows. Las clases del espacio de nombres System.Windows.Forms proporcionan un entorno de clases .NET con los controles tpicos de Windows, como cuadros de dilogo, mens y botones. Este espacio de nombres no est disponible por defecto; hay que crear una referencia a l desde el men Proyecto. El espacio de nombres System.Xml contiene clases que pueden procesar datos XML. Este espacio de nombres incluye compatibilidad con espacios de nombres XML 1.0, XML, esquemas XML, XPath, XSL y XSLT, DOM Level 2, y SOAP 1.1.
Aunque no es una lista completa, debera darle una idea de la inmensa cantidad de espacios de nombres ya implementados por .NET Framework. Consulte la documentacin del SDK de .NET Framework para conseguir una lista completa de espacios de nombres y clases.
297
Resumen
Las clases y estructuras que desarrolle se pueden encapsular en los llamados espacios de nombres. Los espacios de nombres ayudan a diferenciar unas clases y estructuras de otras que tengan el mismo nombre. Una clase o estructura completa incluye el nombre del espacio de nombres que alberga a la clase o estructura. Cuando se hace referencia a una clase o estructura en un espacio de nombres, hay que cualificar el nombre anteponiendo el nombre del espacio de nombres y un punto. Se puede usar la palabra clave using para facilitar el trabajo con los nombres de espacios de nombres en el cdigo de C#. La palabra clave using puede usarse para proporcionar un alias para una clase particular en un espacio de nombres concreto. Tambin puede usarse como una directiva de espacio de nombres, que avisa al compilador de C# de que nuestro cdigo va a hacer referencia a clases en un espacio de nombres especfico y de que el cdigo no antepondr el identificador de espacio de nombres a las clases de ese espacio de nombres. Puede crear sus propios espacios de nombres y puede usar el cdigo incluido en espacios de nombres desarrollados por otras personas mediante las tcnicas reseadas en este captulo. .NET Framework incluye una gran cantidad de espacios de nombres llenos de clases, lo que facilita la labor de codificar cualquier cosa, desde aplicaciones Windows hasta procesadores de XML y programas de seguridad. Los espacios de nombres de .NET Framework tambin proporcionan clases que pueden usarse para crear cdigo C# mediante tcnicas avanzadas, como el software de multiproceso y la reflexin.
298
13 Interfaces
Una interfaz de C# es un conjunto de firmas de mtodos, propiedades, eventos o indizadores agrupados con un nombre comn. Las interfaces funcionan como conjuntos de funcionalidades definidas que pueden implementarse en una clase o estructura de C#. Las clases o estructuras que implementa una interfaz proporcionan implementaciones para todos los mtodos definidos en la interfaz. Supongamos, por ejemplo, que queremos que las clases de nuestro proyecto puedan guardar los valores de sus campos en una base de datos y recuperarlos ms tarde. Al implementar este requisito, podramos decidir que todas las clases deban implementar un mtodo llamado L o a d ( ) y otro llamado S a v e ( ) . Tambin podramos definir los mtodos en una interfaz llamada IPersistToDisk (los nombres de una interfaz comienzan normalmente con la letra I, aunque no es obligatorio en C#) y exigir que nuestras clases implementen esta interfaz. El cdigo de C# puede consultar un objeto para determinar si admite una interfaz. Consultar a un objeto acerca de una interfaz es bsicamente hacer la pregunta "Admites esta interfaz?". El objeto responde "S" o "No". Si el objeto responde "S", se puede llamar al mtodo en la interfaz. Los mtodos llamados en una interfaz siempre tienen la misma lista de parmetros y valores devueltos, aunque sus implementaciones pueden ser diferentes. Imagine una interfaz como un contrato, una promesa de que una clase implementar un conjunto especfico de funcionalidades. Si un objeto responde
301
"S, yo admito la interfaz por la que est preguntando", est asegurando que proporciona una implementacin para cada uno de los mtodos definidos en la interfaz. Las interfaces no proporcionan sus propias implementaciones de mtodos. Slo proporcionan identificadores de mtodos, listas de parmetros y cdigos devueltos. Las clases que implementan la interfaz son las responsables de proporcionar una implementacin. Dos clases que implementan la misma interfaz pueden implementar los mtodos de la interfaz de modos muy distintos. Esto es correcto, siempre que las clases sigan definiendo las firmas de los mtodos en la definicin de la interfaz. Vamos a usar la interfaz IPersistToDisk como ejemplo. Puede tener objetos en su aplicacin que necesiten abrir su estado desde un disco y guardar su estado de nuevo en un disco. Podra decidir implementar la interfaz IPersistToDisk en estos objetos. Para cada uno de los objetos que implementen IPersistToDisk, necesitar escribir cdigo para los mtodos L o a d ( ) y S a v e ( ) de la interfaz. Algunos de los objetos pueden tener necesidades de almacenamiento bsicas, de modo que esos objetos pueden implementar los mtodos S a v e ( ) y L o a d ( ) mediante un simple cdigo de E/S de disco. Otros objetos pueden ser ms complicados y necesitan compatibilidad con la E/S transaccional, en la que toda la operacin de persistencia debe tener xito o fracasar como un todo. Para esos objetos, quizs prefiera implementar los mtodos L o a d ( ) y S a v e ( ) usando cdigo transaccional, ms robusto. La clave est en que el cdigo que usan estos objetos no necesita saber si el objeto usa cdigo simple o transaccional en su implementacin. Slo pregunta a cada objeto "Admites el mtodo IPersistToDisk?". Para los objetos que responden "s", el cdigo que usa los objetos puede llamar a L o a d ( ) o S a v e ( ) sin necesidad de saber cmo estn implementados realmente esos mtodos. Conceptualmente, las interfaces son muy parecidas a clases base abstractas: las dos proporcionan una lista de mtodos que deben ser implementadas por otros fragmentos de cdigo. Sin embargo, hay una diferencia importante: las interfaces pueden ser implementadas sin importar la posicin de la clase de implementacin en una jerarqua de clases. Si se usan clases base abstractas, todas las clases que quieran implementar 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 necesitan derivarse de una clase base especfica antes de poder implementar una interfaz. El concepto de interfaz como modelo de diseo de software no es nueva; sin embargo, el modelo de objetos componentes (COM) de Microsoft populariz el concepto. COM trajo la idea de las interfaces (conjuntos especficos de funcionalidad implementados por un objeto) a la vanguardia del desarrollo de software basado en Windows. C# llev ms all el concepto, promoviendo el concepto como una caracterstica de lenguaje en el nivel de cdigo. Aunque las primeras versiones de C++ o Visual Basic ya tenan interfaces COM 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 programacin de interfaz sea compatible con el cdigo fuente y est disponible para el cdigo, aunque el cdigo no use COM. Este captulo le ensea a trabajar con interfaces usando C#. Aprender a definir una interfaz usando la palabra clave interface. Tambin aprender a definir e implementar mtodos, propiedades, indizadores y eventos en una interfaz y a acceder a una interfaz implementada por un objeto.
Las interfaces pueden definir mtodos, propiedades, indizadores y eventos. Estos constructores de lenguaje trabajan sobre las interfaces del mismo modo que trabajaran con las clases de C#. Los mtodos de interfaz definen bloques de cdigo con nombre; las propiedades definen variables que pueden ser validadas mediante cdigo descriptor de acceso, y los eventos definen acciones que pueden ocurrir en el cdigo.
303
La interfaz define dos mtodos, pero no proporciona ninguna implementacin para los mtodos. Las declaraciones de mtodo terminan con un punto y coma. Las clases de C# que implementa la interfaz IPersistToDisk prometen que proporcionarn una implementacin de los mtodos L o a d ( ) y S a v e ( ) tal y como se definen en la interfaz.
Para definir una propiedad de slo lectura en una interfaz, basta con incluir la palabra clave descriptora de acceso get:
interface Interface1 { int RecordCount { get; } }
Para definir una propiedad de slo escritura en una interfaz, basta con incluir la palabra clave descriptora de acceso set:
interface Interface1 { int RecordCount { set; } }
304
un indizador en una interfaz hay que indicar el tipo del indizador, la palabra clave this , la lista de parmetros del indizador entre corchetes y las palabras clave descriptoras de acceso seguidas de puntos y comas. Las palabras clave descriptoras de acceso aparecen entre llaves. La clase o estructura que implementa la interfaz es la responsable de proporcionar la implementacin de los descriptores de acceso del indizador. Para definir un indizador de lectura/escritura en una interfaz, use las palabras clave descriptoras de acceso get y set:
interface Interface1 { int this [int Index] { get; set; } }
Para definir un indizador de slo lectura en una interfaz, basta con incluir la palabra clave descriptora de acceso get:
interface Interface1 { int this [int Index] { get; } }
Para definir un indizador de de slo escritura en una interfaz, basta con incluir la palabra clave descriptora de acceso set:
interface Interface1 { int this [int Index] { set; } }
305
siguen al nombre de la interfaz derivada. A diferencia de las clases base, las interfaces pueden derivarse de ms de una interfaz base. Los diferentes nombres de interfaces base se separan por comas, como se puede apreciar en el siguiente ejemplo:
interface Interface1 { void Method1(); } interface Interface2 { void Method2(); } interface Interface3 : Interface1, Interface2 { }
Es til derivar de una interfaz base cuando una interfaz contiene un conjunto de firmas de mtodos, propiedades y eventos que deben ser aadidas a una interfaz ya programada. Por ejemplo, .NET Framework define varias interfaces que pueden implementarse y usarse en C#. Como las interfaces ya son una parte de .NET Framework, la lista de mtodos, propiedades, indizadores y eventos que admiten se ha consolidado y no puede cambiar. Si quiere usar una interfaz definida y necesita aadir ms firmas a la interfaz para uso propio, debera considerar la posibilidad de derivar desde la interfaz ya definida y aadir sus nuevas firmas en la interfaz derivada. Las clases que implementan una interfaz derivada deben proporcionar implementaciones para todos los mtodos definidos por las interfaces base. Cuando una clase implementa una interfaz, debe proporcionar el cdigo para cada uno de los mtodos definidos en la interfaz. Si una clase implementa I n t e r f a c e 3 , por usar el ejemplo anterior, la clase debe proporcionar las implementaciones de mtodo para M e t h o d 1 ( ) y M e t h o d 2 ( ) . No se puede derivar una interfaz de ella misma. Observe la siguiente definicin de una interfaz:
interface Interface1 : { void Method1(); } Interface1
Este mensaje de error indica que el cdigo est intentando derivar una interfaz de s misma, lo que no se permite en C#. Las interfaces slo pueden derivarse de otras interfaces.
306
Ahora suponga que quiere derivar de esa interfaz, pero le gustara usar el identificador ID como nombre de un mtodo:
interface DerivedInterface : Base Inter face { int ID(); }
Esta construccin hace que el compilador de C# emita un aviso sobre la reutilizacin del identificador ID:
warning CS0108: La palabra clave new es necesaria en 'DerivedInterface.ID()' porque oculta el miembro heredado
El compilador est avisando 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 mtodo. La reutilizacin de este nombre puede confundir fcilmente a los usuarios de la interfaz. El aviso del compilador significa que el uso de la palabra clave ID en la interfaz derivada tiene prioridad sobre el uso de la palabra clave ID en la interfaz base. Si un fragmento de cdigo recibe una implementacin de la interfaz DerivedInterface , el cdigo ser incapaz de llamar al mtodo ID() pues no puede distinguirlo de la propiedad ID de la interfaz base. La reutilizacin del identificador ID en la interfaz derivada oculta 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 problema se puede usar la palabra clave new al reutilizar el identificador. El uso de la palabra clave new, mostrado en el siguiente ejemplo, indica al compilador de C# que se quiere dar un nuevo uso al smbolo reutilizado:
interface DerivedInterface : BaseInterface { new int ID ( ) ; }
307
Este cdigo define una interfaz llamada Interface1. El mtodo Interface1 declara un mtodo: un mtodo llamado M e t h o d 1 ( ) . El cdigo tambin declara una clase llamada MyClass , que implementa la interfaz Interface1. La clase MyClass incluye una implementacin para el mtodo M e t h o d 1 ( ) definido por la interfaz Interface1. Aunque la clase slo 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 separar cada interfaz con dos puntos:
class MyClass : Interface1, Interface2, Interface3
La implementacin de interfaz multiple se usa en todo .NET Framework. Por ejemplo, la clase System.String implementa cuatro interfaces definidas por .NET Framework: IComparable, que compara los valores de dos objetos del mismo tipo. ICloneable, que crea un nuevo objeto que tiene el mismo estado que otro objeto. IConvertible, que convierte el valor de un tipo a un valor de otro tipo. IEnumerable, que permite que el cdigo itere a travs de una coleccin.
308
Como la clase S y s t e m . S t r i n g implementa estas cuatro interfaces, la funcionalidad que cada una de las interfaces proporciona es compatible con la clase S y s t e m . S t r i n g . Esto significa que las cadenas pueden ser comparadas con otras cadenas, pueden ser clonadas, pueden convertirse en otros tipos y se puede iterar a travs de sus caracteres como si fuera una coleccin. El concepto de implementacin de interfaz mltiple tambin est disponible para cualquier programador de C#. C# tambin permite derivar una clase de una clase base e implementar interfaces al mismo tiempo:
class MyDerivedClass : CMyBaseClass, Interface1, Interface2
Las clases deben implementar cualquier declaracin de evento, mtodo, propiedad o indizador encontrado en una interfaz que implementen. En caso contrario, el compilador de C# emitir un error. El siguiente cdigo no funciona porque la clase M y C l a s s implementa Interface1 pero no proporciona una implementacin del mtodo Method1() definido en Interface1:
interface Interface1 { void Method1(); } class MyClass : Interface1 { public static void Main() { } }
La clase debe proporcionar una implementacin para el mtodo M e t h o d 1 ( ) definido por Interface1, dado que la clase implementa Interface1. El siguiente ejemplo corrige el error:
interface Interface1 { void Method1(); } class MyClass : Interface1 { public static void Main() { } public void Method1() { } }
309
Este cdigo no se puede compilar. El compilador de C# emite el siguiente mensaje que indica el error que produce:
error CS0536: 'MyClass' no implementa el miembro de interfaz 'Interface1.DoWork()'. MyClass.DoWork()' es esttico, no pblico, o tiene un tipo de valor devuelto incorrecto. error CS0536: 'MyClass' no implementa el miembro de interfaz 'Interface2.DoWork()'. MyClass.DoWork()' es esttico, no pblico, o tiene un tipo de valor devuelto incorrecto.
En los mensajes de error se muestra la sintaxis interfaz/nombre para recordar la sintaxis correcta de las implementaciones de clase. El problema es que la clase M y C l a s s necesita proporcionar cdigo de implementacin para el mtodo DoWork() definido por I n t e r f a c e 1 y del mtodo DoWork() definido por I n t e r f a c e 2 , y las dos interfaces reutilizan el nombre de mtodo DoWork(). Una clase de C# no puede incluir dos mtodos con el mismo nombre, de modo que cmo se pueden definir los dos mtodos de interfaz? La solucin es anteponer el nombre de la interfaz a la implementacin del mtodo y escribir un punto que separe el nombre de la interfaz del nombre de la implementacin, como se muestra a continuacin:
310
Esta clase se compila correctamente, ya que contiene dos implementaciones DoWork(), una por cada interfaz definida. Como los nombres de mtodo estn calificados con los nombres de interfaz, el compilador de C# puede distinguir uno de otro y puede verificar que las dos interfaces han sido implementadas en la clase.
311
Un identificador de objeto. La palabra clave is. Un identificador de interfaz. La expresin 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 muestra el funcionamiento de la palabra clave
is:
Listado 13.1. Cmo usar la palabra clave is para trabajar con una interfaz using System; public interface { void Print(); } IPrintMessage
class Class1 { public void Print() { Console.WriteLine("Hello from Class1!"); } } class Class2 : IPrintMessage { public void Print() { Console.WriteLine("Hello from Class2!"); } } class MainClass { public static void Main() { PrintClassPrintObject = new PrintClass(); PrintObject.PrintMessages(); } } class PrintClass { public void PrintMessages() { Class1 Object1 = new Class1(); Class2 Ob]ect2 = new Class2(); PrintMessageFromObject(Object1);
312
PrintMessageFromObject(Object2); } private void PrintMessageFromObject(object obj) { if(obj is IPrintMessage) { IPrintMessage PrintMessage; PrintMessage = (IPrintMessage) obj ; PrintMessage.Print(); } } }
El listado 13.1 define una interfaz llamada IPrintMessage . La interfaz IPrintMessage define un mtodo llamado Print . Al igual que todos los miembros de interfaz de C#, la interfaz IPrintMessage define miembros pero no los implementa. A continuacin el listado implementa dos clases de control llamadas Class1 y Class2 . La clase Class1 implementa un mtodo llamado c a l l e d Print() . Como Class1 no hereda de la interfaz IPrintMessage, el mtodo Print() implementado por la clase no tiene ninguna relacin con el mtodo Print() definido por la interfaz IPrintMessage. La clase Class2 implementa un mtodo llamado Print(). Como Class2 hereda de la interfaz IPrintMessage, el compilador de C# considera que el mtodo Print() implementado por la clase es una implementacin del mtodo Print() definido por la interfaz IPrintMessage. A continuacin el listado 13.1 define una clase llamada MainClass , que implementa el mtodo M a i n ( ) de la aplicacin, y otra clase llamada PrintClass . El mtodo M a i n ( ) del listado 13.1 crea un objeto de la clase PrintClass y llama a su mtodo pblico para que haga el trabajo de verdad. El listado 13.1 termina declarando una clase llamada PrintClass. La clase PrintClass implementa un mtodo pblico llamado PrintMessages() y un mtodo de ayuda privado llamado P r i n t M e s s a g e F r o m O b j e c t ( ) . El mtodo PrintMessages() es el mtodo al que llama el mtodo M a i n ( ) . Como PrintMessageFromObject() est marcado como privado, slo se le puede llamar desde otros fragmentos de cdigo del objeto PrintClass y no puede ser llamado desde el cdigo en otras clases. El mtodo PrintMessages() crea un objeto de clase Class1 y un objeto a partir de Class2 y pasa cada objeto al mtodo privado PrintMessageFromObject(). El mtodo privado PrintMessageFromObject() acepta un parmetro de tipo object como parmetro. NOTA: Es posible usar un parmetro de tipo object 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 representada en C# puede ser usada como parmetro para un mtodo que espere un tipo object porque todos los tipos son, en ltima instancia, objetos de System.Object. En la siguiente lnea del listado 13.1. el mtodo PrintMessageFromObject() comienza examinando el objeto para comprobar si implementa la interfaz IPrintMessage:
if (obj is IPrintMessage)
Si el objeto implementa la interfaz, la expresin booleana o b j is IPrintMessage devuelve T r u e y el cdigo situado por debajo de la condicin if se ejecuta. Si el objeto no implementa la interfaz, la expresin booleana obj is IPrintMessage devuelve F a l s e y el cdigo situado por debajo de la condicin if no se ejecuta. Si el objeto admite la interfaz, se puede acceder a la implementacin del objeto de la interfaz. Se puede acceder a la implementacin de la interfaz de un objeto declarando una variable del tipo de la interfaz y luego convirtiendo explcitamente el objeto al tipo de la interfaz, como se indica a continuacin:
IPrintMessage PrintMessage; PrintMessage = (IPrintMessage)obj;
Tras inicializar una variable del tipo de la interfaz, se puede acceder a los miembros de la interfaz usando la habitual notacin con punto:
PrintMessage.Print();
En el listado 13-2. se pasa Object1 al mtodo PrintMessageFromObject() y no se escribe nada en la consola porque el objeto Object1 es de la clase Class1 y Class1 no implementa la interfaz IPrintMessage. Cuando se pasa Object1 al mtodo PrintMessageFromObject() , se escribe en la consola el siguiente texto:
Hello from Class2!
Este mensaje aparece porque el objeto Object2 es de clase Class2 y Class2 implementa la interfaz IPrintMessage. Si se llama a la implementacin del objeto del mtodo Print de la interfaz se escribe el siguiente mensaje en la consola.
314
Una vez para consultar al objeto y comprobar si el objeto implementa una interfaz. Una vez para acceder a la implementacin de la interfaz del objeto usando el operador de conversin explcita. Se pueden combinar estos dos accesos mediante el operador as . El operador as realiza dos tarcas en una sola instruccin. El listado 13.2 es una versin modificada del listado 13.1 que usa la instruccin as en lugar de la instruccin
is:
Listado 13.2. Cmo usar la palabra clave as para trabajar con una interfaz using System; public interface { void Print(); }; IPrintMessage
class Class1 { public void Print() { Console.WriteLine("Hello from Class1!"); } } class Class2 : IPrintMessage { public void Print() { Console.WriteLine("Hello from Class2!"); } } class MainClass { public static void Main() { PrintClass PrintObject = new PrintClass(); PrintObject.PrintMessages(); } } class PrintClass { public void PrintMessages() { Class1 Object1 = new Class1(); Class2 Object2 = new Class2();
315
PrintMessageFromObject(Object1); PrintMessageFromObject(Object2); } private void PrintMessageFromObject(object obj) { IPrintMessage PrintMessage; PrintMessage = obj as IPrintMessage; if (PrintMessage != null) PrintMessage.Print(); } }
El operador as se usa como parte de una expresin que se construye como se indica a continuacin: Un identificador de objeto. La palabra clave as. Un identificador de interfaz. Si el objeto designado en la expresin implementa la interfaz designada en la expresin, la implementacin del objeto de la interfaz se devuelve como el resultado de la expresin. Si el objeto designado en la expresin no implementa la interfaz designada en la expresin, se asigna al resultado de la expresin un valor vaco representado por la palabra clave de C# null . La nueva implementacin del mtodo privado PrintMessageFromObject() usa el operador as. Declara una variable local del tipo de la interfaz IPrintMessage y usa el operador as para acceder a la implementacin del objeto del mtodo. Una vez que se ha completado la operacin as, se comprueba la variable de implementacin de la interfaz para descubrir si tiene el valor null. Si la variable no es null, se sabe que el objeto proporcionado implementa la interfaz y se puede llamar al mtodo de la interfaz. El listado 13.2 es funcionalmente equivalente al listado 13.1 y escribe el siguiente texto en la consola:
Hello from Class2!
316
Las interfaces marcadas como public son visibles para cualquier fragmento de cdigo que tenga acceso al cdigo en el que la definicin de la interfaz pueda resolverse en tiempo de ejecucin. Si se desarrolla un ensamblado y se implementa una interfaz pblica en ese ensamblado, cualquier aplicacin de .NET que acceda al ensamblado podr trabajar con la interfaz. Las interfaces marcadas como private slo son visibles para la clase en la que se definen. Slo las interfaces cuyas definiciones estn anidadas en clases pueden marcarse como private. Las interfaces marcadas como protected slo son visibles para las clases en la que son definidas o desde clases derivadas de la clase. Slo las interfaces cuyas definiciones estn anidadas en clases pueden marcarse como protected. Las interfaces marcadas como internal son visibles para cualquier cdigo en el mismo archivo binario, pero no son visibles para el cdigo que se encuentre en otros archivos binarios. Si se define una interfaz en C# y se compila la clase formando un ensamblado, cualquier fragmento de cdigo del ensamblado puede acceder a las interfaces internas. No obstante, si otro fragmento de cdigo usa ese ensamblado, no tendr acceso a la interfaz.
C# permite especificar una interfaz sin especificar ninguna palabra clave de mbito. Si se declara una interfaz sin especificar ninguna palabra clave de mbito, por defecto se le concede a la interfaz accesibilidad pblica.
317
nombran los colores del arco iris) como elementos de una matriz, como se muestra en el siguiente ejemplo:
Rainbow MyRainbow = new Rainbow(); for(ColorIndex = 0; ColorIndex < MyRainbow.Count; ColorIndex++) { string ColorName; ColorName = MyRainbow[ColorIndex]; System.Console.WriteLine(ColorName);
Se puede reducir an ms este cdigo usando la palabra clave foreach con la clase, como se muestra en el siguiente fragmento de cdigo:
Rainbow MyRainbow = new Rainbow(); foreach(string Color in MyRainbow) Console.WriteLine(ColorName);
Por defecto, las clases no admiten la palabra clave foreach y usarla para acceder a los elementos de la clase hace que el compilador de C# genere el siguiente mensaje de error:
error CS1579: La instruccin foreach no funciona en variables del tipo 'Rainbow' porgue 'GetEnumerator' no contiene una definicin para 'miembro' o es inaccesible
Sin embargo, se puede usar foreach en las clases si la clase implementa una interfaz de .NET Framework llamada IEnumerable . La interfaz IEnumerable contiene mtodos que .NET Framework usa para extraer elementos de los objetos. Si la clase contiene una coleccin de elementos y se quiere que otras partes del cdigo usen la palabra clave foreach para iterar cada uno de los elementos de la coleccin, hay que implementar la interfaz IEnumerable en la clase. La interfaz IEnumerable contiene una sola definicin de mtodo:
IEnumerator GetEnumerator();
El mtodo GetEnumerator() debe implementarse en la clase y debe devolver un objeto que implementa otra interfaz .NET Framework llamada IEnumerator . La interfaz IEnumerator es la responsable de implementar el cdigo que devuelve los elementos individuales de la clase. La interfaz IEnumerator define una propiedad y dos mtodos como sigue:
object Current {get;}
bool M o v e N e x t ( ) ; void R e s e t ( ) ;
318
La propiedad Current devuelve una referencia al elemento actual de la coleccin. El mtodo MoveNext() se mueve al siguiente elemento de la coleccin y devuelve T r u e si hay otro elemento a continuacin o F a l s e si se ha llegado al final de la coleccin y no hay otro elemento a continuacin. El mtodo Reset() devuelve el apuntador al principio de la coleccin. Cuando se accede a los datos de una clase con el constructor foreach , .NET Framework accede a las interfaces de las clases IEnumerable e IEnumerator con cdigo como el del siguiente pseudo-cdigo:
IEnumerable IEnumerableImplementation; IEnumerator IEnumeratorImplementation; IEnumerableImplementation = YourClass as IEnumerable; if (IEnumerableImplementation != null) { IEnumeratorImplementation = IEnumerableImplementation.GetEnumerator(); If (IEnumeratorImplementation != null) { while(IEnumeratorImplementation.GetNext() == true) CurrentValue = IEnumeratorImplementation.Current; } }
Las interfaces IEnumerable e IEnumerator estn definidas en un espacio de nombres de .NET Framework llamado System.Collections y hay que hacer referencia a ese espacio de nombres cuando se trabaja con estas interfaces. Se puede hacer referencia a los nombres de espacio explcitamente:
class MyClass : System.Collections.IEnumerable , System.Collections.IEnumerator
Si se quiere, se puede usar en su lugar la palabra clave using para hacer referencia a los espacios de nombres:
using System.Collections;
El listado 13.3 remodela el listado 9.3 y usa la clase Rainbow para implementar las interfaces IEnumerable e IEnumerator; tambin usa el constructor foreach del mtodo Main() para recorrer los elementos de la clase.
Listado 13.3. Cmo admitir foreach mediante IEnumerable IEnumerator using System; using System.Collections;
319
class Rainbow : IEnumerable, IEnumerator { private short IteratorIndex = -1; public IEnumerator { return this; } GetEnumerator()
public object Current { get { switch(IteratorIndex) { case 0: return "Red"; case 1: return "Orange"; case 2 : return "Yellow"; case 3 : return "Green"; case 4 : return "Blue"; case 5 : return "Indigo"; case 6: return "Violet"; default: return "*** ERROR ***"; } } } public bool MoveNext() { IteratorIndex++; if (IteratorIndex == 7) return false; return true; } public void Reset() { IteratorIndex = -1; } public static void Main() ( Rainbow MyRainbow = new Rainbow(); foreach (string ColorName in MyRainbow)
320
Console.WriteLine(ColorName); } }
La clase R a i n b o w implementa las interfaces I E n u m e r a b l e e IEnumerator . La clase mantiene un campo privado llamado IteratorIndex que se usa para seguir el rastro del siguiente elemento que debe devolverse en el bucle foreach . Se inicializa a -1; veremos el por qu cuando examinemos la implementacin de MoveNext() en las siguientes pginas. La implementacin de clase de IEnumerable.GetEnumerator() devuelve una referencia al objeto que se llama, con la siguiente instruccin:
return this;
Recuerde que el mtodo debe devolver una referencia a una clase que implemente el interfaz IEnumerator. Como el objeto usado para llamar a IEnumerable. GetEnumerator() tambin implementa la clase IEnumerator, se puede devolver el objeto al que se est llamando. Se puede usar la palabra clave this como valor devuelto. En este contexto, la palabra clave this hace referencia al objeto cuyo cdigo se est ejecutando en ese momento. Como el compilador de C# puede determinar en tiempo real que el objeto que se ejecuta en ese momento implementa IEnumerable, el cdigo no necesita convertir explcitamente la palabra clave this a una variable de tipo IEnumerator. El cdigo resultante podra ser como el siguiente:
return this as IEnumerator;
Sin embargo, esto es redundante porque el compilador de C# ya puede comprobar que el objeto this (el objeto que se est ejecutando en ese momento) implementa la interfaz IEnumerator. Si se usa este cdigo para devolver una referencia IEnumerator, recibir el siguiente aviso del compilador de C#:
warning CS0183: La expresin dada es siempre del tipo proporcionado ('System.Collections.IEnumerator')
El resto de la clase R a i n b o w implementa miembros de la interfaz IEnumerator. El primer miembro proporciona la implementacin para la propiedad IEnumerator.Current. Examina el valor de la propiedad privada de
321
la clase IteratorIndex y devuelve una cadena que representa el color del arco iris en el ndice al que hace referencia el valor de la propiedad IteratorIndex. La propiedad Current devuelve una variable de tipo o b j e c t , pero como las cadenas son objetos igual que todos los otros tipos de datos, el CLR acepta los valores devueltos basados en cadenas. La implementacin del mtodo IEnumerator.MoveNext() incrementa el valor de la propiedad privada IteratorIndex. Como el arco iris tiene siete colores, la implementacin MoveNext() da por sentado que los valores vlidos de IteratorIndex van de 0 a 6. Si el valor llega a 7, la implementacin de MoveNext() asume que el iterador ha llegado a su lmite y devolver F a l s e . En caso contrario, la implementacin devuelve T r u e . La instruccin que incrementa el valor de IteratorIndex exige que el valor inicial de IteratorIndex sea -1. Cuando se llama a MoveNext() por vez primera, la instruccin de incremento aumenta el valor de IteratorIndex de -1 a 0, dndole a IteratorIndex un valor vlido en la primera iteracin del bucle. La implementacin del mtodo IEnumerator.Reset() simplemente restablece el valor de IteratorIndex a - 1 . A este mtodo se le llama si se llama a ms de un constructor foreach y .NET Framework necesita devolver el estado de la enumeracin a su valor inicial. Toda esta implementacin hace que el mtodo M a i n ( ) sea muy claro. El mtodo puede crear un objeto de la clase Rainbow y usar foreach para iterar cada nombre de color de la clase.
322
olvidarse de tener que llamar a una instruccin de destruccin como delete y ayuda a eliminar todo un tipo de errores relacionados con la prdida de memoria. CLR usa un esquema de recuperacin de memoria llamado recoleccin 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 COM y COM+. En su lugar, los objetos se destruyen algo ms tarde, cuando el recolector de objetos no utilizados del CLR 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 del CLR usadas para seguir la pista al objeto. Es importante tener en cuenta este diseo de recoleccin de objetos no utilizados al programar las clases de C#. Las clases que gestionan recursos y necesitan ser explcitamente 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 cdigo de limpieza en el destructor de la clase significa que los recursos no sern liberados hasta que el recolector de objetos no utilizados destruya el objeto, que puede ser mucho despus de que se libere la ltima referencia al objeto. .NET Framework admite una interfaz llamada IDisposable que las clases pueden implementar para admitir recursos de limpieza de clases. La interfaz IDisposable se incluye en el espacio de nombres System de .NET Framework. Admite un solo mtodo llamado Dispose() , que no toma parmetros y no devuelve nada, como se muestra en el siguiente ejemplo:
using System; public class MyClass : IDisposable { public MyClass() { } ~MyClass() { } public void Dispose() { } }
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 llamar cuando el cdigo cliente se deshace del objeto. El cdigo cliente puede consultar los objetos para ver si son compatibles con la interfaz IDisposable y puede llamar a su mtodo Dispose() para 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 forma sencilla mediante una sintaxis especial que incluye la palabra clave using . La palabra clave using puede usarse en una expresin con parntesis que incluye la creacin de un nuevo objeto:
using(MyClass MyObject = new MyClass()) { // use aqu "MyObject" }
En este ejemplo, la palabra clave using se usa para crear un nuevo objeto llamado MyObject. El nuevo objeto pertenece a la clase MyObject. El objeto puede usarse en cualquier instruccin que est incluida entre las llaves que siguen a la palabra clave using. El objeto es automticamente destruido cuando la ruta de acceso del cdigo llega a la llave de cierre del bloque using. Si la clase del objeto creado en la instruccin using admite IDisposable, entonces el mtodo Dispose() de la clase es invocado automticamente sin que el cliente deba hacer nada. El listado 13.4 muestra una clase llamada MyClass que implementa la interfaz IDisposable.
Listado 13.4. IDisposable y la palabra clave using using System; public class MyClass : IDisposable { public MyClass() { Console.WriteLine("constructor"); } ~MyClass() { } public void Dispose() { Console.WriteLine("implementation of IDisposable.Dispose()"); } } public class MainClass { static void Main() { using(MyClass MyObject = new MyClass()) {
324
} } }
Esta aplicacin de consola implementa la clase M y C l a s s mostrada en el listado 13.4 y contiene instrucciones en su constructor, destructor e implementacin Dispose() que escriben mensajes en la consola. El listado 13.4 tambin incluye una instruccin using en su mtodo M a i n ( ) que crea un objeto de tipo MyClass. Si se ejecuta el listado 13.4 se escribe el siguiente mensaje en la consola:
constructor implementation of destructor IDisposable.Dispose()
Observe que la implementacin Dispose() de la interfaz IDisposable es invocada automticamente sin que el mtodo Main() intervenga. Tenga en cuenta que slo debe implementar la interfaz IDisposable para las clases que tienen recursos que deben ser liberados explcitamente, como conexiones de bases de datos o indicadores de ventana. Si la clase slo contiene referencias a objetos gestionados por el CLR, entonces no es necesario implementar IDisposable. Implementar IDisposable significa que el CLR necesita realizar ms trabajo para eliminar los objetos, y este trabajo adicional puede ralentizar el proceso de recoleccin de elementos no utilizados. Implemente IDisposable cuando sea necesario, pero no lo haga en caso de que no lo sea.
Resumen
Piense en una interfaz como en una promesa de que una clase implementar los mtodos, propiedades, indizadores y eventos definidos en la interfaz. Las interfaces proporcionan definiciones de miembros, pero no proporcionan ninguna implementacin. Se necesita una clase que implemente una interfaz para proporcionar una implementacin de cada uno de los miembros de la interfaz. Una clase puede implementar varias interfaces, aunque slo 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 palabras clave de C# is y as pueden usarse para trabajar con objetos que implementen interfaces. La palabra clave is se usa en una expresin booleana que devuelve T r u e si un objeto implementa una interfaz y F a l s e en caso contrario. La palabra clave as convierte una variable de objeto en una variable de un tipo de interfaz. Las expresiones que usan la palabra clave as devuelven n u l l si el objeto no implementa la interfaz designada. En este captulo se ve un ejemplo sobre cmo implementar una interfaz definida por .NET Framework. .NET Framework implementa muchas interfaces y es
325
aconsejable revisar la documentacin y estudiarlas todas. Las interfaces de .NET Framework comienzan con la letra I. Revselas todas. Puede usar las interfaces de .NET Framework para implementar cualquier cosa, desde dar formato personalizado a la consola para mecanizarla, hasta la semntica de eliminacin de la recoleccin de elementos no utilizados.
326
14 Enumeraciones
Algunas de las variables definidas en el cdigo pueden usarse para contener un valor tomado de un conjunto de valores posibles. Por ejemplo, puede necesitar seguir el rastro del estado de un archivo. Podra definir una variable que pudiera describir si un archivo est abierto, cerrado o si no se puedo encontrar. Un modo de lograrlo sera escoger algunas constantes para definir las distintas opciones y un entero para que contenga el valor actual, como en el siguiente cdigo:
const int FileOpen = 1; const int FileClosed = 2; const int FileNotFound = 3; int FileStatus; FileStatus = FileClosed;
Este cdigo es cdigo C# vlido y se compilar perfectamente. Sin embargo, un programador puede asignar 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 nmero entero y el compilador de C# acepta perfectamente cualquier valor entero vlido que asigne a la variable, aunque el objetivo es restringir el conjunto de valores vlidos al conjunto definido por las constantes. En teora, asignar a F i l e S t a t u s un valor no definido por las constantes no debera estar
329
permitido porque lo que se pretenda en un principio era restringir el conjunto de valores posibles al conjunto definido para esa variable. La situacin de desarrollo ideal en casos como ste es que debera ser posible definir una variable y asociar el valor a un conjunto de posibles valores vlidos. Adems, el compilador de C# debera ser capaz de evitar que los programadores asignen a la variable un valor no definido del conjunto de valores posibles. Como resultado, C# admite un constructor llamado enumeracin que se ocupa de este caso concreto. Las enumeraciones son un grupo de constantes definidas con un nombre comn. El nombre de la enumeracin puede usarse como un tipo de variable una vez que se haya definido la enumeracin. Cuando se usa una variable definida como un tipo de enumeracin, el compilador de C# se asegura de que los valores asignados a las variables del tipo de la enumeracin concuerdan con uno de los valores del conjunto de constantes definidas en la definicin de la enumeracin. Las enumeraciones son ideales para las situaciones en las que una variable debe asociarse a un conjunto de valores especfico. Supongamos, por ejemplo, que est escribiendo una clase de C# que controla una puerta electrnica y decide escribir para la clase una propiedad llamada D o o r S t a t e , que abre o cierra la puerta:
public int DoorState { set { InternalDoorState = value; } }
Tambin puede definir algunas constantes que se pueden usar para hacer el cdigo ms legible:
public const int DoorStateOpen = 1; public const int DoorStateClosed = 2;
La propiedad y las constantes permiten que el cdigo que trabaja con los objetos de la clase pueda escribir cdigo legible como el siguiente:
DoorStateObject = new DoorClass(); DoorObject.DoorState DoorObject.DoorState = DoorClass.DoorStateOpen; = DoorClass.DoorStateClosed;
El cdigo anterior se compila y ejecuta sin problemas. Sin embargo, la propiedad D o o r S t a t e se define como un i n t y no hay nada que impida a los invocadores usar valores que no tienen sentido y asignarlos a la propiedad DoorState:
DoorObject.DoorState = 12345;
330
Este cdigo tambin es vlido porque el literal 12345 est dentro de los lmites vlidos de un entero de C# y la propiedad D o o r S t a t e est definida como poseedora de un tipo i n t . Aunque este cdigo es vlido desde el punto de vista de la compilacin de C#, no tiene sentido en el nivel de clase porque el estado de la puerta en realidad slo debera ser abierta o cerrada. Podra crear algn cdigo para la verificacin de errores en la propiedad D o o r S t a t e para que slo acepte valores vlidos, pero sera incluso mejor hacer que el compilador de C# imponga la restriccin por nosotros cuando se crea el cdigo. Las enumeraciones proporcionan el mecanismo en tiempo de compilacin que est buscando. Le permiten agrupar las constantes relacionadas, como las constantes D o o r S t a t e O p e n y D o o r S t a t e C l o s e d , bajo un nombre de grupo y usar ese nombre de grupo como un tipo de valor. Puede, por ejemplo, agrupar las constantes D o o r S t a t e O p e n y D o o r S t a t e C l o s e d en una enumeracin llamada L e g a l D o o r S t a t e s y redefinir la propiedad D o o r S t a t e para que trabaje con un tipo de L e g a l D o o r S t a t e s , en lugar de con un i n t . El compilador de C# puede entonces asegurar que los valores asignados a la propiedad son miembros de la enumeracin y producir un error si el valor no existe en la enumeracin.
Cada uno de los miembros de las enumeraciones tiene un valor numrico asociado. Por defecto, el valor numrico del primer elemento es cero y el valor de cada uno de los otros miembros es una unidad mayor que el valor del elemento anterior. Si se usan estas reglas y la enumeracin definida anteriormente, el valor
331
por defecto de D o o r S t a t e O p e n es 0 y el valor de D o o r S t a t e C l o s e d es 1. Si lo desea, puede invalidar estos valores asignando valores a los miembros cuando se definen usando el operador de asignacin:
enum LegalDoorStates { DoorStateOpen = 100, DoorStateClosed = 150 }
Puede asignar los miembros a un valor literal o al resultado de una expresin constante:
enum LegalDoorStates { DoorStateOpen = (75 + 25), DoorStateClosed = 150 }
Si no se asigna un valor a un miembro concreto de la enumeracin, se aplican las reglas de asignacin de valor por defecto. Observe la siguiente enumeracin:
enum LegalDoorStates { DoorStateOpen = 100, DoorStateClosed }
Usando esta enumeracin y las reglas de asignacin de valor por defecto, el valor de DoorStateOpen es 100 y el valor de DoorStateClosed es 101. C# tambin permite el uso de un identificador de enumeracin para asignar un valor a otro identificador:
enum LegalDoorStates { DoorStateOpen = 100, DoorStateClosed, LastState = DoorStateClosed }
En este ejemplo, el valor de la enumeracin L a s t S t a t e es igual al valor de la enumeracin D o o r S t a t e C l o s e d , que en este caso es igual a 101. Esto demuestra que dos identificadores en una enumeracin tienen el mismo valor. Las enumeraciones corresponden a un tipo de valor particular. Este tipo correspondiente recibe el nombre de tipo subyacente de la enumeracin. Las enumeraciones pueden ser convertidas explcitamente a su tipo subyacente. Por defecto, el tipo subyacente de todas las enumeraciones es int . Si quiere usar un tipo subyacente diferente, especifique el tipo subyacente despus de dos puntos detrs del identificador de la enumeracin:
332
Todas las asignaciones explcitas deben usar valores que estn incluidos dentro de los lmites vlidos del tipo subyacente de la enumeracin. Observe el error en la siguiente enumeracin:
enum Weather : uint { Sunny = -1, Cloudy = - 2 , Rain = -3, Snow = -4 }
Esta declaracin de enumeracin es un error porque el tipo subyacente es u i n t y las asignaciones usan valores negativos que estn fuera de los valores legales de un u i n t . Si se compila la anterior enumeracin, el compilador de C# emite los siguientes errores:
error CS0031: 'uint' error CS0031: 'uint' error CS0031: 'uint' error CS0031: 'uint' El valor constante '-1' no se puede convertir a El valor constante '-2' no se puede convertir a El valor constante '-3' no se puede convertir a El valor constante '-4' no se puede convertir a
333
public LegalDoorStates State { get { return CurrentState; } set { CurrentState = value; } } } class MainClass { public static void Main() { DoorController Door; Door = new DoorController(); Door.State } } = LegalDoorStates.DoorStateOpen;
La enumeracin L e g a l D o o r S t a t e s est definida fuera de una declaracin de clase. Esto est permitido en C# y hace que la enumeracin sea visible para todas las clases del archivo fuente. Una alternativa es definir las enumeraciones dentro de una declaracin de clase usando palabras clave de mbito ( p u b l i c , p r o t e c t e d , i n t e r n a l o p r i v a t e ) para especificar el modo en que la enumeracin es visible para las otras clases. Tras definir la enumeracin L e g a l D o o r S t a t e s , se puede usar su nombre como un tipo de variable. Se usa como tipo para el campo privado C u r r e n t S t a t e de la clase D o o r C o n t r o l l e r y tambin como tipo de la propiedad pblica State de la misma clase. Para referirse a una enumeracin del cdigo se usa el nombre de la enumeracin y uno de los identificadores de la enumeracin. Estos identificadores estn separados por un punto, como se muestra en la siguiente instruccin:
Door.State = LegalDoorStates.DoorStateOpen;
El valor de la expresin L e g a l D o o r S t a t e s . D o o r S t a t e O p e n es igual al valor del identificador D o o r S t a t e O p e n de la enumeracin L e g a l D o o r S t a t e s . Este valor se asigna a la propiedad S t a t e . La ventaja de este diseo basado en enumeraciones es que el compilador puede identificar los lugares donde el cdigo intenta asignar a la propiedad S t a t e un valor diferente del valor procedente de la enumeracin. Observe el error en la siguiente instruccin:
Door.State = 12345;
334
El anterior cdigo es un error porque la propiedad S t a t e est definida como si tomase un valor de tipo L e g a l D o o r S t a t e s y en su lugar se le asigna un valor entero. El cdigo del anterior ejemplo produce el siguiente error del compilador de C#:
error CS0029: No se puede convertir implcitamente el tipo 'int' a 'LegalDoorStates'
335
AttrNone = 0, AttrReadOnly = 1, AttrHidden = 2, AttrReadyForArchive = 4 } class MainClass { public static void Main() { FileAttributes FileAttr; FileAttr = FileAttributes.AttrReadonly | FileAttributes.AttrHidden; Console.WriteLine(FileAttr); } }
El cdigo del listado 14.2 define un conjunto de valores enumerados que especifican atributos para un archivo o un disco. Un archivo puede no tener atributos especiales ( F i l e A t t r i b u t e s . A t t r N o n e ) , atributos de slo lectura ( F i l e A t t r i b u t e s . A t t r R e a d O n l y ) , atributos ocultos ( F i l e A t t r i butes.Hidden ) o atributos listos para ser archivados ( FileAttributes. AttrReadyForArchive). El cdigo del mtodo Main() especifica una variable local llamada FileAttr, que es de tipo FileAttributes. El cdigo asigna el valor a un archivo oculto de slo lectura mediante una operacin OR sobre los atributos F i l e A t t r i b u t e s . A t t r R e a d O n l y y FileAttributes.Hidden . El valor de la variable local se escribe a continuacin en la consola. Si se compila y ejecuta el listado 14.2 se escribe lo siguiente en la consola:
3
El listado 14.2 produce el valor 3 porque el valor de la enumeracin FileAttributes.AttrReadOnly , 1, se uni al valor de la enumeracin FileAttributes.Hidden , 2, en una operacin OR. Realizar una operacin booleana OR con los valores 1 y 2 produce un resultado de 3. Tambin puede convertir un valor enumerado a un valor con el tipo del tipo subyacente de la enumeracin:
enum IntEnum { EnumOne = 1 , EnumTwo, EnumThree } IntEnum IntEnumValue; int IntValue;
336
El cdigo anterior convierte el valor de una variable de enumeracin I n t E n u m V a l u e a su equivalente entero y asigna el entero I n t V a l u e a ese valor. Como la variable I n t V a l u e es un entero estndar, se le puede asignar cualquier valor vlido para un entero. No est limitado al conjunto de valores definidos por la enumeracin, aunque se le asigna un valor que procede de una variable enumerada.
CurrentState;
337
class MainClass { public static void Main() { DoorController Door; string EnumName; Door = new DoorController(); Door.State = LegalDoorStates.DoorStateOpen; EnumName = LegalDoorStates.GetName(typeof (LegalDoorStates), Door.State); Console.WriteLine(EnumName); } }
El mtodo M a i n ( ) del listado 14.3 usa el mtodo GetName() de la clase System.Enum para obtener una cadena que represente un valor de enumeracin. El primer parmetro es un objeto Type que especifica la enumeracin a la que se consulta. La expresin typeof(LegalDoorStates) devuelve un objeto .NET Type para el tipo especificado (en este caso, la enumeracin LegalDoorStates ). El segundo parmetro es el valor de la enumeracin actual, de la que debe devolverse la representacin de su cadena. La siguiente instruccin muestra cmo puede usarse el mtodo GetName() para obtener el nombre de un valor enumerado:
EnumName = LegalDoorStates.GetName(typeof(LegalDoorStates), Door.State);
Esta instruccin se interpreta como: "Devuelve una cadena que represente el nombre del valor de la propiedad Door.State . Este valor es una parte de la enumeracin LegalDoorStates ." Si se ejecuta el listado 14.3 se escribe lo siguiente en la consola:
DoorStateOpen
Tambin se puede usar el mtodo Format() para recuperar el nombre de un valor de enumeracin, segn su valor numrico. La llamada GetName() del listado 14.3 puede reemplazarse por la siguiente llamada a Format():
EnumName = LegalDoorStates.Format(typeof(LegalDoorStates), 0, " g " ) ;
El primer parmetro de Format() es el mismo que el primer parmetro de GetName(), que es el tipo de enumeracin que se usa en la llamada. El segundo parmetro de Format() es el valor numrico que debe devolver la llamada. El ltimo parmetro 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 enumeracin con el valor numrico que concuerde con el valor del segundo parmetro.
338
x, que especifica que el valor del segundo parmetro debe devolverse como una cadena que represente el valor en notacin hexadecimal. d, que especifica que el valor del segundo parmetro debe devolverse como una cadena que represente el valor en notacin hexadecimal. f, que especifica que el valor debe ser tratado como un conjunto de valores enumerados combinados y que el mtodo debe devolver una lista de valores delimitados por comas como una cadena.
El valor de formato f se cre para ser usado con enumeraciones que representan valores de bit. Observe la siguiente enumeracin:
public enum BitsToSet { Bit0Set = 1, Bit1Set = 2, Bit2Set = 4, Bit3Set = 8, Bit4Set = 16, Bit5Set = 32, Bit6Set = 64, Bit7Set = 128 }
La enumeracin anterior representa un conjunto de bits que pueden asignarse a un byte. Se pueden asignar varios bits a una variable mediante el operador booleano OR, como en el siguiente ejemplo:
BitsToSet Byte; Byte = BitsToSet.Bit1Set | BitsToSet.Bit3Set | BitsToSet.Bit6Set;
Llamar al mtodo Format() en la variable Byte con el parmetro de formato f devuelve una cadena que representa los nombres de los valores enumerados cuyos valores se encuentran en la variable:
Bit1Set, Bit3Set, Bit6Set
339
{ public enum Color { Red = 0, Orange, Yellow, Green, Blue, Indigo, Violet } static void Main() { Color MyColor; MyColor = Color.Green; Console.WriteLine("{0}", MyColor.CompareTo(Color.Red)); Console.WriteLine("{0}", MyColor.CompareTo(Color.Green)); Console.WriteLine("{0}", MyColor.CompareTo(Color.Violet)); } }
El listado 14.4 declara una clase con una enumeracin pblica llamada Col o r . Sus valores varan de 0 a 6. El mtodo M a i n ( ) declara una variable de tipo C o l o r llamada MyColor y asigna el valor G r e e n a la variable. A continuacin invoca a CompareTo() para comparar el valor de la variable con otros valores de la enumeracin. El mtodo CompareTo() devuelve uno de estos tres valores: -1 si el valor que se pasa como argumento a CompareTo() tiene un valor superior al valor enumerado usado para invocar al mtodo. 1 si el valor que se pasa como argumento a CompareTo() tiene un valor inferior al valor enumerado usado para invocar al mtodo.
0 si los dos valores son iguales. En el listado 14.4 se llama tres veces al mtodo CompareTo(). En la primera llamada, la variable MyColor se compara con el valor Red. Como G r e e n , que tiene el valor 3, tiene un valor superior a Red, que tiene el valor 0, CompareTo() devuelve 1. En la segunda llamada, la variable MyColor se compara con el valor G r e e n . Como los valores son iguales, CompareTo() devuelve 0. En la ltima llamada, la variable MyColor se compara con el valor Violet. Como Green, que tiene el valor 3, tiene un valor inferior a V i o l e t , que tiene un valor 6, CompareTo() devuelve -1. El argumento usado en la llamada a CompareTo() debe ser del mismo tipo que la enumeracin usada para llamar al mtodo. Usar cualquier otro tipo, incluso el tipo subyacente de la enumeracin, produce un error en tiempo de ejecucin.
340
Este cdigo recupera el tipo subyacente para una enumeracin llamada BitsToSet y escribe el nombre del tipo en la consola, que produce una cadena como la siguiente:
System.Int32
Este cdigo llama a GetValues() en la enumeracin Color definida con anterioridad. El mtodo GetValues() devuelve una matriz y se recorren los elementos de la matriz de uno en uno mediante la palabra clave foreach . El nombre de cada elemento de la matriz se escribe en la consola, como se puede ver a continuacin:
Red Orange
341
Esta llamada devuelve un objeto que representa el valor enumerado llamado Blue en una enumeracin llamada Color . Como muchos otros mtodos de enumeracin, el mtodo Parse() es llamado con el tipo, en lugar de una variable del tipo. El mtodo Parse() devuelve un objeto, que necesita ser convertido explcitamente en un valor del tipo apropiado. El siguiente ejemplo muestra cmo el mtodo Parse() pude ser usado como uno de los muchos modos de representar un valor enumerado:
Color ColorValue; object ParsedObject; ParsedObject = Color.Parse(typeof(Color), "Blue"); Console.WriteLine(ParsedObject.GetType().ToString()); ColorValue = (Color)ParsedObject; Console.WriteLine(ColorValue.ToString()); Console.WriteLine(Color.Format(typeof(Color), ColorValue, "d"));
En este cdigo se llama a Parse() con el tipo de enumeracin Color y se le otorga una cadena de entrada Blue . Esta llamada devuelve un objeto y el cdigo escribe el tipo del objeto en la consola. El objeto se convierte entonces explcitamente en una variable del tipo Color y se escribe en la consola el nombre del valor de la enumeracin y el valor decimal:
MainClass+Color Blue 4
Este resultado demuestra que el objeto devuelto por el mtodo Parse() es del tipo C o l o r . La variable convertida, que es una variable de Color, tiene un nombre de cadena Blue y un valor decimal 4.
342
Resumen
Las enumeraciones se emplean para agrupar un conjunto de constantes relacionadas. Al dar a sus enumeraciones un nombre, se puede usar ese nombre en el cdigo como un tipo de variable una vez que se haya definido la enumeracin. Las enumeraciones, por defecto, se basan en un conjunto de constantes int . Se puede invalidar este valor por defecto especificando un tipo subyacente para la enumeracin. Se pueden usar como tipo subyacente de una enumeracin muchos de los tipos numricos de C#. Se deben emplear enumeraciones cuando queramos que el compilador de C# garantice que las constantes con las que trabajamos en el cdigo proceden de un conjunto de valores vlidos. Por defecto, el compilador de C# asigna valores numricos a los identificadores de las enumeraciones. El primer identificador tiene el valor de cero y los otros elementos aumentan su valor a partir de ah. Si lo deseamos, se puede usar el operador de asignacin para asignar un valor a un identificador de enumeracin cuando se define la enumeracin. Un valor en una enumeracin se especifica escribiendo el nombre de la enumeracin, un punto y el nombre d e l identificador de la enumeracin. Los identificadores de enumeracin pueden convertirse implcitamente al tipo subyacente de la enumeracin. Esta conversin implcita tambin permite el uso de algunos de los operadores de C# para trabajar con los valores de enumeracin. Todas las enumeracin de C# derivan de una clase base de .NET llamada System.Enum . La clase System.Enum contiene algunos mtodos tiles que pueden ayudar a obtener las mximas prestaciones de las enumeraciones. Este captulo ha examinado la mayora de estos mtodos.
343
15 Eventos y delegados
En el flujo general del tpico segmento de software orientado a objetos, un fragmento de cdigo crea un objeto de una clase y llama a mtodos de ese objeto. En este contexto, el invocador es el cdigo activo porque es el cdigo el que llama a los mtodos. El objeto es pasivo, en el sentido de que espera y realiza una accin slo cuando se invoca a uno de sus mtodos. Sin embargo, tambin se puede producir el contexto contrario. Un objeto puede realizar una tarea y avisar al invocador cuando ocurre algo durante el proceso. A este algo se le llama evento, y a la publicacin de ese evento del objeto se le conoce como desencadenar un evento. El proceso de activado por eventos, en el que fragmentos de cdigo informan a otras piezas de cdigo cuando se producen eventos relevantes, no es una novedad de .NET. La capa de interfaz de usuario de Windows siempre ha usado una forma de eventos para informar a las aplicaciones Windows cuando los usuarios trabajan con el ratn, presionan una tecla en el teclado o mueven una ventana. Los controles ActiveX desencadenan eventos para los contenedores de control de ActiveX cuando el usuario realiza una accin que afecta al control. El lenguaje C# contiene palabras clave especiales que hacen que sea fcil desencadenar, publicar y procesar eventos en el cdigo de C#. Se pueden usar estas palabras clave para permitir que las clases de C# desencadenen y procesen eventos con el mnimo esfuerzo.
345
Si se declaran delegados en la clase que desencadena el evento, se les pueden anteponer las palabras clave public , protected , internal o private como se pueden ver en este ejemplo de una definicin delegate.
public delegate void EvenNumberHandler(int Number);
En este ejemplo se crea un delegado pblico llamado EvenNumberHandler que no devuelve nada. Este delegado slo define un parmetro para ser pasado, de tipo i n t . El identificador de delegado, EvenNumberHandler, puede ser cualquier nombre que elija mientras no le d el nombre de una palabra clave de C#.
346
depsito 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 salpicadero para que sepa que tiene que comprar ms combustible. En pocas palabras, un evento es un medio que tiene el ordenador de avisarle de una condicin. Para definir un evento que desencadene una clase se usa la palabra clave de C# event . En su forma ms simple, las declaraciones de eventos de C# usan la siguiente sintaxis: La palabra 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 void NewRequestHandler(string URL); public class Webserver { public event NewRequestHandler NewRequestEvent; // ... }
Este ejemplo declara un delegado llamado 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 para los mtodos que procesan el evento new request . Todos los mtodos que necesitan procesar el evento new request deben obedecer las convenciones de llamada del delegado: no deben devolver ningn dato y deben tener una sola cadena como lista de parmetros. Las implementaciones de control de eventos pueden tener cualquier nombre de mtodo mientras su tipo devuelto y su lista de parmetros concuerden con el patrn de delegados. La clase Webserver define un evento llamado NewRequestEvent . El tipo de este evento es NewRequestHandler. Esto significa que slo los controles de evento escritos para que concuerden con las convenciones de llamada del delegado pueden ser usadas para procesar el evento NewRequestEvent.
347
Tras crear la nueva instancia controladora de eventos, se usa el operador += para aadirla a la variable de evento:
NewRequestEvent += HandlerInstance;
Esta instruccin enlaza la instancia de delegado HandlerInstance , que admite el mtodo MyNewRequestMethod , con el evento NewRequestEvent . Mediante el operador += se pueden enlazar tantas instancias de delegado como quiera para un evento. Del mismo modo, puede usar el operador -= para eliminar una instancia de delegado de un evento:
NewRequestEvent -= HandlerInstance;
Los parmetros usados para desencadenar el evento deben coincidir con la lista de parmetros del delegado del evento. El delegado del evento NewRequestEvent se defini para aceptar un parmetro de cadena; por tanto. se debe proporcionar una cadena cuando se desencadene el evento desde la clase del cliente Web.
348
Listado 15.1. Cmo recuperar eventos de nmeros pares using System; public delegate void EvenNumberHandler(int Number); class Counter { public event EvenNumberHandler OnEvenNumber; public Counter() { OnEvenNumber = null; } public void CountTo100() { int CurrentNumber; for(CurrentNumber = 0; CurrentNumber <= 100; CurrentNumber++) { if (CurrentNumber % 2 == 0) ( if (OnEvenNumber != null) { OnEvenNumber(CurrentNumber); } } } } } class EvenNumberHandlerClass { public void EvenNumberFound(int EvenNumber) { Console.WriteLine(EvenNumber); } } class MainClass { public static void Main() { Counter MyCounter = new Counter(); EvenNumberHandlerClass MyEvenNumberHandlerClass = new EvenNumberHandlerClass(); MyCounter.OnEvenNumber += new EvenNumberHandler(MyEvenNumberHandlerClass.EvenNumberFound); MyCounter.CountTo100(); } }
349
Para compilar esta aplicacin hay que crear una nueva aplicacin de consola en Visual Studio y copiar en ella el cdigo fuente o simplemente usar el bloc de notas para guardar el archivo y a continuacin usar:
csc <filename>
El listado 15.1 implementa tres clases: La clase Counter es la clase que realiza el clculo. Implementa un mtodo pblico llamado C o u n t T o 1 0 0 ( ) y un evento pblico llamado O n E v e n N u m b e r . El evento OnEvenNumber es del tipo delegado EvenNumberHandler. La clase EvenNumberHandlerClass contiene un mtodo pblico llamado EvenNumberFound . Este mtodo acta como el controlador de evento para el evento OnEvenNumber de clase Counter. Imprime en la consola el entero proporcionado como parmetro. La clase MainClass contiene el mtodo Main() de la aplicacin.
El mtodo M a i n ( ) crea un objeto de clase Counter y da nombre al objeto M y C o u n t e r . Tambin crea un nuevo objeto de clase E v e n N u m b e r HandlerClass y llama al objeto MyEvenNumberHandlerClass. El mtodo Main() llama al mtodo CountTo100() del objeto MyCounter, pero no antes de instalar una instancia de delegado en la clase Counter. El cdigo crea una nueva instancia de delegado que gestiona el mtodo EvenNumberFound del objeto MyEvenNumberHandlerClass y lo aade al evento OnEvenNumber del objeto MyCounter usando el operador +=. La implementacin del mtodo CountTo100 usa una variable local para contar desde 0 a 100. Cada vez que pasa por el bucle contador, el cdigo comprueba si el nmero es par examinando si el nmero tiene resto al dividirse entre dos. Si el nmero es par, el cdigo desencadena el evento OnEvenNumber y proporciona el nmero par como el argumento para hacerlo coincidir con la lista de parmetros del delegado de eventos. Como el mtodo EvenNumberFound de MyEvenNumberHandlerClass estaba instalado como un controlador de eventos y como ese mtodo escribe el parmetro proporcionado en la consola, si se compila y ejecuta el cdigo del listado 15.1, se escriben en la consola los nmeros pares entre 0 y 100.
350
Una referencia al objeto que desencaden el evento. Un objeto que contiene datos relacionados con el evento.
El segundo parmetro, 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 diseo preferido.
Listado 15.2. Cmo recuperar nmeros pares con la convencin de delegados .NET using System; public delegate void EvenNumberHandler(object Originator, OnEvenNumberEventArgs EvenNumberEventArgs); class Counter { public event EvenNumberHandler OnEvenNumber; public Counter() { OnEvenNumber = null; } public void CountTo100() { int CurrentNumber; for(CurrentNumber = 0; CurrentNumber <= 100; CurrentNumber++) { if (CurrentNumber % 2 == 0) { if (OnEvenNumber != null) { OnEvenNumberEventArgs EventArguments; EventArguments = new OnEvenNumberEventArgs(CurrentNumber); OnEvenNumber(this, EventArguments); } } } } } public class OnEvenNumberEventArgs : EventArgs { private int EvenNumber; public OnEvenNumberEventArgs(int EvenNumber)
351
{ this.EvenNumber = EvenNumber; } public int Number { get { return EvenNumber; } } ) class EvenNumberHandlerClass { public void EvenNumberFound(object Originator, OnEvenNumberEventArgs EvenNumberEventArgs) { Console.WriteLine(EvenNumberEventArgs.Number); } } class MainClass { public static void Main() { Counter MyCounter = new Counter(); EvenNumberHandlerClass MyEvenNumberHandlerClass = new EvenNumberHandlerClass(); MyCounter.OnEvenNumber += new EvenNumberHandler(MyEvenNumberHandlerClass.EvenNumberFound); MyCounter.CountTo100(); } }
El listado 15.2 aade una nueva clase llamada OnEvenNumberEventArgs que deriva de la clase .NET EventArgs . Implementa un constructor que toma un nmero entero y lo almacena en una variable privada. Tambin expone una propiedad de slo lectura llamada Number , que devuelve el valor de la variable privada. La firma del delegado tambin ha cambiado para cumplir con la nueva convencin. Ahora acepta dos parmetros de entrada: una referencia al objeto que desencadena el evento y un objeto de tipo OnEvenNumberEventArgs. Cuando la clase Counter se prepara para desencadenar el evento, antes crea un nuevo objeto de tipo OnEvenNumberEventArgs y lo inicializa con el nmero par. A continuacin pasa este objeto al evento como segundo parmetro. La nueva implementacin del mtodo EvenNumberFound examina el segundo parmetro, un objeto de clase OnEvenNumberEventArgs y escribe el valor de la propiedad Number del objeto en la consola.
352
El cdigo del bloque de cdigo add se invoca cuando un usuario aade un nuevo controlador de eventos al evento usando el operador += . La ventaja de usar descriptores de acceso de eventos es que tenemos total libertad en lo que respecta al modo en que almacenamos los controladores de eventos. En lugar de definir campos separados para cada evento, podemos almacenar una sola lista enlazada o una matriz de controladores y podemos implementar el descriptor de acceso de eventos remove para eliminar un controlador de eventos de la matriz o de la lista. Como en las propiedades estndar, podemos usar la palabra clave de C# value para hacer referencia al controlador de evento que se aade o elimina, como se puede ver en la siguiente instruccin:
353
En esta instruccin, se aade al evento O n E v e n N u m b e r un nuevo objeto EvenNumberHandler. Si implementsemos el evento como una propiedad, el bloque de cdigo add podra usar la palabra clave add para hacer referencia al nuevo objeto EvenNumberHandler:
public event EvenNumberHandler OnEvenNumber { add { AddToList(value); } remove { RemoveFromList(value); } }
Cuando se usa en descriptores de acceso de eventos, la palabra clave value es una variable de tipo Delegate .
Eventos estticos
Los eventos modificados con la palabra clave static se comportan de forma parecida a los campos estticos, en el sentido de que, aunque cada copia de una clase contiene copias separadas de todos los campos, slo puede haber una copia de un miembro esttico en un momento dado. Todos los objetos de la clase comparten los eventos estticos. Cuando se les hace referencia, debe ser a travs del nombre de la clase y no mediante el nombre del objeto, como se aprecia a continuacin:
public class Counter {
354
Como puede ver, debe hacer referencia al evento esttico OnEvenNumber especificando el nombre de la clase y el nombre del objeto.
Eventos virtuales
Los eventos modificados con la palabra clave virtual marcan cualquier descriptor de acceso add o remove como virtual. Los descriptores de acceso virtuales en clases derivadas pueden ser reemplazados.
Eventos de reemplazo
Los eventos modificados con la palabra clave override marcan cualquier descriptor de acceso add o remove como eventos de reemplazo add o remove con el mismo nombre en una clase base.
Eventos abstractos
Los eventos modificados con la palabra clave abstract marcan cualquier descriptor de acceso add o remove como abstracto. Los descriptores de acceso abstractos no proporcionan una implementacin propia: en su lugar, un evento de reemplazo de una clase derivada proporciona una implementacin.
Resumen
Las clases pueden desencadenar eventos cuando un programa pide a sus clientes que le avisen de las acciones llevadas a cabo por la clase. Sin eventos, los usuarios llaman a un mtodo para realizar una operacin, pero en realidad no saben lo avanzada que est la operacin. Imagine, por ejemplo, un mtodo que recupera una pgina Web de un servidor Web. Esa operacin consiste en varios pasos: Conectar con el servidor Web. Solicitar la pgina Web. Recuperar la pgina Web devuelta.
355
Es posible disear una clase como sta con eventos que se desencadenen cuando comience cada una de estas acciones. Al desencadenar los eventos en los pasos crticos del proceso, le da pistas a los usuarios de su clase sobre en qu parte del proceso se encuentra el cdigo. Los invocadores responden a los eventos registrando mtodos llamados controladores de eventos. Los controladores de eventos se invocan cuando una clase desencadena algn evento. Estos mtodos se corresponden con la lista de parmetros y devuelven un valor de un mtodo patrn especial llamado delegado. Un delegado describe el diseo de un controlador de eventos, indicando qu parmetros debe admitir y cmo debe ser su cdigo devuelto. Los controladores de eventos se instalan usando el operador +=. Los eventos se declaran normalmente como campos pblicos en una clase, y los invocadores aaden sus controladores de eventos a la clase creando un nuevo objeto de la clase delegado y asignando el objeto al evento usando el operador +=. C# permite especificar varios controladores de eventos para un solo evento, y tambin permite usar el operador -= para eliminar un controlador de eventos de un evento. C# no obliga a usar un nico patrn de diseo para los delegados, pero .NET Framework recomienda un diseo. Usar el diseo recomendado proporciona un estndar que, cuando se respeta, puede dar a los delegados una lista de parmetros de mtodo: 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 argumentos para el evento. C# hace que sea muy sencillo implementar eventos en las clases. En el nivel ms bsico, cada evento se declara como un campo pblico y se pueden gestionar tantos eventos como se desee. Si la clase va a gestionar varios eventos y el nmero de campos pblicos dentro de la clase parece originar una cantidad excesiva de cdigo, se pueden escribir descriptores de acceso de eventos que permitan controlar el modo en que la clase gestiona los controladores de eventos. En lugar de definir campos pblicos para los eventos, los descriptores de acceso de eventos permiten definir los eventos como propiedades con bloques de cdigo add y remove . El bloque de cdigo add se invoca cuando se aade un controlador de eventos a un evento, y el bloque de cdigo remove se invoca cuando se elimina un controlador de eventos de un evento. Estos bloques de cdigo se pueden implementar almacenando los controladores de eventos en una matriz o en una lista para ser usados posteriormente. El concepto de C# de eventos y delegados es un concepto nuevo para la familia de lenguajes C. Los eventos pueden ser desencadenados en C y C++ usando otros mecanismos, pero estos lenguajes no definen palabras clave para hacer que funcionen los eventos. En C#, los eventos y delegados son elementos completamente definidos por s mismos, y tanto el lenguaje como el compilador tienen compatibilidad integrada para el control de eventos. Otra ventaja de usar eventos en C# es que son completamente compatibles con el CLR, lo que significa que se puede preparar un mecanismo de eventos en
356
C# y desencadenar eventos que se controlen por otro lenguaje .NET. Como el CLR admite eventos en el nivel de tiempo de ejecucin, se puede desencadenar un evento en C# y controlarlo y procesarlo con otro lenguaje, como Visual Basic .NET.
357
16 Control de excepciones
Buscar errores y manejarlos adecuadamente es un principio fundamental para disear software correctamente. En teora, escribimos el cdigo, cada lnea funciona como pretendemos, y los recursos que empleamos siempre estn presentes. Sin embargo, ste no siempre es el caso en el mundo real. Otros programadores (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 palabras, el cdigo que se escribe tiene que ser capaz de detectar errores como stos y responder adecuadamente. Los mecanismos para informar de errores son tan diversos como los propios errores. Algunos mtodos pueden estar diseados para devolver un valor booleano que indique el xito o el fracaso de dicho mtodo. Otros mtodos pueden escribir errores en un fichero de registro o una base de datos de algn tipo. La variedad de modelos de presentacin de errores nos indica que el cdigo que escribamos para controlar los errores debe ser bastante consistente. Cada mtodo puede informar de un error de un modo distinto, lo que significa que la aplicacin estar repleta de gran cantidad de cdigo necesario para detectar los diferentes tipos de errores de las diferentes llamadas al mtodo. .NET Framework proporciona un mecanismo estndar, llamado control de excepciones estructurado (SEH), para informar de los errores. Este mecanismo de-
359
pende de las excepciones para indicar los fallos. Las excepciones son clases que describen un error. .NET Framework las usa para informar de los errores y podemos utilizarlas en nuestro cdigo. Puede escribir cdigo que busque excepciones generadas por cualquier fragmento de cdigo, tanto si procede del CLR como si procede de nuestro propio cdigo, y podemos ocuparnos de la excepcin generada adecuadamente. Usando SEH slo necesitamos crear un diseo de control de errores para nuestro cdigo. Esta metodologa unificada del proceso de errores tambin es crucial para permitir la programacin .NET multilinge. Al disear todo nuestro cdigo usando SEH, podemos mezclar y comparar cdigo (por ejemplo C#, C++ o VB.NET), sin peligro alguno y fcilmente. Como premio por seguir las reglas del SEH, .NET Framework garantiza que todos los errores sern expuestos y controlados convenientemente en los diferentes lenguajes. El proceso de deteccin y gestin de excepciones en el cdigo de C# es sencillo. Se deben identificar tres bloques de cdigo cuando se trabaja con excepciones: El bloque de cdigo que debe usar el procesamiento de excepciones. Un bloque de cdigo que se ejecuta si se encuentra una excepcin mientras se procesa el primer bloque de cdigo. Un bloque de cdigo opcional que se ejecuta despus de que se procese la excepcin. En C# la generacin de una excepcin recibe el nombre de iniciacin de una excepcin. El proceso de informar de que se ha iniciado una excepcin recibe el nombre de capturar una excepcin. El fragmento de cdigo que se ejecuta despus de que se haya procesado la excepcin es el bloque finally . En este captulo veremos cmo se usan estos constructores en C#. Tambin estudiaremos los miembros de la jerarqua de las excepciones. NOTA: Un debate largo y recurrente de la comunidad de usuarios de software orientado a objetos es si las excepciones deberan usarse en todos los errores (incluyendo los errores que uno espera que ocurran frecuentemente) o slo para los errores graves (los conocidos como errores de excepcin, que slo 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 mtodo de control, como los cdigos de devolucin. La respuesta 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 reido debate. La investigacin exhaus-
360
tiva (principalmente por parte de la comunidad de usuarios de C++) ha llegado a la conclusin de que evitar la prdida de recursos sin excepciones es prcticamente imposible. Por supuesto, C# evita la prdida de memoria con la recogida de elementos no utilizados, pero todava necesitamos un mecanismo para evitar las prdidas de recursos de distintos tipos de punteros de operaciones del sistema. Como en todas las instrucciones de diseo, usar excepciones indiscriminadamente para todos los errores supone un uso excesivo de recursos. Cuando el error es local para un bloque de cdigo, puede ser ms apropiado usar cdigos de devolucin de error. Con frecuencia vemos este enfoque cuando se implementa una validacin de formularios. ste es un cambio aceptable porque los errores de validacin suelen estar localizados en el formulario que recoge la entrada. En otras palabras, cuando ocurre un error de validacin, se presenta en pantalla un mensaje y pedimos al usuario que vuelva a introducir correctamente la informacin requerida. Como el error y el cdigo controlador estn en el mismo bloque, controlar la prdida de recursos es sencillo. Otro ejemplo es controlar una condicin de fin de archivo cuando se est leyendo un archivo. Esta condicin puede ser controlada fcilmente sin usar el encabezado de excepciones habitual. De nuevo, la condicin de error se controla completamente dentro del bloque de cdigo donde ocurre el error. Cuando observe que se realizan llamadas al cdigo fuera del bloque de cdigo donde ocurre el error, debe tender a procesar los errores usando SEH.
Si se inicia una instruccin mientras se est ejecutando cualquiera de las instrucciones del bloque try, se puede capturar la excepcin en el cdigo y ocuparse de ella adecuadamente.
361
Las instrucciones del bloque catch se ejecutan si se inicia una excepcin desde el bloque try. Si ninguna de las instrucciones del bloque try inicia una excepcin, entonces no se ejecuta nada del cdigo del bloque catch.
362
Las instrucciones que deben ejecutarse cuando se inicia una excepcin del tipo especificado desde el anterior bloque try Una llave de cierre
El bloque catch de este ejemplo atrapa 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 cdigo del bloque catch para obtener ms informacin sobre la excepcin iniciada. El cdigo del bloque try puede iniciar diferentes clases de excepciones y queremos controlar cada una de las diferentes clases. C# permite especificar varios bloques catch, cada uno de los cules controla una clase de error especfica:
try { // coloque aqu las instrucciones } catch(Exception ThrownException) { // Bloque catch 1 } catch(Exception ThrownException2) { // Bloque catch 2 }
En este ejemplo, se revisa el cdigo del bloque try en busca de excepciones iniciadas. Si el CLR descubre que el cdigo del bloque try inicia alguna excepcin, examina la clase de la excepcin y ejecuta el bloque catch adecuado. Si la excepcin iniciada es un objeto de clase Exception, se ejecuta el cdigo del Bloque catch 1. Si la excepcin iniciada es un objeto de alguna otra clase, no se ejecuta ninguno de los bloques. Tambin se puede aadir un bloque catch genrico a la lista de bloques de cdigo catch, como en el siguiente ejemplo:
try { // coloque aqu las instrucciones
363
En este caso, se revisa el cdigo del bloque try en busca de excepciones. Si la excepcin iniciada es un objeto de clase Exception, se ejecuta el cdigo de Bloque catch 1. Si la excepcin iniciada es un objeto de alguna otra clase, se ejecuta el cdigo del bloque genrico catch (Bloque catch 2).
El bloque de cdigo finally es un buen sitio donde liberar los recursos que se haban utilizado en el mtodo con anterioridad. Suponga, por ejemplo, que estamos escribiendo un mtodo que abre tres archivos. Si encerramos el cdigo de acceso a los archivos en un bloque try, podremos atrapar excepciones relacionadas con la apertura, lectura o escritura de esos archivos. Sin embargo, al final del cdigo necesitaremos cerrar los tres archivos, aunque se haya iniciado una excepcin. Probablemente queramos colocar las instrucciones de cierre de archivos en un bloque finally, pudiendo estructurar el cdigo como se indica a continuacin:
try { // abrir archivos // leer archivos } catch { // atrapar excepciones
364
El compilador de C# permite definir un bloque finally sin ningn bloque catch . Se puede escribir un bloque finally inmediatamente despus de un bloque try .
La clase exception
Todas las excepciones iniciadas por .NET Framework son clases derivadas de la clase System.Exception . La tabla 16.1 describe algunos miembros tiles de esta clase.
Tabla 16.1. Miembros de la clase System.Exception
Descripcin Un vnculo al archivo de ayuda que proporciona ms informacin sobre la excepcin El texto que se ha proporcionado, normalmente como parte del constructor de la excepcin, para describir la condicin del error El nombre de la aplicacin u objeto que provoc la excepcin Una lista de las llamadas al mtodo en la pila El nombre del mtodo que inici la excepcin
365
OutOfMemoryException
El CLR inicia la excepcin OutOfMemoryException cuando agota su memoria. Si el cdigo intenta crear un objeto usando el operador new y el CLR no dispone de suficiente memoria para ello, el CLR inicia la excepcin OutOfMemoryException, mostrada en el listado 16.1.
Listado 16.1. Excepcin OutOfMemoryException using System; class MainClass { public static void Main() { int [] LargeArray; try { LargeArray = new int [2000000000]; } catch(OutOfMemoryException) { Console.WriteLine("The CLR is out of memory."); } ) }
El cdigo del listado 16.1 intenta asignar un espacio a una matriz de dos mil millones de nmeros enteros. Dado que un nmero entero requiere cuatro bytes de memoria, se necesitan ocho mil millones de bytes para contener una matriz de este tamao. Es bastante probable que su ordenador no disponga de esta cantidad de memoria y de que la asignacin falle. El cdigo encierra a la asignacin en un bloque try y define un bloque catch para que controle cualquier excepcin OutOfMemoryException iniciada por el CLR. NOTA: El cdigo del listado 16.1 no escribe un identificador para la excepcin del bloque catch. Esta sintaxis (en la que se especifica la clase de una excepcin pero no se le da nombre) es vlida. Funciona perfectamente cuando se quiere atrapar una clase de excepciones pero no se necesita informacin del propio objeto especfico de la excepcin.
StackOverflowException
El CLR inicia la excepcin StackOverflowException cuando agota el espacio de la pila. El CLR gestiona una estructura de datos llamada stack, que
366
registra los mtodos que han sido llamados y el orden en el que fueron llamados. El CLR tiene una cantidad limitada de espacio de pila disponible y si se llena, se inicia la excepcin. El listado 16.2 muestra la excepcin StackOverflowException. Listado 16.2. Excepcin StackOverflowException
using System; class MainClass { public static void Main() { try { Recursive(); } catch(StackOverflowException) { Console.WriteLine("The CLR is out of stack space."); } } public static void Recursive() { Recursive(); } }
El cdigo del listado 16.2 implementa un mtodo llamado Recursive() , que se llama a s mismo antes de regresar. Este mtodo es llamado por el mtodo M a i n ( ) , y con el tiempo hace que el CLR agote su espacio de pila porque el mtodo Recursive() nunca regresa. El mtodo M a i n ( ) llama a Recursive(), que a su vez llama a Recursive(), que a su vez llama a Recursive() y as sucesivamente. A la larga, el CLR se quedar sin espacio de pila e iniciar la excepcin StackOverflowException.
NullReferenceException
En este ejemplo, el compilador atrapa un intento de eliminar la referencia de un objeto null. El listado 16.3 muestra la excepcin N u l l R e f e r e n c e Exception . Listado 16.3. Excepcin NullReferenceException
using System; class MyClass { public int Value;
367
} class MainClass { public static void Main() { try { MyObject = new MyClass(); MyObject = null; MyObject.Value = 123; // espere a que el usuario compruebe los resultados Console.WriteLine("Hit Enter to terminate..."); Console.Read(); } catch(NullReferenceException) { Console.WriteLine("Cannot reference a null object."); //espere a que el usuario compruebe los resultados Console.Read(); } } }
El cdigo del listado 16.3 declara una variable de objeto de tipo MyClass y asigna a la variable el valor de null (si no se usa la instruccin new, sino que slo se declara una variable de objeto de tipo M y C l a s s , el compilador emitir el siguiente mensaje de error cuando se compile: "Uso de la variable local no asignada M y O b j e c t " ) . A continuacin intentar trabajar con el campo pblico V a l u e del objeto, lo que no est permitido porque no se puede hacer referencia a objetos null. El CLR atrapa este error e inicia la excepcin N u l l R e f e r e n c e Exception .
TypeInitializationException
El CLR inicia la excepcin TypeInitializationException cuando una clase define un constructor esttico y el constructor inicia una excepcin. Si no hay bloques catch en el constructor para atrapar la excepcin, el CLR inicia una excepcin TypeInitializationException.
InvalidCastExpression
El CLR inicia la excepcin InvalidCastExpression si falla una conversin explcita. Esto puede ocurrir en contextos de interfaz. El listado 16.4 muestra una excepcin InvalidCastExpression.
368
does not
implement the
El cdigo del listado 16.4 usa un operador de conversin de tipo explcito para obtener una referencia a una interfaz .NET llamada IFormattable . Como la clase M a i n C l a s s no implementa la interfaz IFormattable, la operacin de conversin explcita falla y el CLR inicia la excepcin I n v a l i d C a s t Exception .
ArrayTypeMismatchException
El CLR inicia la excepcin ArrayTypeMismatchException cuando el cdigo intenta almacenar un elemento en una matriz cuyo tipo no coincide con el tipo del elemento.
IndexOutOfRangeException
El CLR inicia la excepcin IndexOutOfRangeException cuando el cdigo intenta almacenar un elemento en una matriz empleando un ndice de elemento que est fuera del rango de la matriz. El listado 16.5 describe la excepcin IndexOutOfRangeException.
369
El cdigo del listado 16.5 crea una matriz con cinco elementos y a continuacin intenta asignar un valor al elemento 10 de la matriz. Como el ndice 10 est fuera del rango de la matriz de nmeros enteros, el CLR inicia la excepcin
IndexOutOfRangeException.
DivideByZeroException
El CLR inicia la excepcin DivideByZeroException cuando el cdigo intenta realizar una operacin que da como resultado una divisin entre cero.
OverflowException
El CLR inicia la excepcin OverflowException cuando una operacin matemtica guardada por el operador de C# checked da como resultado un desbordamiento. El listado 16.6 muestra la excepcin OverflowException.
Listado 16.6. Excepcin OverflowException
using System; class MainClass
370
{ public static void Main() { try { checked { int Integer1; int Integer2; int Sum; Integer1 = 2000000000; Integer2 = 2000000000; Sum = Integer1 + Integer2; } // espere a que el usuario compruebe los resultados Console.WriteLine("Hit Enter to terminate..."); Console.Read(); } catch(OverflowException) { Console.WriteLine ( " A mathematical overflow.");
operation caused an
El cdigo del listado 16.6 suma dos nmeros enteros, cada uno con un valor de dos mil millones. El resultado, cuatro mil millones, se asigna a un tercer nmero entero. El problema es que el resultado de la suma es mayor que el valor mximo que se puede asignar a un nmero entero de C# y se inicia una excepcin de desbordamiento matemtico.
371
Algunas de estas propiedades pueden especificarse en uno de los constructores de la clase System.Exception :
public Exception(string message); public Exception(string message, Exception innerException);
Las excepciones definidas por el usuario pueden llamar al constructor de clase base en su constructor, de modo que se puedan asignar valores a las propiedades, como muestra el siguiente cdigo:
372
using System; class MyException : ApplicationException { public MyException() : base("This is my exception message.") { } }
Este cdigo define una clase llamada 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.
373
// espere a que el usuario compruebe los resultados Console.Read(); } } public void ThrowException() { throw new MyException(); } }
El cdigo del listado 16.7 declara una clase new llamada MyException , que deriva de la clase base ApplicationException definida por .NET Framework. La clase M a i n C l a s s contiene un mtodo llamado ThrowException , que inicia un nuevo objeto de tipo MyException. El mtodo es invocado por el mtodo M a i n ( ) , que encierra la llamada en un bloque try . El mtodo M a i n ( ) tambin contiene un bloque catch , cuya implementacin escribe el mensaje de la excepcin en la consola. Como el mensaje se estableci cuando 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 is my exception message.
374
{ public CoordinateOutOfRangeException() coordinate is out of range.") { } } public class Point { private int XCoordinate; private int YCoordinate; public int X { get { return XCoordinate; } set { if((value >= 0) && (value < 640)) XCoordinate = value; else throw new CoordinateOutOfRangeException(); } } public int Y { get { return YCoordinate; } set { if ((value >= 0) && (value < 480)) YCoordinate = value; else throw new CoordinateOutOfRangeException(); } } public static void Main() { Point MyPoint = new Point(); try { MyPoint.X = 100; MyPoint.Y = 200; Console.WriteLine("({0}, MyPoint.X = 1500; MyPoint.Y = 600; : base ("The supplied
375
Console.WriteLine("({0},
// espere a que el usuario compruebe los resultados Console.WriteLine("Hit Enter to terminate..."); Console.Read(); } catch(CoordinateOutOfRangeException CaughtException) { Console.WriteLine(CaughtException.Message); // espere a que el usuario compruebe los resultados Console.Read(); } catch { Console.WriteLine("An unexpected caught.");
exception was
El cdigo del listado 16.8 comprueba el valor de los descriptores de acceso de la propiedad set para garantizar que el valor proporcionado est dentro de los lmites vlidos. En caso contrario, se inicia una excepcin. La asignacin del primer punto tiene xito ya que los dos valores estn dentro de los lmites vlidos. Sin embargo, el segundo punto no tiene xito, ya que la coordenada x est fuera de los lmites vlidos. Este valor fuera de los lmites vlidos hace que se inicie un objeto de clase CoordinateOutOfRangeException. Si se compila y ejecuta el listado 16.8 se escribe lo siguiente en la consola:
(100, 200) The supplied coordinate is out of range.
Resumen
.NET Framework usa las excepciones para informar 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, adems de trabajar con las excepciones definidas por .NET Framework. El cdigo de C# puede iniciar y atrapar excepciones. Tambin puede atrapar excepciones iniciadas por .NET Framework. La ventaja de usar excepciones reside en que no es necesario comprobar cada llamada de mtodo en busca de un error. Se pueden encerrar un grupo de llamadas de mtodo en un bloque try y se puede escribir cdigo como si cada llamada de
376
mtodo del bloque tuviera xito. Esto hace que el cdigo del bloque try sea mucho ms limpio porque no necesita ninguna comprobacin de errores entre lneas. Cualquier excepcin iniciada desde el cdigo del bloque try es gestionada en un bloque catch .
377
Los captulos anteriores estaban dedicados a las palabras clave que definen el comportamiento de una clase y sus miembros. Por ejemplo, las palabras clave public , private , protected e internal definen la accesibilidad de la declaracin para otras clases del cdigo. Estos modificadores estn implementados por palabras clave predefinidas cuyo significado est integrado en el lenguaje C# y no puede ser cambiado. C# tambin permite mejorar las declaraciones de clases y de miembros de clase mediante informacin que es interpretada por otras clases de C# en tiempo real. Esta informacin se especifica usando un constructor llamado atributo. Los atributos permiten incluir directivas en las clases y en sus miembros. El comportamiento del atributo se define por el cdigo que escribimos o por el cdigo que proporciona .NET Framework. Los atributos permiten ampliar el lenguaje C# mediante la escritura de clases de atributo que mejoran el comportamiento de otras clases cuando se ejecuta el cdigo, aunque escribamos la clase de implementacin de atributo antes de que los otros usuarios apliquen el atributo a sus propias clases. Al compilar aplicaciones, la informacin del atributo que se aade es enviada a los metadatos del ensamblado, lo que permite que otras aplicaciones o herramientas vean que se est usando el atributo. Mediante el desensamblador IL (ILDASM) o las clases del espacio de nombres System.Reflection se pue-
379
de comprobar fcilmente qu atributos se han aadido a las secciones de cdigo y se puede determinar si son tiles. En C# se pueden usar dos tipos de atributo: los que estn integrados en el lenguaje y los atributos personalizados, que podemos crear. En la primera parte de este captulo aprenderemos a usar atributos y examinaremos 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 C#: Clases. Miembros de clase, incluyendo constantes, campos, mtodos, propiedades, eventos, indizadores, sobrecargas de operador, constructores y destructores. Estructuras. Interfaces. Miembros de interfaces, incluyendo mtodos, propiedades, eventos e indizadores. Enumeraciones y miembros de enumeraciones. Delegados.
Para especificar un atributo en el cdigo se escribe su nombre entre corchetes. La especificacin de atributo debe aparecer antes de la declaracin en la que se debe aplicar el atributo. Los atributos ms simples pueden tener este aspecto:
[MyAttribute]
Un buen ejemplo de un atributo simple es el modelo de subprocesos que se usa para crear una aplicacin de consola en C#. Observe en el siguiente fragmento de cdigo el atributo [STAThread] aplicado a la funcin M a i n ( ) . Este atributo indica al compilador que la funcin Main() debe introducir un apartado de un nico subproceso (STA)COM antes de que se ejecute algn cdigo basado en COM.
// <summary> // El punto de entrada principal de la aplicacin. // </summary> [STAThread] static void Main(string[] args)
380
Tambin se puede anteponer a un atributo un modificador que defina el elemento 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 enlazar un atributo. La tabla 17.1 enumera los tipos de declaraciones y los elementos a los que hace referencia para los atributos especficos. Los elementos a los que las clases de atributo hacen referencia estn predefinidos: por ejemplo, si una clase .NET contiene una clase de atributo que slo hace referencia a enumeraciones, el atributo slo puede ser usado para enumeraciones y no puede aplicarse a otros constructores del cdigo C#, como clases y estructuras. Ms adelante aprender a especificar los destinos de los atributos para sus clases de atributo personalizadas.
Tabla 17.1. Listado de destinos de atributos
Declaracin Assembly Module Class Struct Interface Enum Delegate Method Parameter Field Property - Indexer Property - Get Accessor Property - Set Accessor Event - Field Event - Property Event - Add Event - Remove
Destino Assembly Module Type Type Type Type Type (por defecto) o Return Value Method (por defecto) o Return Value Param Field Property Method (por defecto) o Return Value Method (por defecto), Param or Return Value Event (por defecto), Field o Method Event (por defecto), Property Method (por defecto) o Param Method (por defecto) o Param
381
Por ejemplo, para enlazar explcitamente un atributo a un mtodo, se debe escribir algo parecido a esto:
[method:MyAttribute] int MyMethod() { }
Los modificadores de atributo son tiles en las situaciones en las que su enlace puede ser ambiguo, como muestra el siguiente ejemplo:
[MyAttribute] int MyMethod() { }
En realidad, este ejemplo no es muy aclaratorio. Se aplica el atributo M y A t t r i b u t e al mtodo o a su tipo devuelto? Si se especifica explcitamente el enlace, como se muestra en el anterior ejemplo, se indica al compilador de C# que el atributo se aplica a todo el mtodo. En el ejemplo en el que el atributo [STAThread] se aplica a la funcin Main() cuando se crea una aplicacin de consola, se puede hacer la siguiente modificacin para hacer ms evidente el enlace:
/// <summary> /// El punto de entrada principal de la aplicacin. /// </summary> [method: STAThread] static void Main(string[] args) { // // TODO: Aada el cdigo para iniciar la aplicacin aqu // }
Algunos atributos estn construidos para aceptar parmetros. Los parmetros de atributo siguen al nombre del atributo y estn entre parntesis. Los parntesis estn a su vez entre corchetes. Un atributo con un parmetro puede tener este aspecto:
[MyAttribute(Parameter)]
Ahora que tiene un conocimiento bsico de la sintaxis de los atributos, podemos examinar las clases de atributos integradas que ofrece .NET. Observe que las clases de atributos funcionan en todos los lenguajes, de modo que, aunque escriba atributos para los tipos de C#, la informacin de atributo puede ser usada por Visual Basic .NET, JScript .NET y todos los lenguajes orientados al entorno comn de ejecucin (CLR). El objetivo del uso de atributos es aumentar la funcionalidad del lenguaje.
382
Sin atributos, no sera posible informar al compilador de C# del modo en el que pretendemos usar un mtodo especfico en una DLL externa, y si el lenguaje C# incluyese esta funcionalidad en el lenguaje base, no sera lo suficientemente genrica como para poder ejecutarse en otras plataformas. Con la posibilidad de llamar a componentes Win32 a travs de todas las plataformas, se obtiene control sobre qu propiedades usar, en su caso, cuando se llama a mtodos externos. Como .NET Framework cuenta con tal cantidad de clases de atributos, es imposible describir cada una de ellas en un solo captulo. Adems, como las clases de atributos son especficas de las clases en las que se definen, slo son tiles en el contexto de esas clases. A medida que codifique aplicaciones y se vaya familiarizando con los espacios de nombres de .NET Framework para los que est codificando, las clases de atributos asociados a los espacios de nombres se harn ms transparentes. Algunas clases de atributos reservadas pueden funcionar por s mismas y afectar directamente al lenguaje C#. Las clases System.Obsolete -
383
Attribute,
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.
ConditionalAttribute son clases de atributos que pueden usarse independientemente y que afectan directamente al resultado del cdigo. NOTA: En .NET Framework, las clases de atributos tienen alias, por lo que, cuando se usan clases de atributos, es normal ver el nombre de la clase de atributo sin la palabra "Attribute" a continuacin. El sufijo est implcito, de modo que la forma corta no produce un error. Por ejemplo, O b s o l e t e A t t r i b u t e puede usarse como O b s o l e t e , ya que los atributos estn entre llaves, lo que hace evidente que son atributos y no algn otro tipo de modificador. Observemos algunas de las muchas clases de atributos disponibles en .NET Framework. De esta forma, se acostumbrar al modo de trabajar de estas clases y al modo de aplicar estos atributos en el cdigo de C#.
System.Diagnostics.ConditionalAttribute
El atributo C o n d i t i o n a l es el alias de S y s t e m . D i a g n o s t i c s . ConditionalAttribute , que slo puede aplicarse a declaraciones de mtodos de clase. Especifica que el mtodo slo debe ser incluido como una parte de la clase si el compilador de C# define el smbolo que aparece como parmetro del atributo. El listado 17.1 muestra el funcionamiento del atributo Conditional.
Listado 17.1. Cmo trabajar con el atributo Conditional using System; using System.Diagnostics; public class TestClass { public void Method1() { Console.WriteLine("Hello from Method1!"); } [Conditional("DEBUG")] public void Method2() { Console.WriteLine("Hello from Method2!"); } public void Method3() { Console.WriteLine("Hello from Method3!"); }
384
} class MainClass { public static void Main() { TestClass MyTestClass = new TestClass(); MyTestClass.Method1(); MyTestClass.Method2(); MyTestClass.Method3(); } }
NOTA: Recuerde hacer referencia al espacio de nombres S y s t e m . Diagnostics en el cdigo de modo que no tenga que usar el espacio de nombres completo al usar la clase de atributo Conditional y el compilador de C# pueda encontrar la implementacin de la clase. El listado 17.1 declara dos clases: T e s t C l a s s y M a i n C l a s s . La clase T e s t C l a s s contiene tres mtodos: M e t h o d 1 ( ) , M e t h o d 2 ( ) y M e t h o d 3 ( ) . Las clases M e t h o d 1 ( ) y M e t h o d 3 ( ) se implementan sin atributos, pero M e t h o d 2 ( ) usa el atributo C o n d i t i o n a l con un parmetro llamado DEBUG. Esto significa que el mtodo M e t h o d 2 ( ) es una parte de la clase slo cuando el compilador de C# construye la clase con el smbolo DEBUG definido. Si el compilador de C# construye la clase sin haber definido el smbolo DEBUG, el mtodo no se incluye como una parte de la clase y se pasa por alto cualquier llamada al mtodo. La clase M a i n C l a s s implementa el mtodo M a i n ( ) de la aplicacin, que crea un objeto de tipo T e s t C l a s s y llama a los tres mtodos de la clase. El resultado del listado 17.1 cambia dependiendo del modo en que es compilado el cdigo. En primer lugar, intente compilar el listado 17.1 con el smbolo DEBUG definido. Puede usar el argumento de lnea de comando del compilador de C# /D para definir smbolos para el compilador:
csc /D:DEBUG Listingl7-1.cs
Cuando el cdigo del listado 17.1 se compila mientras el smbolo DEBUG est definido, el mtodo M e t h o d 2 ( ) de la clase T e s t C l a s s se incluye en la construccin y al ejecutar la aplicacin se escribe lo siguiente en la consola:
Hello from Method1! Hello from Method2! Hello from Method3!
385
Cuando se compila el cdigo del listado 17.1 mientras el smbolo DEBUG no est definido, el mtodo M e t h o d 2 ( ) de la clase T e s t C l a s s no se incluye en la construccin y se pasa por alto la llamada al mtodo M e t h o d 2 ( ) realizada en el mtodo M a i n ( ) . Si se crea el cdigo del listado 17.1 sin definir el smbolo DEBUG se genera cdigo que escribe lo siguiente en la consola al ser ejecutado:
Hello from Method1! Hello from Method3!
Como puede ver, el atributo Conditional es eficaz y til. Antes de empezar a usar esta clase, preste atencin a las siguientes reglas: El mtodo marcado con el atributo Conditional debe ser un mtodo de una clase. El mtodo marcado con el atributo Conditional no debe ser un mtodo override . El mtodo marcado con el atributo Conditional debe tener un tipo devuelto void . Aunque el mtodo marcado con el atributo Conditional no debe estar marcado con el modificador override , puede estar marcado con el modificador virtual . Los reemplazos de estos mtodos son implcitamente condicionales y no deben estar marcados explcitamente con un atributo Conditional. El mtodo marcado con el atributo Conditional no debe ser una implementacin de un mtodo de interfaz; en caso contrario, se producir un error en tiempo de compilacin.
System.SerializableAttribute class
El atributo Serializable es el alias de la clase System.SerializableAttribute , que puede ser aplicado a clases. Indica a .NET Framework que los miembros de la clase pueden ser serializados a y desde un medio de almacenamiento, como un disco duro. El uso de este atributo hace que no resulte necesario agregar la funcin de estado en las clases para realizar su almacenamiento en el disco y su posterior recuperacin. Cuando se serializan tipos, todos los datos de la clase marcada como Serializable se guardan 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 atributo NonSerialized, que es el alias de la clase System.NonSerializableAttribute . En el siguiente fragmento de cdigo, los datos de la cadena password marcados como NonSerialized no son persistentes para el archivo o flujo para el que se escriben los datos de la clase:
386
[Serializable()] public class Users{ public string username; public string emailaddress; public string phonenumber; // Aada un campo que no vaya a ser persistente [NonSerialized()] public string password;
public FillData() { username = "admin"; password = "password"; emailaddress = "[email protected]"; phonenumber = "555-1212"; } }
Para mostrar un ejemplo de serializacin completo, el listado 17.2 vuelve a la clase P o i n t 2 D con la que ya hemos trabajado. La clase est marcada con el atributo Serializable, lo que significa que puede ser guardada y leda desde un flujo de datos.
Listado 17.2. Cmo trabajar con el atributo Serializable using System; using System.IO; using System.Runtime.Serialization.Formatters.Binary; [Serializable] class Point2D { public int X; public int Y; } class MyMainClass { public static void Main() { Point2D My2DPoint = new Point2D(); My2DPoint.X = 100; My2DPoint.Y = 200; Stream WriteStream = File.Create("Point2D.bin" ); BinaryFormatter BinaryWrite = new BinaryFormatter(); BinaryWrite.Serialize(WriteStream, My2DPoint); WriteStream.Close(); Point2D ANewPoint = new Point2D();
387
Console.WriteLine("New Point Before Deserialization: ({0}, {1})", ANewPoint.X, ANewPoint.Y); Stream ReadStream = File.OpenRead("Point2D.bin"); BinaryFormatter BinaryRead = new BinaryFormatter(); ANewPoint = (Point2D)BinaryRead.Deserialize(ReadStream); ReadStream.Close(); Console.WriteLine("New Point After Deserialization: ({0}, {1})", ANewPoint.X, ANewPoint.Y); } }
El cdigo del listado 17.2 crea un nuevo objeto Point2D y le otorga las coordenadas (100, 2 0 0 ) . A continuacin serializa la clase en un archivo llamado Point2D.bin. El cdigo crea entonces un nuevo punto y deserializa los contenidos del archivo Point2D.bin en el nuevo objeto Point2D. El proceso de deserializacin 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 cdigo del listado 17.2 se escribe lo siguiente en la consola:
New Point Before Deserialization: (0, 0) New Point After Deserialization: (100, 200)
Cuando se crea el nuevo objeto Point2D, sus miembros se inicializan con sus valores por defecto de 0. El proceso de deserializacin, que asigna los valores de acuerdo con los datos almacenados en el archivo Point2D.bin, cambia los valores. El listado 17.2 emplea dos clases de .NET Framework en su proceso de serializacin. La clase Stream se encuentra en el espacio de nombres System.IO y gestiona el acceso a los flujos de datos, incluyendo los archivos de disco. La clase B i n a r y F o r m a t t e r se encuentra en el espacio de nombres System.Runtime.Serialization.Formatters.Binary y gestiona la serializacin de datos a una representacin binaria. .NET Framework incluye otros formateadores que pueden usarse para representar datos serializados en otros formatos. Por ejemplo, la clase SoapFormatter da a los datos serializados un formato adecuado para una llamada XML SOAP. NOTA: La clase BinaryFormatter es una patente de .NET Framework. Si tiene pensado que su destino sean otros sistemas que pueden no ser compatibles con el formato binario, considere usar la clase SoapFormatter para que persistan los datos de un formato XML que es compatible con otros sistemas.
System.ObsoleteAttribute class
El atributo Obsolete puede ser aplicado a cualquier tipo de C# excepto a ensamblados, mdulos, parmetros y valores devueltos. El atributo Obsolete
388
permite definir fragmentos de cdigo que se van a reemplazar o que ya no son vlidos. Las propiedades Message e IsError de la clase Obsolete otorgan el control del modo en el que el compilador controla los tipos marcados con el atributo Obsolete. Al asignar a la propiedad IsError el valor T r u e , el compilador produce un error y el mensaje de error es la propiedad de cadena asignada a la propiedad Message. El valor por defecto de la propiedad IsError es F a l s e , lo que hace que se produzca un aviso cuando se compila el cdigo. En el siguiente cdigo, el mtodo HelloWorld est marcado como Obsolete.
using System; public class RunThis { public static void Main() { // Esto genera un aviso de tiempo de compilacin. Console.WriteLine(HelloWorld()); Console.ReadLine(); } // Marca HelloWord como Obsolete [Obsolete("Next version uses Hello Universe")] public static string HelloWorld() { return ("HelloWorld"); } }
La figura 17.1 muestra la lista de tareas de los avisos producidos al compilar el cdigo anterior.
Lista de tareas - 1 tarea(s): Error al generar (filtro) Descripcin 'RunThis.HelloWorld()' est obsoleto: 'Next version uses Hello Universe'
Lista de tareas
Resultados
Si quiere asegurarse de que se produce un error y no slo un mensaje de aviso, puede modificar el cdigo marcado asignando a la propiedad IsError el valor t r u e y la clase no se compilar. Si modifica el atributo Obsolete del cdigo anterior con la siguiente lnea, se produce un error:
[Obsolete("Next version uses Hello Universe", true)]
Como puede ver, el uso del atributo Obsolete permite mantener el cdigo existente mientras nos aseguramos de que los programadores no estn usando tipos desfasados.
389
Esta clase define un atributo llamado CodeAuthorAttribute . Este nombre de atributo puede usarse como un atributo una vez que se ha definido la clase. Si el nombre de atributo termina con el sufijo Attribute, el nombre de atributo puede ser usado entre corchetes y sin el sufijo:
[CodeAuthorAttribute] [CodeAuthor]
Estos dos atributos hacen referencia a la clase CodeAuthorAttribute. Tras definir una clase de atributo, se usa como cualquier otra clase de atributo .NET.
390
atributo Obsolete solamente debe aplicarse a mtodos y no a otros constructores de C#. La clase de atributo AttributeUsage contiene una enumeracin pblica llamada AttributeTargets , cuyos miembros aparecen en la tabla 17.1. Estos miembros AttributeTargets pueden aparecer juntos en una expresin OR y ser usados como parmetros del atributo AttributeUsage para especificar que la clase de atributo define un atributo que slo puede usarse en determinados contextos, como muestra el siguiente ejemplo:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] public class CodeAuthorAttribute : Attribute { }
Este constructor declara una clase llamada CodeAuthorAttribute y especifica que el atributo slo puede ser usado con clases y estructuras. El compilador de C# le fuerza a usar el atributo para asegurarse de que se emplea de acuerdo con los valores de la enumeracin de AttributeTargets especificados en el atributo AttributeUsage. Si usa un atributo en una expresin que no est permitida en la definicin del atributo, el compilador emitir un error. Por ejemplo, suponga que escribe un atributo llamado Name y slo usa la enumeracin AttributeTargets.Class como parmetro del atributo AttributeUsage:
[AttributeUsage(AttributeTargets.Class)] public class NameAttribute : Attribute { }
Si a continuacin intenta aplicar el atributo Name a algo que no sea una clase, el compilador emitir un mensaje de error parecido al siguiente:
error CS0592: El atributo "Name" no es vlido en este tipo de declaracin. Slo es vlido en declaraciones "class".
391
true)]
El uso de varios atributos permite asignar varios valores a un constructor de C# usando un solo atributo. El siguiente constructor marca el atributo Name como un atributo de varios usos y permite a los programadores usar el atributo ms de una vez en un solo elemento de C#:
[Name("Jeff Ferguson")] [Name("Jeff Ferguson's Assistant")] public class MyClass { }
Los atributos de varios usos tambin pueden aparecer en un solo conjunto de corchetes, separados por una coma:
[Name("Jeff Ferguson"), Name("Jeff Ferguson's Assistant")] public class MyClass { }
392
Este atributo necesita que se suministre un parmetro de cadena cada vez que es usado:
[CodeAuthor("Jeff Ferguson")]
Debe suministrar los parmetros especificados en el constructor de la clase cuando se usa el atributo. En caso contrario, obtendr un error del compilador:
error CS1501: Ninguna sobrecarga para el mtodo 'CodeAuthorAttribute' adquiere '0' argumentos
Los parmetros proporcionados al constructor de la clase de atributo reciben el nombre de parmetros posicionales. Los parmetros posicionales asocian los datos de parmetro con sus nombres de parmetro basndose en la posicin de los datos en la lista de parmetros. Por ejemplo, el elemento de datos del segundo parmetro est asociado a la variable del segundo parmetro especificada en la lista de parmetros de la declaracin de la funcin. Tambin se pueden proporcionar parmetros con nombre, que son almacenados por las propiedades implementadas en la clase de atributo. Los parmetros con nombre se especifican con el nombre de la propiedad, un signo igual y el valor de la propiedad. Los parmetros con nombre se asocian a datos de parmetros con el nombre del parmetro basado en el nombre del parmetro que aparece antes del valor. Dado que la asociacin entre un nombre de variable y su valor se especifica mediante el nombre del parmetro y no mediante la posicin del valor en la lista de parmetros, los parmetros con nombre pueden aparecer en cualquier orden. Suponga que aade un parmetro con nombre llamado Date al atributo CodeAuthorAttribute . Esto significa que la clase puede admitir una propiedad llamada Date cuyo valor puede asignarse en la definicin del atributo:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct) ] public class CodeAuthorAttribute : Attribute { public CodeAuthorAttribute(string Name) { } public string Date { set { } } }
Tras definir la propiedad, un parmetro con nombre puede establecer su propiedad cuando el atributo aparezca en el cdigo:
[CodeAuthor("Jeff Ferguson", Date = "Apr 01 2001")]
393
A diferencia de los parmetros posicionales, los parmetros con nombre son opcionales y pueden omitirse de una especificacin de atributo.
394
{ Console.WriteLine("Hello from Method3!"); } } public class MainClass { public static void Main() { TestClass MyTestClass = new TestClass(); MyTestClass.Method1(); MyTestClass.Method2(); MyTestClass.Method3(); object [] ClassAttributes; MemberInfo TypeInformation; TypeInformation = typeof(TestClass); ClassAttributes = TypeInformation.GetCustomAttributes(typeof(ClassAuthorAttribute), false); if(ClassAttributes.GetLength(0) != 0) { ClassAuthorAttribute ClassAttribute; ClassAttribute = (ClassAuthorAttribute) (ClassAttributes[0]); Console.WriteLine("Class Author: {0}", ClassAttribute.Author); } } }
El cdigo del listado 17.3 comienza con una nueva clase de atributo llamada CodeAuthorAttribute . La clase sirve como una clase de atributo para un atributo que slo puede aplicarse a otras clases. La clase recibe un parmetro de cadena, que se almacena en una variable privada y al que se accede pblicamente mediante una propiedad de slo lectura llamada Author . La intencin del parmetro es marcar una clase como poseedora de un nombre de programador especfico adjunto, de modo que los dems programadores sepan con quin deben contactar si tienen alguna duda sobre la implementacin de la clase. La clase TestClass usa el atributo CodeAuthor y proporciona el parmetro
Jeff Ferguson.
El rasgo ms interesante del listado 17.3 es el mtodo 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 reflexin, que implementa las clases de un espacio de nombres .NET llamado System.Reflection . Mediante la reflexin, el cdigo puede, en tiempo de ejecucin, estudiar la implementacin de una clase y descu-
395
brir cmo est construida. La reflexin permite que el cdigo examine otros fragmentos de cdigo para derivar informacin, como los mtodos y propiedades que admite y la clase base de la que deriva. La reflexin es una funcin muy potente y es completamente compatible con .NET Framework. El cdigo del listado 17.3 usa la reflexin para obtener una lista de atributos asociados a una clase particular. El cdigo de atributo comienza recuperando un objeto Type para la clase TestClass. Para conseguir el objeto Type se usa el operador de C# t y p e o f ( ) . Este operador toma como argumento el nombre de la clase cuyo tipo de informacin se va a recuperar. El objeto Type devuelto, que est definido en el espacio de nombres de .NET Framework System , funciona como una tabla de contenidos, describiendo todo lo que se debe saber sobre la clase requerida. Despus de recuperar el objeto Type para la clase, el mtodo M a i n ( ) llama a un mtodo llamado GetCustomAttributes() para conseguir una lista de los atributos que permite la clase descrita por el objeto Type. Este mtodo devuelve una matriz de objetos y acepta como parmetro el tipo del atributo que debe recuperarse. En el listado 17.3, el mtodo GetCustomAttributes() es invocado con informacin de tipo para la clase CodeAuthorAttribute como parmetro. Esto obliga al mtodo GetCustomAttributes() a devolver slo informacin sobre los atributos de clase que sean del tipo CodeAuthorAttribute. Si la clase hubiera usado algn otro atributo, la llamada no podra devolverlos. El cdigo del listado 17.3 finaliza tomando el primer atributo CodeAuthorAttribute de la matriz y solicitndole el valor de su propiedad Author . El valor de la cadena se escribe en la consola. Si se ejecuta el cdigo del listado 17.3 se escribe lo siguiente en la consola (si compila el cdigo sin definir el smbolo DEBUG ):
Hello from Method1! Hello from Method3! Class Author: Jeff Ferguson
Resumen
.NET Framework permite usar atributos en los lenguajes que se ejecutan con el CLR. El concepto de atributo abre las puertas a la expansin de la funcionalidad de los lenguajes .NET con clases que pueden agregar comportamientos al cdigo. El lenguaje C# permite el uso de atributos creados por otras personas en su cdigo C# y tambin la creacin de atributos propios, que pueden ser usados por otros programadores de .NET. El concepto de atributo no es exclusivo de C#; ms bien, est disponible para cualquier lenguaje que se ejecute con el CLR. Los atributos le conceden la posibilidad de extender el entorno del lenguaje y aportan nuevas herramientas para que los programadores trabajen con cdigo .NET. El proceso de serializacin es un
396
buen ejemplo de esto. La serializacin no est integrada en la especificacin 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 ejecucin para que admita una caracterstica que no fue diseada en ese lenguaje. Al igual que los dems constructores de .NET Framework, los atributos son objetos. Se definen por clases que derivan de la clase System.Attribute de .NET Framework. Puede usar C# para desarrollar nuevas clases de atributos con slo derivar una nueva clase de la clase base System.Attribute. Los atributos que desarrolle en C# y los atributos ya definidos por .NET Framework pueden ser usados por cualquier lenguaje compatible con el CLR. Los atributos se usan especificando entre corchetes el nombre de clase del atributo, inmediatamente antes del constructor de C# al que se aplica el atributo. Los atributos pueden aceptar datos en forma de parmetros, que pueden asociar datos de estado al atributo. Estos datos pueden ser recuperados por cdigo de reflexin que puede consultar el cdigo y buscar atributos.
397
399
bras clave. Si recuerda el captulo 8, las clases que creamos y usamos pueden ser consideradas clases base. Estas clases tienen la funcionalidad bsica que necesitan las aplicaciones. Al declarar una instancia de una clase, est derivando de esa clase para usar su funcionalidad. Las bibliotecas de clases base de .NET Framework estn basadas en este modelo; todo lo que hacemos mientras programamos aplicaciones .NET est basado en una clase base. Todo el entorno deriva de la clase base System.Object , de modo que incluso cuando deriva una simple variable est derivando una funcionalidad de la clase base System.Object. El listado 18.1 muestra las caractersticas de las clases base y derivadas.
Listado 18.1. Una clase base y una clase derivada using System; public class BaseClass { protected int Value; public BaseClass() { Value = 123; ) } public class DerivedClass : BaseClass { public void PrintValue() { Console.WriteLine("Value = " + Value); } } class MainClass { public static void Main ( ) { DerivedClass DerivedClassObject = new DerivedClass(); DerivedClassObject.PrintValue(); } }
El cdigo del listado 18.1 es relativamente sencillo. Contiene una clase base llamada BaseClass que incluye una variable entera protegida. Otra clase, llamada DerivedClass, deriva de la clase BaseClass e implementa un mtodo llamado P r i n t V a l u e ( ) . El mtodo M a i n ( ) crea un objeto de tipo DerivedClass y llama a su mtodo PrintValue(). Si se ejecuta el cdigo del listado 18.1 se escribe lo siguiente en la consola:
Value = 123
400
Ahora suponga que los requisitos cambian y otro programador decide desarrollar la clase BaseClass mientras continuamos trabajando en nuevas mejoras para la clase DerivedClass. Qu ocurrir si el otro programador agrega un mtodo a la clase BaseClass llamado PrintValue() y proporciona una implementacin ligeramente diferente? El cdigo sera como el listado 18.2.
Listado 18.2. Adicin de PrintValue() a la clase BaseClass using System; public class BaseClass { protected int Value; public BaseClass() { Value = 123; } public virtual void PrintValue() { Console.WriteLine("Value: " + Value) ; ( } public class DerivedClass : BaseClass { public void PrintValue() { Console.WriteLine("Value = " + Value) ; } } class MainClass { public static void Main() { DerivedClass DerivedClassObject = new DerivedClass(); DerivedClassObject.PrintValue(); } }
Ahora tenemos un problema. La clase DerivedClass deriva de la clase BaseClass y ambas implementan un mtodo llamado PrintValue(). La clase BaseClass ha sido actualizada a una nueva versin, mientras que la clase DerivedClass ha permanecido con su implementacin original. En el listado 18.2, la relacin entre el mtodo PrintValue() de la clase base y el mtodo PrintValue() de la clase derivada no est clara. El compilador debe saber que mtodo reemplaza a la versin de la clase base. Y el compilador no sabe qu
401
ste es un buen aviso, porque la filosofa del lenguaje C# fomenta la claridad y el compilador de C# siempre avisa sobre los constructores de cdigo que no estn claros.
402
{ Value = 123; } public void PrintValue() { Console.WriteLine("Value: } } public class DerivedClass : BaseClass { new public void PrintValue() { Console.WriteLine("Value = " + Value); } } class MainClass { public static void Main() { DerivedClass DerivedClassObject = new DerivedClass(); DerivedClassObject.PrintValue(); } }
" + Value);
NOTA: El operador new y el modificador new son implementaciones diferentes de la palabra clave new. El operador new se usa para crear objetos, mientras que el modificador new se usa para ocultar un miembro heredado de un miembro de clase base. El cdigo del listado 18.3 usa la palabra clave new en la implementacin del mtodo PrintValue() de la clase DerivedClass . Esto indica al compilador de C# que debe tratar este mtodo como distinto del mtodo de la clase base, aunque los dos mtodos tengan el mismo nombre. El uso de la palabra clave resuelve la ambigedad y permite que el compilador de C# compile el cdigo sin emitir advertencias. En este caso, el mtodo M a i n ( ) llama al mtodo de la clase derivada y el listado 18.3 escribe lo siguiente en la consola:
Value = 123
Todava puede ejecutar el mtodo de la clase base porque la palabra clave new ha asegurado bsicamente que los dos mtodos PrintValue() de cada una de las clases puedan ser llamados por separado. Puede llamar al mtodo de la clase base convirtiendo explcitamente el objeto de la clase derivada en un objeto del tipo de la clase base:
403
BaseClass
BaseClassObject
(BaseClass)DerivedClassObject;
BaseClassObject.PrintValue();
Como puede ver, el uso del modificador 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 para estar seguro de que est usando la funcionalidad correcta.
404
} class MainClass { public static void Main() { DerivedClass DerivedClassObject = new DerivedClass(); DerivedClassObject.PrintValue(); } }
En el listado 18.4, la palabra clave override indica al compilador de C# que la implementacin de PrintValue() en la clase derivada reemplaza a la implementacin del mismo mtodo en la clase base. La implementacin de la clase base est oculta bsicamente a los invocadores. A diferencia del listado 18.3, el cdigo del listado 18.4 slo contiene una implementacin de PrintValue(). La implementacin de la clase base de PrintValue() no es accesible al cdigo del mtodo M a i n ( ) , incluso si el cdigo convierte explcitamente el objeto de la clase derivada en un objeto de clase base y llama al mtodo en el objeto de la clase base. Debido a que la palabra clave override se usa en el listado 18.4, todas las llamadas al mtodo realizadas mediante un objeto convertido explcitamente son dirigidas a la implementacin reemplazada de la clase derivada. Observe el cdigo usado en la llamada a la implementacin de la clase base de PrintValue() cuando se usa el operador new para resolver la ambigedad:
BaseClass BaseClassObject = (BaseClass)DerivedClassObject;
BaseClassObject.PrintValue();
Este cdigo no es suficiente para hacer que se llame a la implementacin de la clase base cuando se use la palabra clave override. Esto es debido a que el objeto fue creado como un objeto de la clase DerivedClass . Puede llamar al mtodo de la clase base pero, debido a que la implementacin de la clase base ha sido reemplazada con el cdigo de la clase derivada, se seguir invocando a la implementacin de la clase derivada. Debe usar la palabra clave de C# base para llamar a la implementacin de la clase base, como en el siguiente ejemplo:
base.PrintValue();
Esta instruccin llama a la implementacin de PrintValue() que se encuentra en la clase base de la clase actual. Al colocar esta instruccin en la clase DerivedClass , por ejemplo, se llama a la implementacin de PrintValue() que se encuentra en la clase BaseClass . Puede considerar el uso de la palabra clave base igual al uso de la sintaxis completa y con ruta de acceso namespace.object.method , ambas simplemente hacen referencia a la instancia de clase base correcta que se est usando.
405
Resumen
Los ejemplos de este captulo situaban juntas todas las clases del listado en un nico archivo fuente para hacerlos ms sencillos. Sin embargo, la programacin en el mundo real puede ser ms complicada. Si varios programadores estn trabajando en un nico proyecto, el proyecto podra estar formado por ms de un archivo fuente. El programador que trabaja en la clase base podra colocarla en un archivo fuente de C# y el programador que trabaja en la clase derivada puede colocarla en otro archivo fuente de C#. El asunto podra ser an ms complicado si la clase base se compila en un ensamblado y se implementa la clase derivada en un proyecto que hace referencia al ensamblado. Lo importante en este caso es que las clases base y las clases derivadas pueden proceder de varias fuentes diferentes y que coordinar la programacin de las clases cobra una gran importancia. Es de vital importancia comprender que, con el tiempo, las clases base y las clases derivadas aadirn funcionalidades a medida que el proyecto progrese. Como programador, debera tener esto en cuenta: disee sus clases de modo que puedan ser usadas en varias versiones de un proyecto y puedan evolucionar a medida que evolucionen los requisitos del proyecto. A priori la otra solucin para los problemas que presentan las versiones es incluso ms sencilla: no use el mismo nombre para mtodos que tengan implementaciones diferentes a menos que est reemplazando realmente la funcionalidad de la clase base. Aunque, en teora, ste puede parecer el mejor medio para solventar el problema, en la prctica no siempre es posible. Las palabras clave de C# new y override le ayudan a evitar este problema de programacin y le permiten reutilizar nombres de mtodos en caso de que sea necesario para su proyecto. El principal uso de la palabra clave override es avisar de la creacin de nuevas implementaciones de mtodos virtuales en clases base, pero tambin tiene un papel en las versiones de C#.
406
409
C# puede trabajar con variables sin que necesitemos conocer los detalles de cmo y dnde se almacenan las variables en memoria. Como el CLR protege el cdigo de C# de estos detalles de la memoria, el cdigo de C# est libre de errores relacionados con el acceso directo a la memoria. No obstante, alguna vez tendr que trabajar con una direccin de memoria especfica del cdigo C#. El cdigo puede necesitar un poco ms de rendimiento o quizs quiera que el cdigo de C# trabaje con cdigo heredado que necesite que le proporcione la direccin de un fragmento de memoria especfico. El lenguaje C# admite un modo especial, llamado modo no seguro, que permite trabajar directamente con memoria desde el interior del cdigo C#. Este constructor de C# especial recibe el nombre de modo no seguro porque el cdigo ya no dispone de la proteccin que ofrece la gestin de memoria del CLR. En el modo no seguro, el cdigo de C# puede acceder directamente a la memoria, y puede tener los mismos fallos relacionados con la memoria que el cdigo de C y C++ si no es extremadamente cuidadoso con su forma de gestionar la memoria. En este captulo estudiaremos el modo no seguro del lenguaje C# y cmo puede ser usado para permitirle acceder directamente a las direcciones de memoria usando constructores al estilo de los puntero de C y C++.
Esta instruccin declara un puntero entero llamado MyIntegerPointer . El tipo del puntero indica el tipo de la variable a la que el puntero puede apuntar. Por ejemplo, un puntero entero slo puede apuntar a memoria usada por una variable entera. Los punteros deben ser asignados a una direccin de memoria y C# hace que sea fcil escribir una expresin que evale la direccin de memoria de una variable. Si se antepone el operador de concatenacin o smbolo de unin a una expresin unaria, devuelve una direccin de memoria, como se muestra en el siguiente ejemplo:
int MyInteger = 123; int * MyIntegerPointer = &MyInteger;
El cdigo anterior hace dos cosas: Declara una variable entera llamada MyInteger y le asigna el valor 123.
410
Declara una variable entera llamada MyIntegerPointer y la apunta a la direccin de la variable MyInteger .
MyInteger
123
MyIntegerPointer
Los punteros en realidad tienen dos valores: El valor de la direccin de memoria del puntero El valor de la variable a la que apunta el puntero
C# permite escribir expresiones que evalen cualquiera de los dos valores. Si se antepone un asterisco al identificador del puntero, se obtiene el valor de la variable a la que apunta el puntero, como demuestra el siguiente cdigo:
int MyInteger = 123;
Tipos de punteros
Los punteros pueden tener uno de los siguientes tipos: sbyte byte short ushort int uint
411
long ulong char float double decimal bool un tipo de enumeracin void , usado para especificar un puntero para un tipo desconocido
No se puede declarar un puntero para un tipo de referencia tal como un objeto. La memoria para los objetos est gestionada por el CLR y la memoria puede ser borrada cada vez que el recolector de elementos no utilizados necesite liberar la memoria del objeto. Si el compilador de C# le permite mantener un puntero sobre un objeto, su cdigo corre el riesgo de apuntar a un objeto cuya memoria puede ser liberada en otro punto por el recolector de elementos no utilizados del CLR. Imagine que el compilador de C# permitiese escribir cdigo como el siguiente:
MyClass MyObject = new MyClass(); MyClass * MyObjectPointer; MyObjectPointer = &MyObject;
La memoria usada por MyObject es gestionada automticamente por el CLR y dicha memoria es liberada cuando todas las referencias al objeto se liberan y se ejecuta el recolector de elementos no utilizados del CLR. El problema es que el cdigo no seguro ahora contiene un puntero al objeto, y el resultado ser un puntero que apunta hacia un objeto cuya memoria se ha liberado. El CLR no tiene ningn modo de saber que hay un puntero para el objeto y el resultado es que, despus de que el recolector de elementos no utilizados haya liberado la memoria, habr un puntero apuntando hacia la nada. C# solventa este problema no permitiendo que existan variables a tipos de referencia con memoria que es gestionada por el CLR.
412
El cdigo no seguro permite escribir cdigo que acceda directamente a la memoria, sin hacer caso de los objetos que gestionan la memoria en las aplicaciones. Como a las direcciones de memoria se accede directamente, el cdigo no seguro puede funcionar mejor en algunos tipos de aplicaciones. Esta instruccin compila el archivo fuente file1.cs y permite compilar el cdigo no seguro de C#. NOTA: En C#, el cdigo no seguro permite declarar y usar punteros del mismo modo que en C++.
Los punteros slo son vlidos en C# en cdigo no seguro y hay que definir explcitamente el cdigo no seguro al compilador. Para hacerlo se emplea la palabra clave de C# unsafe . La palabra clave unsafe debe aplicarse a un bloque de cdigo que use punteros. Para especificar que un bloque de cdigo se ejecute en el modo no seguro de C# se aplica la palabra clave unsafe a la declaracin del cuerpo de cdigo, como se muestra en el listado 19.1.
Listado 19.1. Mtodos no seguros
using System;
public class MyClass { public unsafe static void Main() { int MyInteger = 123; int * MyIntegerPointer = &MyInteger; Console.WriteLine(*MyIntegerPointer); } }
El mtodo M a i n ( ) del listado 19.1 usa el modificador unsafe en su declaracin. Esto indica al compilador de C# que todo el cdigo del mtodo debe ser considerado no seguro. Despus de usar esta palabra clave, el cdigo del mtodo puede usar constructores de punteros no seguros.
413
La palabra clave unsafe se aplica slo al mtodo en el que aparece. Si la clase del listado 19.1 va a contener otro mtodo, ese otro mtodo no podr usar constructores de punteros no seguros a menos que, tambin, sea declarado con la palabra clave unsafe. Las siguientes reglas se aplican al modificador unsafe. Clases, estructuras y delegados pueden incluir el modificador unsafe. que indica que todo el cuerpo del tipo se considera no seguro. Campos, mtodos, propiedades, eventos, indizadores, operadores, constructores, destructores y constructores estticos pueden definirse con el modificador unsafe, que indica que la declaracin del miembro especfico no es segura. Un bloque de cdigo puede ser marcado con el modificador unsafe, que indica que todo el cdigo debe ser considerado no seguro.
PointerToMyPoint->X,
414
El listado 19.2 contiene la declaracin de una estructura llamada P o i n t 2 D . La estructura tiene dos miembros pblicos. El listado tambin incluye un mtodo no seguro M a i n ( ) que crea una nueva variable del tipo de la estructura y crea un puntero para la nueva estructura. A continuacin, el mtodo usa el operador de acceso a miembros del puntero para asignar valores a la estructura, que se escribe en la consola. Esto es diferente del acceso a miembros del modo seguro, por defecto, de C#, que usa el operador '.'. Si se usa un operador incorrecto en un modo incorrecto, el compilador de C# emite un error. Si usa el operador '.' con un puntero no seguro, el compilador de C# emite el siguiente mensaje de error:
error CS0023: El operador '.' no se puede aplicar a operandos del tipo 'Point2D*'
Si se usa el operador -> en un contexto seguro, el compilador de C# tambin emite un mensaje de error:
error CS0193: El operador * o -> se debe aplicar a un puntero
415
sin preocuparse de que el CLR pueda mover la direccin de memoria de la variable de la direccin a la que apunta su puntero. Para especificar que la direccin de memoria de una variable debe ser fija se usa la palabra clave de C# fixed . La palabra clave fixed va seguida de una expresin entre parntesis que contiene una declaracin de puntero con una asignacin a una variable. La expresin fijada va seguida de un bloque de cdigo y la variable fijada permanece en la misma direccin de memoria a lo largo del bloque de cdigo fijado, como se muestra en el listado 19.3.
Listado 19.3. Cmo fijar en memoria los datos gestionados using System; public class MyClass { public unsafe static void Main() { int ArrayIndex; int [] IntegerArray; IntegerArray = new int [5]; fixed(int * IntegerPointer = IntegerArray) { for(ArrayIndex = 0; ArrayIndex < 5; ArrayIndex++) IntegerPointer[ArrayIndex] = ArrayIndex; } for(ArrayIndex = 0; ArrayIndex < 5; ArrayIndex++) Console.WriteLine(IntegerArray[ArrayIndex]); } }
La palabra clave fixed del listado 19.3 declara un puntero entero que apunta a una matriz entera. Va seguida por un bloque de cdigo que escribe valores en la matriz usando un puntero. Dentro de este bloque de cdigo, est garantizado que la direccin de la matriz IntegerArray es fija y que el CLR no mover su posicin. Esto permite al cdigo usar un puntero con la matriz sin preocuparse de si el CLR va a mover la posicin de la memoria fsica de la matriz. Despus de que el bloque de cdigo fijado termine, ya no puede usarse el puntero y el CLR vuelve a tener en cuenta a la variable IntegerArray cuando reubica la memoria.
416
Esta lnea de cdigo trata al puntero como si fuese una matriz. La sintaxis del elemento de matriz puntero permite al cdigo de C# no seguro ver la memoria a la que apunta el puntero como una matriz de variables en la que se puede escribir y leer de ella.
Al igual que los tipos de valores, estos operadores devuelven los valores booleanos True y False cuando se usan con tipos de puntero.
En este bloque de cdigo, el puntero es desplazado por un valor y la suma se usa para apuntar a una direccin de memoria. La siguiente instruccin realiza aritmtica de puntero:
*(IntegerPointer + ArrayIndex) = ArrayIndex;
Esta instruccin debe interpretarse como: "Toma el valor de IntegerP o i n t e r e incremntalo en el nmero de posiciones especificadas por ArrayIndex . Coloca el valor de ArrayIndex en esa posicin". La aritmtica de punteros aumenta la posicin de un puntero en un nmero especificado de bytes, dependiendo del tamao del tipo al que se est apuntando.
417
El listado 19.3 declara una matriz entera y un puntero entero. Cuando se usa aritmtica de punteros en el puntero entero, el valor usado para modificar el puntero especifica el nmero de "tamaos de variable" que deben moverse, no el nmero de bytes. La siguiente expresin usa aritmtica de punteros para desplazar la localizacin de un puntero en tres unidades:
IntegerPointer + 3
El valor literal 3 de esta expresin especifica que se debe incrementar el puntero en el espacio que ocupan tres nmeros enteros, no en tres bytes. Dado que el puntero apunta a un nmero entero, el 3 se interpreta como "espacio necesario para tres nmeros enteros" y no "espacio para tres bytes". Dado que un nmero entero ocupa cuatro bytes de memoria, la direccin del puntero se aumenta en doce bytes (tres nmeros enteros multiplicado por cuatro bytes por cada nmero entero), no en tres.
Expresin sizeof(sbyte) sizeof(byte) sizeof(short) sizeof(ushort) sizeof(int) sizeof(uint) sizeof(long) sizeof(ulong) sizeof(char) sizeof(float) sizeof(double) sizeof(bool)
Resultado 1 1 2 2 4 4 8 8 2 4 8 1
418
Tras la palabra clave stackalloc se escribe un tipo de dato. Devuelve un puntero al bloque de memoria al que se le asigna el espacio y se puede usar la memoria exactamente igual que la memoria gestionada por el CLR. No hay una operacin explcita para liberar la memoria asignada por la palabra clave stackalloc. La memoria se libera automticamente cuando finaliza el mtodo que asign esa memoria.
Resumen
El modo no seguro de C# permite a su cdigo trabajar directamente con la memoria. Su uso puede mejorar el rendimiento porque el cdigo accede directamente 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 cdigo falle si no trabaja adecuadamente con la memoria. En general, evite el uso del modo no seguro de C#. Si necesita un poco ms de rendimiento para su cdigo o si est trabajando con cdigo heredado de C o C++ que necesita que especifique una posicin de memoria, siga con el modo seguro que se ofrece por defecto y deje que el CLR gestione los detalles de la asignacin de memoria.
419
La pequea reduccin en el rendimiento que se produce se compensa con creces por no tener que realizar la pesada tarea de gestionar la memoria de su cdigo y por conseguir la posibilidad de escribir cdigo libre de errores relacionados con la gestin de memoria.
420
20 Constructores avanzados de C#
En este captulo examinaremos algunas facetas interesantes del lenguaje C#. Tambin veremos algunos ejemplos de cdigo y aprenderemos por qu el cdigo funciona como lo hace. La comprensin de problemas de programacin como los presentados en este captulo le ayudarn a enfrentarse a sus propias dudas sobre programacin en C#. En primer lugar, observe la funcin de conversin implcita de C# y cmo 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 mtodos de operadores implcitos que definan cmo se gestionan las conversiones implcitas de un tipo u otro; pero, como ver, las cosas se vuelven un poco ms complicadas cuando se trabaja con tipos en tiempo de ejecucin y en tiempo de compilacin. A continuacin, nos adentraremos en la inicializacin de estructuras. Las estructuras, al igual que las clases, pueden contener campos y propiedades. Sin embargo, la inicializacin de estructuras con campos se realiza de forma ligeramente diferente a la inicializacin de estructuras con propiedades. En este captulo, descubrir por qu y cmo resolver este problema. En la tercera parte de este captulo, investigaremos el paso de un objeto de una clase derivada a una llamada de mtodo 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, pasar un objeto de clase derivada a un elemento que espera una
423
clase base puede parecer bastante sencillo. En este apartado estudiaremos por qu esta tcnica no es tan simple como podra parecer. Finalmente, nos adentraremos en el uso avanzado de los indizadores de clase. En la inmensa mayora de los casos, los indizadores que escriba servirn para hacer que una clase se comporte como una matriz de elementos. Por lo general, las matrices aceptan valores enteros como para especificar el elemento del ndice. En este apartado estudiaremos la tcnica de usar tipos de datos distintos de enteros para los ndices de matriz.
C# no define conversiones implcitas para todas las combinaciones existentes de tipos de datos Sin embargo, puede escribir cdigo de conversin de operadores implcitos que indique al entorno comn de ejecucin (CLR) cmo debe comportarse cuando un usuario de la clase intente hacer una conversin implcita entre la clase y otro tipo. En este apartado estudiaremos una faceta del operador de conversin implcita que trata con la conversin entre dos clases diferentes. El listado 20.1 contiene dos clases: TestClass y MainClass . La clase MainClass contiene el mtodo Main() de la aplicacin. La clase TestClass contiene una variable privada de tipo MainClass. Tambin define un mtodo de operador implcito que convierte los objetos TestClass en objetos MainClass. La implementacin de operador implcito devuelve una referencia al objeto privado MainClass del objeto TestClass.
Listado 20.1. Excepciones a las conversiones invlidas con operadores implcitos public class TestClass { private MainClass MyMainClassObject; public TestClass() { MyMainClassObject = new MainClass(); }
424
public static implicit operator MainClass(TestClass Source) { return Source.MyMainClassOb]ect; } } public class MainClass ( public static void Main() ( object MyObject; MainClass MyMainClassObject; MyObject = new TestClass(); MyMainClassObject = (MainClass)MyObject; } }
El cdigo del mtodo M a i n ( ) crea un nuevo objeto TestClass y convierte explcitamente el objeto en otro objeto de tipo MainClass. Como en TestClass se define un operador implcito, esta conversin debe tener xito. Sin embargo, si ejecuta el listado 20.1, recibir el siguiente mensaje en la consola:
Excepcin no controlada: System.InvalidCastException: La conversin especificada no es vlida. at MainClass.Main()
Por qu el CLR considera que la conversin es invlida, incluso cuando se define un operador implcito? El problema aqu es que la conversin funciona entre tipos en tiempo de compilacin, no entre tipos en tiempo de ejecucin. El mtodo M a i n ( ) crea un nuevo objeto de tipo TestClass y asigna el nuevo objeto a la variable de tipo object . A continuacin, esta variable se convierte en un tipo de clase MainClass. Como el objeto fue creado como un objeto de tipo TestClass, puede esperar que la conversin explcita convierta un objeto de tipo TestClass en un objeto de tipo MainClass. Sin embargo, C# no realiza conversiones explcitas basadas en el tipo usado en el momento de crear el objeto. En su lugar, realiza conversiones 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 compilador de C# genera cdigo que convierte un objeto de tipo object a tipo MainClass. Como no se ha definido una conversin de object a MainClass, no se produce la conversin explcita.
Inicializacin de estructuras
Como sabe, las estructuras pueden contener elementos del lenguaje que tambin se encuentran en las clases, incluyendo mtodos, campos y propiedades. Para asignar valores a los mtodos y campos de una estructura se usa una simple
425
instruccin de asignacin. 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 cdigo compilado en la estructura. Esto significa que se debe tener especial cuidado cuando se asignan valores a propiedades de estructuras recin creadas. Este captulo estudia este tema.
426
public class MainClass { public static void Main() { StructWithPublicMembers MembersStruct; StructWithProperties PropertiesStruct; MembersStruct.X = 100; MembersStruct.Y = 200; PropertiesStruct.X = 100; PropertiesStruct.Y = 200; } }
Lo ms interesante de este error es que hace referencia a la segunda variable de estructura definida en el mtodo M a i n ( ) . Sin embargo, el compilador de C# compila el cdigo que funciona con la primera variable de estructura. En otras palabras, se puede acceder a los miembros pblicos del listado 20.2, pero no a las propiedades pblicas del listado 20.2. Cul es la diferencia?
Esta respuesta tiene que ver con el hecho de que las propiedades son implementadas por el compilador de C# como funciones pblicas cuando genera el cdigo de lenguaje intermedio de Microsoft (MSIL) para la aplicacin. Puede comprobar esto revisando el MSIL generado por el compilador de C#. .NET Framework tiene una herramienta llamada ILDASM, que son las siglas de desensamblador de IL. Se puede usar esta herramienta para examinar el Lenguaje
427
intermedio (IL) y los metadatos de cualquier resultado binario compatible con el CLR de un compilador .NET. Modifique el listado 20.2 para que incluya la nueva operacin y complelo en un ejecutable llamado Listing20-2.exe . Tras generar el ejecutable, puede observar su contenido con ILDASM. Use la siguiente lnea de comando para iniciar el desensamblador IL con el ejecutable del listado 20.2:
ildasm Listing20-2.exe
La figura 20.1 muestra la ventana ILDASM abierta con el ejecutable Listing20-2.exe. El ejecutable contiene el manifiesto, que incluye la informacin de identificacin para el ejecutable y tambin contiene metadatos que describen el cdigo del ejecutable.
Listing20-2.exe - IL DASM File View Help
Listing20-2.exe MANIFEST MainClass class public auto ansi beforefieldinit ctor void() Main void() StructWithProperties class value public sequential ansi sealed beforefieldinit extends [msconlib]System Value Type PrivateX private int32 PrivateY private int32 get_X int32() get_Y int32() set_X void(int32) set_Y void(int32) X instance int32() Y instance int32() StructWithPublicMembers class value public sequential ansi sealed beforefieldinit extends [msconlib]System Value Type X public int32 Y public int32 assembly "Listing20-2" {
ILDASM muestra las clases y estructuras del ejecutable en forma de rbol. La parte del rbol StructWithPublicMembers muestra dos variables pblicas de tipo i n t 3 2 llamadas X e Y. Estas variables reflejan las dos propiedades programadas en la estructura. La parte del rbol StructWithProperties muestra las dos variables privadas; tambin muestra cuatro mtodos que no se escribieron en la estructura: int32 get_X() int32 get_Y()
428
Estos mtodos realmente implementan el acceso a la propiedad de la estructura. El mtodo g e t _ X ( ) contiene el cdigo del descriptor de acceso g e t de la propiedad X y el mtodo g e t _ Y ( ) contiene el cdigo del descriptor de acceso g e t de la propiedad Y. Del mismo modo, el mtodo s e t _ X ( ) contiene el cdigo del descriptor de acceso s e t de la propiedad X y el mtodo s e t _ Y ( ) contiene el cdigo del descriptor de acceso s e t de la propiedad Y. Esto significa que cuando el cdigo accede a una propiedad, en realidad est llamando a un mtodo que implementa la funcionalidad de la propiedad. El problema con el listado 20.2 es que la variable PropertiesStruct no est inicializada con un operador new antes de ser usada. Esto significa que la variable no est asociada a una instancia de estructura y los mtodos no pueden ser llamados en instancias que no existen. Las instrucciones de la propiedad del mtodo M a i n ( ) obligan a que se llame a los mtodos de propiedades subyacentes, pero la llamada no encuentra ninguna instancia. El compilador de C# detecta este problema y emite el mensaje de error mostrado tras el listado 20.2. La inicializacin del miembro pblico tiene xito porque las clases y las estructuras pueden inicializarse directamente, sin usar un constructor, siempre y cuando se haya dado explcitamente un valor a todas las variables de la instancia.
Clases derivadas
Cuando tratamos las clases de C# vimos cmo las clases pueden derivarse de otras clases. Las clases derivadas heredan la funcionalidad de su clase primaria o base. La relacin entre una clase derivada y una clase base recibe el nombre de una relacin "es un" en trminos orientados a objetos. Por ejemplo, todas las clase de C# derivan en ltima instancia del tipo System.Object de .NET, de modo que se puede decir que la clase "es un" System.Object. Como todas las clases derivadas heredan funcionalidad de su clase base, podemos presuponer que los objetos de una clase derivada pueden ser usados en cualquier lugar donde se pueda usar un objeto de la clase base de la clase. Sin embargo, la seguridad de tipos integrada en C# tiene preferencia, y el cdigo de este apartado explica este tema.
429
El listado 20.3 tambin contiene una clase llamada MainClass . que crea un objeto de la clase TestClass y una cadena. La cadena se usa como parmetro para la llamada al mtodo Test() del objeto TestClass.
Listado 20.3. Cmo pasar cadenas donde se esperan objetos public class TestClass { public void Test(ref object ObjectReference) { } } public class MainClass { public static void Main() { TestClass TestObject = new TestClass(); string TestString = "Hello from C#!"; TestObject.Test(ref TestString) ; } }
A primera vista, no parece lgico. Como cada tipo de dato de .NET es un object, cualquier tipo de dato debera convertirse en ltima instancia en una variable de tipo object . Esto debera incluir a los objetos s t r i n g . Por qu no se puede convertir implcitamente el objeto string en una variable de tipo object?
430
dor ref permite que la implementacin del mtodo cambie el valor de la variable y el cambio es visible para el invocador. Esto le da permiso al mtodo Test() para sobrescribir la variable ObjectReference con cualquier valor que pueda volver a convertirse explcitamente en un object. Observe la siguiente implementacin alternativa del mtodo Test():
public void Test(ref object { TestClass TestObject; ObjectReference)
En esta implementacin, se crea un objeto de clase TestClass . La variable de referencia ObjectReference se asigna al nuevo objeto y el invocador ve esta asignacin. El problema es que el listado 20.3 pasa una cadena al mtodo Test(). Con esta nueva implementacin del mtodo Test() situado en el listado 20.3, el invocador podra pasar una cadena y recuperara un objeto TestClass. El invocador podra no esperar que la variable cambiara a un tipo diferente del suministrado y podran surgir numerosos problemas si el invocador siguiera trabajando con el cdigo presuponiendo que la variable sigue siendo una cadena. C# evita este problema exigiendo que slo se usen tipos de parmetros correctos cuando se invoquen mtodos.
Tras inicializar la matriz del tablero de ajedrez, querr hacer referencia a una casilla usando la tradicional sintaxis para las casillas del tablero de ajedrez. Quizs quiera hacer referencia a la casilla B7, por ejemplo, de esta manera:
Chessboard['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 para solucionar este problema:
Listado 20.4. Cmo representar un tablero de ajedrez con un indizador using System; public class Chessboard { private int [ , ] Board; public Chessboard() { Board = new int[8,8]; } public int this [char Column, int RowIndex] { get { int ColumnIndex; switch(Column) { case 'A': case 'a': ColumnIndex break; case 'B': case 'b': ColumnIndex break; case 'C': case 'c': ColumnIndex break; case 'D': case 'd': ColumnIndex break; case 'E': case 'e': ColumnIndex break; case 'F': case 'f': ColumnIndex break; case 'G': case 'g': ColumnIndex
= 0;
= 1;
= 2;
= 3;
= 4;
= 5;
= 6;
432
break; case 'H': case 'h': ColumnIndex = 7; break; default: throw new Exception("Illegal column specifier."); } Console.WriteLine("(returning cell [{0},{l}]", ColumnIndex, RowIndex); return Board[ColumnIndex, RowIndex]; } } } public class MainClass { public static void Main() { int CellContents; Chessboard MyChessboard = new Chessboard(); CellContents = MyChessboard ['B', 7 ] ; } }
El cdigo del listado 20.4 declara una clase llamada Chessboard para representar un tablero de ajedrez. La clase incluye una matriz de enteros privada de dos dimensiones llamada Board , que puede usarse para representar qu piezas de ajedrez estn en cada casilla del tablero (para mantener la claridad del ejemplo, esta matriz realmente no se usa). La clase tambin implementa un indizador con un descriptor de acceso g e t . El indizador acepta dos parmetros: un carcter y un entero. El indizador da por hecho que el carcter especificado en el primer parmetro representa un identificador de columna y traduce el carcter como una columna de la matriz privada. El especificador de columna A lo traduce como la columna 0 en la matriz privada Board. El especificador de columna B lo traduce como la columna 1 en la matriz privada Board, y as sucesivamente. El indizador escribe un mensaje en la consola que especifica los elementos de indizador traducidos y luego devuelve el valor de los elementos de la matriz a los que hacen referencia los parmetros del indizador. El mtodo Main() del listado 20.4 crea un nuevo objeto de tipo Chessboard y usa el indizador para hacer referencia a la casilla B7:
CellContents = MyChessboard ['B', 7 ] ;
Cuando el cdigo del listado 20.4 se ejecuta, el cdigo del mtodo M a i n ( ) llama al indizador, que escribe lo siguiente en la consola:
(returning cell [1,7]
433
El indizador tradujo el primer parmetro, B, en la referencia de columna basada en enteros que el compilador de C# necesita para acceder a un elemento de la matriz privada. Este esquema permite disear clases que usan una sintaxis natural para la aplicacin (en este caso, indizadores de elementos de matriz basados en caracteres) de forma que se cumplan los requisitos de C# de usar indizadores de elementos de matriz basados en enteros.
Resumen
El mejor modo de resolver un difcil problema de programacin de C# es intentar diferentes mtodos de codificacin con el compilador. No tenga miedo de experimentar. El compilador de C# le avisar si hace algo equivocado. Tambin podra considerar el uso la herramienta ILDASM para colarse en sus ensamblados y ver cmo el compilador de C# crea cdigo ejecutable a partir de su cdigo fuente. La comprensin del cdigo que genera el compilador de C# puede ayudarle a entender por qu el cdigo funciona de la manera que lo hace. En este captulo aprendi que el cdigo descriptor de acceso de propiedades se convierte en mtodos especiales por el compilador de C#. Esto puede ser difcil de descubrir sin observar el cdigo generado por ILDASM. La herramienta ILDASM funciona con cualquier ensamblado generado por un compilador de .NET que genere MSIL. Examine con ILDASM algunos ensamblados y aplicaciones de .NET. bralos con la herramienta y vea cmo estn construidos. El anlisis de otros ensamblados y su contenido puede darle una mejor comprensin de cmo funciona el cdigo .NET y puede ayudarle a ser un mejor programador .NET.
434
Parte IV
435
21 Cmo
construir aplicaciones WindowsForms
La mayor parte del material escrito sobre .NET Framework se centra en la ayuda que reciben los programadores que escriben aplicaciones para Internet. El motor ASP.NET 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, .NET Framework no trata slo con Internet. Microsoft se dio cuenta de que, aunque muchos programadores estn escribiendo aplicaciones para Internet, otros muchos se dedican a desarrollar aplicaciones de escritorio al estilo Win32. .NET Framework no ha olvidado a estos programadores. Incluye un conjunto de clases de .NET que facilita el desarrollo de aplicaciones Windows de escritorio que usan un lenguaje compatible con .NET. Este conjunto de clases y el modelo de programacin que admite se llama WindowsForms. En este captulo estudiaremos la estructura bsica de una aplicacin WindowsForms. Ver cmo crear una forma bsica y cmo aadir controles a los formularios. Examinaremos las clases de .NET Framework que puede usar una aplicacin WindowsForms y tambin estudiaremos algunos de los atributos de ensamblado que se pueden usar para agregar la informacin de versiones y derechos de autora a sus aplicaciones.
437
Arquitectura de WindowsForms
Para programar una aplicacin WindowsForms es necesario comprender cmo se usan las clases base de WindowsForms con las aplicaciones .NET. Este captulo examina la arquitectura de la biblioteca de clases de WindowsForms. Todas las clases que se usan para construir aplicaciones de WindowsForms se incluyen en un espacio de nombres de .NET Framework llamado System. Windows.Forms . Este espacio de nombres contiene todas las clases necesarias para construir elaboradas aplicaciones de escritorio para Windows. Estas clases permiten trabajar con formularios, botones, controles de edicin, casillas de verificacin, listas y muchos otros elementos de interfaz de usuario. Todas estas clases estn a su disposicin listas para ser usadas en sus aplicaciones WindowsForms. Las aplicaciones WindowsForms usan dos clases fundamentales de .NET Framework: la clase Form , que gestiona los formularios de la aplicacin y los controles del formulario, y la clase Application , que gestiona el modo en que la aplicacin controla los mensajes Windows enviados y recibidos de los formularios de la aplicacin. Estas dos clases se incluyen en el espacio de nombre System.Windows.Forms de .NET Framework y componen la estructura bsica de una aplicacin WindowsForms.
La clase Form
El espacio de nombre System.Windows.Forms incluye una clase base llamada Form. La clase Form representa una forma o ventana de su aplicacin. Al crear una aplicacin de WindowsForms en C#, se disea una clase de ventana y se usa la clase Form como clase base para la clase de ventana. Esta clase de ventana hereda todo el comportamiento bsico de una ventana y agrega la funcionalidad necesaria para la aplicacin. Todos los comportamientos bsicos de ventana estn integrados en la clase base Form, y esos comportamientos se heredan automticamente si se deriva una clase de ventana de la clase Form.
La clase Application
Las clases Form definen el aspecto y comportamiento de las ventanas de una aplicacin, pero no se ejecutan por s mismas. WindowsForms debe ejecutarse dentro del contexto de una aplicacin. El espacio de nombres System. Windows.Forms incluye una clase llamada Application, que contiene mtodos que ayudan a gestionar las aplicaciones WindowsForms. La clase Application contiene mtodos que permiten iniciar, gestionar y detener las aplicaciones WindowsForms. WindowsForms responde a eventos iniciados por el usuario, como mover el ratn o pulsar una tecla del teclado.
438
Desde los comienzos de Windows, la arquitectura de las aplicaciones de escritorio de Windows ha sido diseada sobre el concepto de bucle de mensajes. En una aplicacin Windows estndar, la parte ms importante del cdigo se incluye en un bucle y recibe mensajes del sistema operativo Windows. Cuando el usuario mueve el ratn, pulsa una tecla o realiza alguna otra operacin sobre la que puede actuar una ventana, el sistema operativo toma nota de la accin y enva un mensaje a la ventana correspondiente informndole de la accin. Es el cdigo de la ventana el que debe utilizar convenientemente el mensaje. En la arquitectura WindowsForms, los conceptos bsicos siguen siendo los mismos. Las aplicaciones WindowsForms tienen un fragmento de cdigo que espera la llegada de mensajes de interaccin del usuario desde el sistema operativo y luego los envan a la ventana apropiada. En la arquitectura WindowsForms, la clase Application es el cdigo principal que gestiona este control de mensajes y la clase Forms es la clase que controla los mensajes que enva la clase
Application.
La clase Application est sellada. No se pueden derivar clases de ella. Sus mtodos son estticos, lo que significa que pueden ser llamados sin que haya un objeto de la clase Application disponible.
public class SimpleHelloWorld : Form { public static void Main() { Application.Run(new SimpleHelloWorld()); } public SimpleHelloWorld() { Text = "Hello, WindowsForms!"; } }
El cdigo del listado 21.1 declara una clase llamada SimpleHelloWorld . Se deriva de la clase Form de .NET Framework, que califica la clase como una
439
clase W i n d o w s . F o r m . La clase contiene un mtodo M a i n ( ) que crea un nuevo objeto de la clase SimpleHelloWorld y usa ese objeto como parmetro del mtodo en la clase Application llamado R u n ( ) . El constructor de la clase SimpleHelloWorld establece el valor de la propiedad heredada Text a Hello, WindowsForms! Esta cadena se muestra como el ttulo del formulario. La figura 21.1 muestra el aspecto del formulario cuando se ejecuta el cdigo del listado 21.1.
Hello, WindowsForms!
Como todas las aplicaciones de C#, este cdigo genera un ejecutable llamado Listing21-1.exe . Si ejecuta este archivo desde una consola, aparecer la ventana de la aplicacin. Sin embargo, ocurre algo interesante cuando se hace doble clic sobre el icono del ejecutable en el explorador de Windows. Si se inicia el ejecutable desde el Explorador de Windows aparecen dos ventanas: una ventana de consola que est vaca, y la ventana WindowsForms de la aplicacin. Este comportamiento es diferente de la mayora de las aplicaciones Windows. La mayora de las aplicaciones Windows no presentan una ventana de consola vaca
440
cuando se inicia la aplicacin. Por qu el ejecutable del listado 21.1 muestra una ventana de consola y, lo ms importante, cmo podemos eliminarla? Por defecto, el compilador de C# genera aplicaciones de consola. Estas aplicaciones necesitan una ventana de consola para que los mtodos, como Console.WriteLine() , dispongan de una consola en la que escribir sus mensajes. En tiempo de ejecucin, .NET consulta el ejecutable cuando es iniciado, y si el ejecutable est compilado como una aplicacin de consola, el cargador en tiempo de ejecucin crea una nueva ventana de consola para la aplicacin. Si se compila el listado 21.1 mediante el compilador de C# por defecto, se generara una aplicacin .NET para consola. Esto explica la ventana de consola vaca que aparece cuando se ejecuta el ejecutable compilado. WindowsForms trabaja con ventanas, no con consolas, y las aplicaciones WindowsForms no necesitan la ventana de consola. El compilador de C# admite un argumento en lnea de comandos llamado target que se usa para especificar el tipo de la aplicacin que se quiere compilar. Puede usar este argumento para indicarle al compilador de C# que quiere compilar una aplicacin Windows que no necesita una consola. La siguiente lnea de comando:
csc /target:winexe Listing21-1.cs
ordena al compilador de C# que compile el archivo Listing21-1.cs y use sus contenidos para crear un ejecutable Windows que no necesita una consola. Si usa esta lnea de comandos para compilar el cdigo del listado 21.1 e inicia el ejecutable desde el explorador de Windows, aparecer el formulario de la aplicacin, sin que aparezca tambin una ventana de consola vaca. NOTA: El compilador de C# acepta /t como abreviatura de /target. La anterior lnea de comandos puede acortarse a csc /t:winexe Listing21-1.es .
441
finales pueden examinar la informacin de la versin y de derechos de autora de las aplicaciones .NET WindowsForms exactamente del mismo modo que pueden examinar dicha informacin en las aplicaciones Win32 estndar. Las clases que implementan estos atributos estn en un espacio de nombres .NET llamado System.Reflection. Cuando se usan estos atributos, hay que hacer referencia a este espacio de nombres con la palabra clave using o anteponer el nombre del espacio de nombres a los nombres de los ensamblados. Parte de la informacin especificada en estos atributos se escribe en el manifiesto del ensamblado que, junto con otros trozos de cdigo, se almacenan como recursos de informacin de versin incrustados en el ejecutable del ensamblado. Puede observar el manifiesto del ensamblado mediante la herramienta ILDASM, y puede ver el recurso de informacin de versin del ejecutable haciendo clic con el botn derecho del ratn en el archivo en el Explorador de Windows y seleccionando Propiedades>Versin. TRUCO: Puede agregar estos atributos a cualquier proyecto de cdigo .NET. La informacin de la versin siempre est disponible en el producto compilado del cdigo. En este apartado estudiaremos los siguientes atributos: AssemblyTitle, que asigna un ttulo al ensamblado. AssemblyDescription, que asigna una descripcin al ensamblado. AssemblyConfiguration, que describe las opciones usadas para construir el ensamblado. AssemblyCompany, que asigna un nombre de compaa al ensamblado. AssemblyProduct, que asigna informacin del producto al ensamblado. AssemblyCopyright, que asigna informacin de derechos de autora al ensamblado. AssemblyTrademark, que asigna informacin de marca al ensamblado. AssemblyCulture, que asigna informacin local al ensamblado. AssemblyVersion, que asigna un nmero de versin al ensamblado.
AssemblyTitle
El atributo AssemblyTitle permite asignar un ttulo al ensamblado. El atributo toma un parmetro de cadena en su constructor que especifica el ttulo, como muestra el siguiente ejemplo:
[assembly: AssemblyTitle("Listing 21-2")]
442
El ttulo del ensamblado no se escribe en su manifiesto, pero est disponible en el campo Description del bloque de informacin de versin del archivo compilado.
AssemblyDescription
El atributo AssemblyDescription permite proporcionar una descripcin del ensamblado. El atributo toma un parmetro de cadena en su constructor que especifica la descripcin, como muestra el siguiente ejemplo:
[assembly: AssemblyDescription("This executable was produced by compiling the code in Listing 21-2.")]
La descripcin del ensamblado se escribe en el manifiesto del ensamblado; tambin est disponible en el campo Comment del bloque de informacin de versin del archivo compilado.
AssemblyConfiguration
El atributo AssemblyConfiguration permite especificar informacin de configuracin de compilacin del ensamblado. Por ejemplo, la informacin de configuracin de ensamblado puede especificar si el ensamblado se compil con configuracin para su distribucin o depuracin. El atributo toma un parmetro de cadena en su constructor que especifica la informacin de configuracin:
[assembly: AssemblyConfiguration("retail")]
La descripcin del ensamblado se escribe en el manifiesto del ensamblado, pero no est disponible en el bloque de informacin de versin del archivo compilado.
AssemblyCompany
El atributo AssemblyCompany permite especificar un nombre de compaa para asociarlo al ensamblado. El atributo toma un parmetro de cadena en su constructor que especifica el nombre de la compaa:
[assembly: AssemblyCompany("John Wiley, Inc.")]
El nombre de la compaa del ensamblado se escribe en el manifiesto del ensamblado; tambin est disponible en el campo Company Name del bloque de informacin de versin del archivo compilado.
AssemblyProduct
El atributo AssemblyProduct permite especificar un nombre de producto para asociarlo al ensamblado. El atributo toma un parmetro de cadena en su
443
constructor que especifica el nombre de producto de la compaa, como muestra el siguiente ejemplo:
[assembly: AssemblyProduct("C# Bible")]
El nombre del producto del ensamblado se escribe en el manifiesto del ensamblado; tambin est disponible en el campo Product Name del bloque de informacin de versin del archivo compilado.
AssemblyCopyright
El atributo AssemblyCopyright permite especificar informacin de derechos de autora para el ensamblado. El atributo toma un parmetro de cadena en su constructor que especifica la informacin de derechos de autora, como muestra el siguiente ejemplo:
[assembly: AssemblyCopyright("(c) 2002 John Wiley, Inc.")]
El nombre del producto del ensamblado se escribe en el manifiesto del ensamblado; tambin est disponible en el campo Copyright del bloque de informacin de versin del archivo compilado.
AssemblyTrademark
El atributo AssemblyTrademark permite especificar informacin de marca registrada para el ensamblado. El atributo toma un parmetro de cadena en su constructor que especifica la informacin de marca registrada, como muestra el siguiente ejemplo:
[assembly: AssemblyTrademark("Windows is a trademark of Microsoft Corporation.")]
El nombre del producto del ensamblado se escribe en el manifiesto del ensamblado; tambin est disponible en el campo Legal del bloque de informacin de versin del archivo compilado.
AssemblyCulture
El atributo AssemblyCulture permite especificar informacin de referencia cultural para el ensamblado. La informacin de referencia cultural especifica la informacin de lenguaje y pas que el ensamblado usa para hacer su trabajo. El atributo toma un parmetro de cadena en su constructor que especifica la informacin de referencia cultural, como muestra el siguiente ejemplo:
[assembly: AssemblyCulture("us-en")]
444
Las cadenas de referencia cultural son definidas mediante un estndar de Internet llamado RFC1766. El estndar se titula Tags for the Identification of Languages y est disponible en Internet en www.ietf.org/rfc/ rfc1766.txt . El nombre del producto del ensamblado se escribe en el manifiesto del ensamblado; tambin est disponible en el campo Legal Trademarks del bloque de informacin de versin del archivo compilado. NOTA: La informacin de referencia cultural slo se puede aadir a bibliotecas y mdulos. No puede aadirse a ejecutables, porque los ejecutables no pueden adaptarse localmente. Si se intenta aadir el atributo Assembly Culture al cdigo 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 ' System.Reflection.AssemblyCultureAttribute''Los archivos ejecutables no se pueden adaptar y no deben tener referencia cultural'
La descripcin del ensamblado se escribe en el manifiesto del ensamblado, pero no est disponible en el bloque de informacin de versin del archivo compilado.
AssemblyVersion
El atributo AssemblyVersion permite asignar un nmero de versin al ensamblado. Los nmeros de versin de .NET constan de cuatro partes: Un nmero de versin principal Un nmero de versin secundaria Un nmero de revisin Un nmero de compilacin
Cada una de estas partes est separada por un punto. El atributo toma un parmetro de cadena en su constructor que especifica el nmero de versin, como muestra el siguiente ejemplo:
[assembly: AssemblyVersion("1.0.0.0")]
Siempre se debe especificar un nmero de versin. Si se especifican los nmeros de versin principal y secundario, puede dejar que el compilador de C# genere automticamente los otros nmeros al compilar el cdigo. Esto puede ser til si se quiere que cada compilacin del cdigo tenga un nmero de versin nico. Si usa el carcter asterisco para un nmero de compilacin, el compilador de C# asigna
445
uno automticamente. El nmero de compilacin generado es igual al nmero de das desde el 1 de enero del 2000, como muestra el siguiente ejemplo:
[assembly: AssemblyVersion("1.0.0.*")]
Este ejemplo asigna al ensamblado una versin principal igual a 1, una versin secundaria igual a 0, un nmero de revisin igual a 0, y un nmero de compilacin asignado automticamente por el compilador de C#. Si usa el carcter asterisco para un nmero de revisin, el compilador de C# asigna automticamente un nmero de revisin y un nmero de compilacin. El nmero de revisin generado es igual al nmero de das desde el 1 de enero del 2000, como muestra el siguiente ejemplo:
[assembly: AssemblyVersion("1.0.*")]
Este ejemplo asigna al ensamblado una versin principal igual a 1, una versin secundaria igual a 0, un nmero de revisin igual a 0, y un nmero de revision asignado automticamente por el compilador de C#. El nombre del producto del ensamblado se escribe en el manifiesto del ensamblado; tambin est disponible en los campos Assembly Version , Product Version y File Version del bloque de informacin de versin del archivo compilado. El listado 21.2 aade atributos de versin de ensamblado al cdigo del listado 21.1.
Listado 21.2. Sencilla aplicacin de formulario Windows con informacin de versin using using System.Reflection; System.Windows.Forms;
[assembly: AssemblyTitle("Listing 21-2")] [assembly: AssemblyDescription("This executable was produced by compiling the code in Listing 21-2.")] [assembly: AssemblyConfiguration("Retail")] [assembly: AssemblyCompany("John Wiley, Inc.")] [assembly: AssemblyProduct("C# Bible")] [assembly: AssemblyCopyright("(c) 2002 John Wiley, Inc.")] [assembly: AssemblyVersion("1.0.*")] public class SimpleHelloWorld : Form ( public static void Main() { Application.Run(new SimpleHelloWorld()); } public SimpleHelloWorld() { Text = "Hello, Windows Forms!"; } }
446
Eventos Application
La clase Application admite cuatro eventos que pueden usarse en las aplicaciones WindowsForms: El evento ApplicationExit se desencadena cuando la aplicacin est a punto de cerrarse. A este evento se le pueden asignar los delegados de tipo EventHandler . El delegado EventHandler est definido en el espacio de nombre System de .NET. El delegado toma dos parmetros: un objeto que hace referencia al objeto que enva el evento y un objeto EventArgs que especifica los argumentos del evento. No devuelve ningn valor. El evento Idle se desencadena cuando la aplicacin termina de enviar mensajes desde el sistema operativo y est a punto de entrar en estado de inactividad. La aplicacin abandona el estado de inactividad cuando considera que hay ms mensajes que procesar. A este evento se le pueden asignar delegados de tipo EventHandler. El delegado EventHandler est definido en el espacio de nombre .NET System. El delegado toma dos parmetros: un objeto que hace referencia al objeto que enva el evento y un objeto EventArgs que especifica los argumentos del evento. No devuelve ningn valor.
El evento ThreadException se desencadena cuando se inicia una excepcin de subproceso no capturada. A este evento se le pueden asignar delegados de tipo ThreadExceptionEventHandler . El delegado ThreadExecptionEventHandler se define en el espacio de nombre .NET System.Threading . El delegado toma dos parmetros: un objeto que hace referencia al objeto que enva el evento y un objeto ThreadingExceptionEventArgs que especifica los argumentos del evento. No devuelve ningn valor. El evento ThreadExit se desencadena cuando un subproceso est a punto de cerrarse. Cuando el proceso principal de una aplicacin est a punto de cerrarse, antes se desencadena este evento, seguido de un evento ApplicationExit . A este evento se le pueden asignar delegados de tipo EventHandler. El delegado EventHandler est definido en el espacio de nombre System de .NET. El delegado toma dos parmetros: un objeto que hace referencia al objeto que enva el evento y un objeto
447
EventArgs que especifica los argumentos del evento. No devuelve ningn valor.
448
Console.WriteLine("an exception thrown from an application thread was caught!"); } public void OnThreadExit(object sender, EventArgs e) { Console.WriteLine("The application's main thread is shutting down."); } } public class MainClass { public static void Main() { HelloWorldForm FormObject = new HelloWorldForm(); ApplicationEventHandlerClass AppEvents = new ApplicationEventHandlerClass(); Application.ApplicationExit += new EventHandler(AppEvents.OnApplicationExit); Application.Idle += new EventHandler(AppEvents.OnIdle); Application.ThreadException += new ThreadExceptionEventHandler(AppEvents.OnThreadException); Application.ThreadExit += new EventHandler(AppEvents.OnThreadExit); Application.Run(FormObject); } }
La nueva clase del listado 21.3 recibe el nombre de A p p l i c a t i o n EventHandlerClass y contiene mtodos que controlan los eventos desencadenados desde el objeto Application . El mtodo M a i n ( ) crea un objeto de la clase ApplicationEventHandlerClass y agrega su cdigo a la lista de controladores de eventos de la clase Application. Los controladores de evento de la clase ApplicationEventHandlerClass escriben en la consola. Esto es perfectamente vlido, incluso en una aplicacin WindowsForms . Si compila el cdigo del listado 21.3 usando el destino ejecutable de consola (csc /target:exe Listing21-3.cs ), los mensajes del controlador de eventos se escriben en la ventana de la consola que se us para iniciar la aplicacin. Si compila el cdigo del listado 21.3 usando el destino de ejecutable de Windows ( csc /target:winexe Listing21-3.cs ), ninguna consola estar asociada al proceso y no se mostrarn mensajes en la consola.
Propiedades Application
La clase Application es compatible con varias propiedades que pueden usarse en las aplicaciones de C#. Los siguientes apartados describen cmo usar cada una de estas propiedades.
449
AllowQuit
La propiedad booleana AllowQuit especifica si el cdigo puede o no terminar la aplicacin mediante programacin. La propiedad devuelve T r u e si el cdigo puede ordenar a la aplicacin que termine, y F a l s e en caso contrario. Esta propiedad es de slo lectura y no se le puede asignar un valor. Por lo general, los usuarios pueden terminar las aplicaciones cerrando el formulario principal de la aplicacin. Algunas aplicaciones se incluyen en contenedores, como clientes Web, y no pueden cerrarse mediante programacin. Slo se cierran cuando su contenedor se cierra. Si la propiedad AllowQuit devuelve t r u e , se puede llamar a un mtodo de Application llamado Exit() para terminar la aplicacin mediante programacin. Los mtodos del objeto Application se estudiarn en el apartado titulado "Mtodos 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 aplicacin puede usar para almacenar datos de registro que deberan estar disponibles para todos los usuarios de la aplicacin. Esta propiedad es de slo lectura y no se le puede asignar un valor. La clase RegistryKey es parte de .NET Framework y est disponible en un espacio de nombres llamado Microsoft.Win32 . Representa una clave especfica del registro y contiene mtodos que permiten crear subclaves, 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 aplicacin puede usar para almacenar datos basados en archivos que deberan estar disponibles para todos los usuarios de la aplicacin. Esta propiedad es de slo lectura y no se le puede asignar un valor. La ruta de datos de la aplicacin se almacena dentro de la ruta de la carpeta de documentos de Windows para todos los usuarios, que suele encontrarse en C:\Documents and Settings\All Users\Datos de programa . La ruta real de la aplicacin apunta a una carpeta dentro de esta ruta de documentos "all users" que tiene la forma C o m p a n y N a m e \ P r o d u c t N a m e \ ProductVersion . Los nombres de carpeta CompanyName, ProductName y ProductVersion se basan en los valores de las propiedades de la aplicacin del mismo nombre. Si no se asignan valores a estas propiedades, la clase Application proporciona unos valores por defecto vlidos. Por ejemplo, el cdigo del listado 21.1 tiene una ruta de datos comunes de la aplicacin C:\Documents and Settings\All Users\Datos de programa\SimpleHelloWorld\SimpleHelloWorld\V0.0. Si el cdigo del listado 21.1 va a asignar valores a las propiedades CompanyName,
450
ProductName o ProductVersion de la clase Application, los nombres de carpetas en la ruta de datos comunes de la aplicacin pueden cambiar para reflejar los valores de esas propiedades. La ruta de la carpeta de la versin del producto slo usa los nmeros de versin principal y secundario especificados en la aplicacin, independientemente del nmero de valores que asigne en el atributo de la aplicacin [AssemblyV e r s i o n ] . Si la aplicacin usa un atributo [AssemblyVersion] con un valor, por ejemplo, 1.2.3.4 , la parte del nmero de versin de la ruta de datos comunes de la aplicacin ser 1.2. La letra V siempre se antepone al nmero de versin principal de la aplicacin en la parte del nmero de versin de la ruta de datos.
CompanyName
La propiedad de cadena CompanyName devuelve el nombre de la compaa asociado a la aplicacin. Esta propiedad es de slo lectura y no se le puede asignar un valor. Por defecto, este valor se asigna al nombre de la clase que contiene el mtodo M a i n ( ) de la aplicacin. Si la aplicacin especifica un nombre de compaa con el atributo [AssemblyCompany] , el valor de ese atributo se usa como el valor de la propiedad CompanyName de la aplicacin.
CurrentCulture
La propiedad CurrentCulture permite trabajar con la informacin de referencia cultural de la aplicacin. Esta propiedad se puede leer y escribir. La propiedad tiene una clase de tipo CultureInfo , que es una clase definida por .NET Framework y que se encuentra en el espacio de nombres System. Globalization . La clase CultureInfo contiene mtodos y propiedades que permiten al cdigo trabajar con datos especficos del entorno cultural en el que se ejecuta la aplicacin. Los objetos CultureInfo ofrecen informacin tal como el formato para mostrar la fecha y la hora, la configuracin de la hora y el formato numrico.
CurrentInputLanguage
La propiedad CurrentInputLanguage permite trabajar con el idioma actual de la aplicacin. La propiedad tiene una clase de tipo InputLanguage , que es una clase definida por .NET Framework y que se encuentra en el espacio de nombres System.Windows.Forms . La clase InputLanguage contiene mtodos y propiedades que permiten al cdigo trabajar con el conocimiento que tenga la aplicacin de las teclas del teclado y cmo se relacionan con los caracteres que pueden introducirse en la aplicacin. Las diferentes versiones especficas de cada lenguaje de Windows establecen equivalencias entre las teclas del teclado y los caracteres especficos de los diferentes lenguajes, y la clase CurrentInputLanguage especifica las caractersticas de esta asignacin.
451
ExecutablePath
La propiedad de cadena ExecutablePath devuelve la ruta del ejecutable de la aplicacin. Esta propiedad es de slo lectura y no se le puede asignar un valor.
LocalUserAppDataPath
La propiedad de cadena LocalUserAppDataPath hace referencia a una ruta en el sistema de archivos que la aplicacin puede usar para almacenar datos basados en archivos que deberan estar disponibles para el usuario conectado en ese momento al equipo. Esta propiedad es de slo lectura y no se le puede asignar un valor. Es usada por los usuarios locales con perfiles de sistema operativo del equipo local. Los usuarios con perfiles mviles usados a travs de un sistema de redes tienen una propiedad distinta, llamada UserAppDataPath , para especificar dnde deben almacenarse los datos de la aplicacin. Al igual que la propiedad CommonAppDataPath , la ruta de datos de usuario local seala a una carpeta incluida dentro de la carpeta de documentos de usuario conectada con una estructura de carpetas que tiene la forma CompanyName\ ProductName\ ProductVersion . Los nombres de carpeta CompanyName , CompanyName y ProductVersion se basan en los valores de la aplicacin del mismo nombre. Si no se asigna un valor a estas propiedades, la clase Application proporciona unos valores vlidos por defecto. Si el cdigo asigna un valor a las propiedades CompanyName, CompanyName o ProductVersion de la clase Application, los nombres de carpetas en la ruta de datos de la aplicacin de usuario local cambian para reflejar los valores de esas propiedades. Al igual que la ruta de datos comunes de la aplicacin, la ruta de carpeta de versin de producto slo usa los nmeros de versin principal y secundario especificados en la aplicacin, independientemente del nmero de valores especificados en el atributo de la aplicacin [AssemblyVersion] . Si la aplicacin usa un atributo [AssemblyVersion] con un valor, por ejemplo, 1.2.3.4, la parte correspondiente al nmero de versin de la ruta de datos de usuario local de la aplicacin ser 1.2. La letra V siempre se antepone al nmero de versin principal de la aplicacin en la parte del nmero de versin de la ruta de datos.
MessageLoop
La propiedad booleana MessageLoop devuelve T r u e si existe un bucle de mensajes para la aplicacin y F a l s e en caso contrario. Esta propiedad es de slo lectura y no se le puede asignar un valor. Como todas las aplicaciones WindowsForms necesitan un bucle de mensajes para que los mensajes de Windows puedan ser enviados al formulario correcto, las aplicaciones WindowsForms devuelven True para esta propiedad.
452
ProductName
La propiedad de cadena ProductName devuelve el nombre del producto asociado a la aplicacin. Esta propiedad es de slo lectura y no se le puede asignar un valor. Por defecto, a este valor se le asigna el nombre de la clase que contiene el mtodo M a i n ( ) de la aplicacin. Si la aplicacin especifica un nombre de producto con el atributo [AssemblyProduct] , el valor de ese atributo se usa como el valor de la propiedad de la aplicacin ProductName.
ProductVersion
La propiedad de cadena ProductVersion devuelve el nmero de versin asociado a la aplicacin. Esta propiedad es de slo lectura y no se le puede asignar un valor. Por defecto, este valor es 0.0.0.0 . Si la aplicacin especifica un nmero de versin con el atributo [AssemblyVersion] , el valor de ese atributo se usa como el valor de la propiedad de la aplicacin ProductVersion.
SafeTopLevelCaptionFormat
La propiedad de cadena SafeTopLevelCaptionFormat hace referencia a la cadena de formato que en tiempo de ejecucin se aplica a los ttulos de la ventana de nivel superior cuando las aplicaciones se ejecutan desde un contexto no seguro. La seguridad es una parte integral de .NET Framework y el entorno comn de ejecucin (CLR). El CLR respeta la configuracin de las diferentes zonas de seguridad en Internet Explorer (Internet, Intranet local, Sitios de confianza y Sitios restringidos) y restringe los servicios en tiempo de ejecucin para las aplicaciones que se ejecutan en zonas no fiables. Las aplicaciones WindowsForms que se ejecutan desde zonas no fiables, como la zona Internet, tienen una etiqueta de aviso que describe la aplicacin como procedente de una localizacin no fiable. El texto de esta etiqueta de aviso est basado en la plantilla de formato de cadena almacenada en la propiedad SafeTopLevelCaptionFormat.
StartupPath
La propiedad de cadena StartupPath devuelve la ruta al archivo ejecutable que inici la aplicacin. Esta propiedad slo devuelve la ruta. No incluye el nombre del archivo ejecutable. Esta propiedad es de slo lectura y no se le puede asignar un valor.
UserAppDataPath
La propiedad de cadena UserAppDataPath hace referencia a una ruta en el sistema de archivos que puede usar la aplicacin para almacenar datos basados en archivos que deberan estar disponibles para el usuario de red que est conectado en ese momento al equipo local. Esta propiedad es de slo lectura y no se le puede asignar un valor. Es usada 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 especificar dnde deben almacenarse los datos de la aplicacin. Al igual que la propiedad CommonAppDataPath , la ruta de datos de usuario local seala a una carpeta incluida dentro de la carpeta de documentos de usuario conectado con una estructura de carpetas que tiene la forma CompanyName\ ProductName\ ProductVersion . Los nombres de carpeta CompanyName, ProductName y ProductVersion se basan en los valores de la aplicacin del mismo nombre. Si no se asigna un valor a estas propiedades, la clase Application proporciona unos valores vlidos por defecto. Si el cdigo asigna un valor a las propiedades CompanyName, ProductName o ProductVersion de la clase Application, los nombres de carpetas en la ruta de datos de la aplicacin de usuario local cambian para reflejar los valores de esas propiedades.
UserAppDataRegistry
El mtodo 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 aplicacin puede usar para almacenar datos de registro que slo deberan estar disponibles para el usuario actual de la aplicacin. Esta propiedad es de slo lectura y no se le puede asignar un valor.
Mtodos Application
La clase Application admite ocho mtodos que pueden ser llamados desde las aplicaciones de C#. Estos mtodos se describen en las siguientes secciones.
AddMessageFilter
El mtodo AddMessageFilter() aade un filtro de mensajes a la aplicacin para controlar los mensajes de Windows mientras los mensajes son enviados a sus destinos. El filtro de mensajes que se instala en la aplicacin recibe los mensajes de Windows antes de que se enven al formulario. Un filtro de mensajes instalado por AddMessageFilter() puede controlar un mensaje que se le enve y puede decidir si el mensaje debe enviarse al formulario. El listado 21.4 muestra cmo se puede usar el filtro de mensajes en una aplicacin WindowsForms. El controlador de mensajes busca mensajes que anunciar cuando se hace clic con el botn izquierdo en el formulario.
Listado 21.4. Cmo instalar un filtro de mensajes using System; using System.Windows.Forms;
454
public class BlockLeftMouseButtonMessageFilter : { const int WM_LBUTTONDOWN = 0x201; const int WM_LBUTTONUP = 0x202;
IMessageFilter
public bool PreFilterMessage(ref Message m) { if(m.Msg == WM_LBUTTONDOWN) { Console.WriteLine("The left mouse button is down."); return true; } if(m.Msg == WM_LBUTTONUP) { Console.WriteLine("The left mouse button is u p . " ) ; return true; } return false; } } public class MainForm : Form { public static void Main() { MainForm MyForm = new MainForm(); BlockLeftMouseButtonMessageFilter MsgFilter = new BlockLeftMouseButtonMessageFilter(); Application.AddMessageFilter(MsgFilter); Application.Run(MyForm); } public MainForm() { Text = "Message Filter Test"; } }
El mtodo AddMessageFilter() recibe un argumento: una implementacin de una interfaz llamada IMessageFilter . La interfaz IMessageFilter es definida por .NET Framework y se incluye en el espacio de nombres System.Windows.Forms. IMessageFilter declara un mtodo:
public bool PreFilterMessage(ref Message m ) ;
El mtodo PreFilterMessage() toma como entrada una referencia a una instancia de una estructura llamada Message . La estructura Message describe un mensaje de Windows y contiene las siguientes propiedades: HWnd, que describe el controlador de la ventana que debe recibir el mensaje.
455
LParam , que describe un fragmento de nmero entero que se enva con el mensaje. Msg , que describe el nmero entero ID asociado al mensaje. Cada mensaje de Windows tiene su propio ID entero. Result , que describe el valor que debe devolverse a Windows en respuesta al control del mensaje. WParam , que describe otro fragmento de nmero entero que se enva con el mensaje.
El listado 21.4 comienza declarando una clase llamada 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 . Esta clase implementa la interfaz IMessageFilter . La implementacin del mtodo PreFilterMessage() de la clase comprueba el ID del mensaje pasado al mtodo. Comprueba si el ID indica que se ha pulsado el botn izquierdo del ratn. En caso afirmativo, se escribe en la consola el mensaje The left mouse button is down . A continuacin comprueba si el ID indica que se ha soltado el botn izquierdo del ratn. En caso afirmativo, se escribe en la consola el mensaje The left mouse
button is up.
NOTA: La clase BlockLeftMouseButtonMessageFilter declara constantes para dar nombres a los mensajes de Windows que busca el filtro. Los nombres empiezan con WM (por Mensaje de Windows) y coinciden con los nombres definidos por Microsoft. Todos los mensajes de Windows disponibles y sus valores numricos estn explicados en la documentacin del SDK de Microsoft. Las implementaciones del mtodo PreFilterMessage() deben devolver un valor booleano que describe si el mensaje debe ser enviado al formulario tras pasar por el filtro o no. Si el filtro considera que el mensaje no debe ser enviado, entonces devuelve T r u e . Si el filtro considera que el mensaje debe ser enviado, entonces devuelve el valor F a l s e . El filtro de mensajes del listado 21.4 devuelve T r u e para los dos mensajes que controla y F a l s e para todos los dems mensajes. El mtodo M a i n ( ) en el listado 21.4 crea un nuevo objeto de la clase BlockLeftMouseButtonMessageFilter y lo usa en una llamada al mtodo AddMessageFilter() del objeto Application . Tras instalar el filtro de mensajes, se crea y ejecuta el formulario principal. Puede ver el filtro de mensajes en accin compilando el listado 21.4. Al ejecutar el cdigo, aparecer el formulario principal de la aplicacin. Cuando aparezca el formulario, mueva el ratn para que el cursor est dentro del formulario y haga clic con el botn izquierdo del ratn. Se escribir en la consola un mensaje indicando que ha pulsado un botn del ratn.
456
DoEvents
El mtodo DoEvents() procesa todos los mensajes que se encuentren en la cola de mensajes de la aplicacin de Windows. El mtodo no recibe argumentos ni devuelve ningn valor. Este mtodo se invoca cuando se quiere estar seguro de que los mensajes en espera de Windows se envan al formulario mientras se realizan otras tareas. Suponga, por ejemplo, que crea un formulario que realiza un clculo largo. Si se mueve otra ventana delante del formulario mientras se realiza el clculo, la ventana enva a la aplicacin un mensaje de Windows que indica que el formulario debe ser dibujado de nuevo. Sin embargo, como el cdigo est realizando un enorme clculo, el mensaje de nuevo dibujo quedar en la cola de mensajes de la aplicacin; despus de todo, el cdigo est ocupado realizando clculos y no procesando mensajes. Se pueden hacer llamadas al mtodo DoEvents() en ciertos puntos del proceso para asegurarnos de que los mensajes de espera de Windows se procesan mientras el cdigo est ocupado realizando otro trabajo.
Exit
El mtodo Exit() obliga a la aplicacin a terminar. El mtodo informa a la cola de mensajes de la aplicacin que debe finalizar y cierra los formularios cuando se procesa el ltimo mensaje de la cola de mensajes de Windows. Por lo general, el cdigo no necesita invocar al mtodo Exit(). Los formulanos de Windows incluyen por defecto un cuadro de cierre en la esquina superior derecha del formulario y al hacer clic en ese cuadro se enva un mensaje de cierre a la cola de mensajes de Windows. Sin embargo, puede ser til llamar a Exit() si el formulario incluye un control, como un botn o un elemento de men que debe finalizar la aplicacin al ser seleccionado.
ExitThread
El mtodo ExitThread() sale del bucle de mensajes y cierra todos los formularios en el subproceso en curso. El mtodo no recibe argumentos ni devuelve ningn valor. Si la aplicacin WindowsForms contiene un solo subproceso (como es habitual), entonces la accin de llamar a ExitThread() es igual que llamar a Exit(). Sin embargo, si la aplicacin usa varios subprocesos, entonces los dos mtodos se comportan de forma diferente. El mtodo ExitThread() cierra un subproceso pero permite que los otros subprocesos sigan ejecutndose. Sin embargo, el mtodo Exit() cierra todos los subprocesos a la vez. Como todos los procesos de Windows, las aplicaciones WindowsForms siguen ejecutndose hasta que finaliza el ltimo subproceso.
OleRequired
El mtodo OleRequired() inicializa OLE en el subproceso en curso de la aplicacin. Si la aplicacin va a trabajar con tecnologa COM, como COM, DCOM,
457
ActiveX u OLE, se debe llamar a este mtodo de la aplicacin antes de usar COM. El mtodo no recibe argumentos, pero devuelve un valor desde una enumeracin llamada ApartmentState que describe el tipo de apartado que introdujo el subproceso. La enumeracin ApartmentState est definida en un espacio de nombres .NET Framework llamado System.Threading y puede tener uno de los siguientes valores: STA, se devuelve cuando el CLR decide inicializar COM para el subproceso entrando en un apartado de un nico subproceso. MTA, se devuelve cuando el CLR decide inicializar COM para el subproceso entrando en un apartado de multiproceso.
OnThreadException
El mtodo OnThreadException() desencadena un evento ThreadException . El evento puede ser capturado por un controlador de eventos OnThreadException() instalado en el objeto Application. El listado 21.5 muestra cmo puede usarse una excepcin de subproceso en una aplicacin WindowsForms .
Listado 21.5. Cmo trabajar con excepciones de subprocesos using System; using System.Threading; using System.Windows.Forms; public class BlockLeftMouseButtonMessageFilter { const int WM_LBUTTONDOWN = 0x201; public bool PreFilterMessage(ref Message m) { if(m.Msg == WM_LBUTTONDOWN) { Exception LeftButtonDownException; LeftButtonDownException = new Exception("The left mouse button was pressed.") ; Application.OnThreadException(LeftButtonDownException); return true; } return false; } } public class ApplicationEventHandlerClass { public void OnThreadException(object sender, : IMessageFilter
458
ThreadExceptionEventArgs e) { Exception LeftButtonDownException; LeftButtonDownException = e.Exception; Console.WriteLine(LeftButtonDownException.Message); } } public class MainForm : Form { public static void Main() { ApplicationEventHandlerClass AppEvents = new ApplicationEventHandlerClass(); MainForm MyForm = new MainForm(); BlockLeftMouseButtonMessageFilter MsgFilter = new BlockLeftMouseButtonMessageFilter(); Application.AddMessageFilter(MsgFilter); Application.ThreadException += new ThreadExceptionEventHandler(AppEvents.OnThreadException); Application.Run(MyForm); } public MainForm() { Text = "Application Exception Test"; } }
El listado 21.5 es parecido al listado 21.4 ya que incluye un controlador de mensajes que busca mensajes que informan cuando se pulsa el botn izquierdo del ratn en el formulario de la aplicacin. La diferencia es que en el listado 21.5 se inicia una excepcin al recibir el mensaje left mouse button down . El controlador de mensajes crea un nuevo objeto E x c e p t i o n y lo inicia usando el mtodo O n T h r e a d Exception() del objeto Application . El cdigo del listado 21.5 tambin incluye un controlador de eventos de la aplicacin, que se implementa en una clase llamada ApplicationEventHandlerClass . Esta clase controla el evento OnThreadException() y el mtodo principal de la aplicacin instala el controlador de eventos usando la propiedad ThreadException del objeto
Application.
El controlador de excepciones del subproceso instalado en la clase ApplicationEventHandlerClass extrae la excepcin del objeto ThreadExceptionEventArgs del controlador y escribe el mensaje de la excepcin en la consola. Cuando se ejecuta el cdigo del listado 21.5, aparece el formulario principal de la aplicacin. Cuando aparezca el formulario, mueva el ratn hacia su interior y haga clic con el botn izquierdo del ratn. El controlador
459
de mensajes iniciar una excepcin y el controlador de excepciones de la aplicacin escribir mensajes de excepcin en la consola de la aplicacin.
RemoveMessageFilter
El mtodo RemoveMessageFilter() elimina un filtro de mensajes instalado por el mtodo AddMessageFilter() . Elimina el filtro de mensajes del generador de mensajes de la aplicacin. El mtodo RemoveMessageFilter() recibe un argumento: una implementacin de una interfaz llamada IMessageFilter . Este argumento debe hacer referencia a una clase que implemente IMessageFilter y que ya ha sido usada en una llamada a AddMessageFilter(). El listado 21.6 muestra cmo funciona este mtodo.
Listado 21.6. Cmo eliminar un filtro de mensajes instalado using System; using System.Windows.Forms; public class BlockLeftMouseButtonMessageFilter { const int WM_LBUTTONDOWN = 0x201; : IMessageFilter
public bool PreFilterMessage(ref Message m) { if(m.Msg == WM_LBUTTONDOWN) { Console.WriteLine("The left mouse button is down."); Application.RemoveMessageFilter(this); return true; } return false; } } public class MainForm : Form { public static void Main() { MainForm MyForm = new MainForm() ; BlockLeftMouseButtonMessageFilter MsgFilter = new BlockLeftMouseButtonMessageFilter(); Application.AddMessageFilter(MsgFilter); Application.Run(MyForm); } public MainForm() { Text = "Message Filter Removal Test"; } }
460
El cdigo del listado 21.6 instala un filtro de mensajes que busca el mensaje left mouse button down , igual que haca el listado 21.4. La diferencia es que la implementacin 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 cdigo del listado 21.6, slo se escribe un mensaje en la consola, independientemente del nmero de veces que pulse el botn izquierdo del ratn con el puntero sobre el formulario. Esto es debido a que el filtro de mensajes es eliminado de la aplicacin cuando se recibe el primer mensaje y, como se elimina el filtro de mensajes, no se pueden detectar nuevos mensajes. Todava se envan mensajes al formulario, pero el cdigo del listado 21.6 elimina el objeto que detecta por primera vez los mensajes despus de que se haya eliminado el objeto de la lista de filtros de eventos de la aplicacin. TRUCO: El listado 21.6 usa la palabra clave this como parmetro para la llamada al mtodo 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 hacer referencia al objeto cuyo cdigo se est ejecutando. Puede pensar en la instruccin del listado 21.6 que llama a RemoveMessageFilter() como si indicara "elimina la referencia a este filtro de mensajes del objeto Application".
Run
El mtodo Run() inicia el bucle de mensajes de Windows para una aplicacin. Todos los listados de este captulo han usado el mtodo Run(), que acepta como parmetro una referencia a un objeto de formulario. Ya debera estar familiarizado con el funcionamiento del mtodo Run().
461
462
GridToolTip
ButtonBase
Label
PropertyGridView
Button
LinkLabel
ParkingWindow
CheckBox
ListControl
AxHost
RadioButton
ComboBox
DataGrid
ScrollBar
ListBox
DateTimePicker
HScrollBar
CheckedListBox
GroupBox
VScrollBar
ListView
ScrollableControl
MdiClient
ContainerControl
Panel
MonthCalendar
Form
TabPage
PictureBox
ComponentEditorForm
ComponentEditorPage
PrintPreviewControl
PrintPreviewForm
ProgressBar
PropertyGrid
SnappableControl
UpDownBase
Splitter StatusBar
DomainUpDown
NumericUpDown
TabControl
UserControl
ToolBar
TextBoxBase
TrackBar
RichTextBox
TreeView
TextBox
UpDownButtons
GridViewEdit
DataGridTextBox
463
El listado 21.7 muestra varios conceptos importantes que deben tenerse en cuenta cuando se trabaja con controles WindowsForms . Estudie el constructor del primer formulario. Crea un nuevo objeto de clase Button y establece su posicin en el formulario con la propiedad Location del botn. Esta propiedad se hereda de la clase Control (lo que significa que la propiedad est disponibles para cualquier control derivado de la clase Control) y establece la posicin de la esquina superior izquierda del botn respecto a su contenedor. En el listado 21.7, la posicin del botn se establece en 25 pxeles a la derecha del borde izquierdo del formulario y en 25 pxeles por debajo de la parte superior del formulario. La posicin se establece con una nueva instancia de una estructura llamada Point , que est disponible en el espacio de nombres System.Drawing de .NET Framework:
MyButton.Location = new Point(25, 25);
TRUCO: El listado 21.7 usa la propiedad Location para establecer la ubicacin del control. El uso de esta propiedad para que el programa coloque los controles puede suponer demasiado trabajo en formularios complicados con muchos controles. Visual Studio .NET dispone de un diseador de formularios que permite, de forma visual, arrastrar y colocar controles en formularios. El diseador crea a continuacin los formularios C# equivalentes, liberando al programador de la tarea de tener que codificar toda la lgica de posicionamiento por s mismo. El siguiente concepto importante del listado 21.7 est relacionado con el control de eventos de un control. Las clases de controles admiten muchos eventos que se desencadenan cuando el usuario interacta con el control. Muchos de estos eventos se incluyen en la clase base Control, aunque la clase de control especfica controla otros eventos. El evento ms evidente de un botn de control sera un evento Click . Los botones de los formularios no son de utilidad a menos que puedan responder a una accin de un usuario que haga clic con el botn. Los controles de .NET Framework usan el modelo estndar delegado/evento para compatibilizar sus eventos. Los eventos de control se instalan usando instancias de un delegado llamado EventHandler . Este delegado admite dos argumentos: un objeto que especifica el elemento que envi el evento, y un objeto de una clase llamada EventArgs que encapsula los argumentos del evento. El cdigo del formulario del listado 21.7 incluye un mtodo llamado MyButtonClicked que modela el delegado EventHandler. Este mtodo se usa como un nuevo controlador de eventos y est conectado al evento Click del botn:
MyButton.Click += new EventHandler(MyButtonClicked);
La clase Form controla el evento Click del botn mostrando un cuadro de texto. Una clase llamada MessageBox admite la visualizacin de cuadros de
464
mensajes de Windows. La clase MessageBox contiene un mtodo esttico llamado Show() que muestra un mensaje en un cuadro de mensaje. El ltimo concepto importante del listado 21.7 es la instruccin que agrega el control al formulario:
Controls.Add(MyButton);
La propiedad Controls est definida en la clase base Control (recuerde que la clase Form se deriva de la clase Control). Es un objeto de una clase llamada ControlsCollection y gestiona una lista de controles secundarios que son gestionados por el control actual. El cdigo WindowsForms debe aadir controles a su coleccin Controls de los formularios contenedores antes de que puedan ser usados realmente.
465
nombre de clave es Message y cuyo valor es Hello from the string table!
Listado 21.8. Ejemplo de un archivo de texto de una tabla de cadenas #============= # String Table #============= Message = Hello from the string table!
Los archivos de tabla de cadenas deben compilarse para formar un ensamblado de modo que las aplicaciones C# puedan leerlos. Esto se hace con una herramienta llamada ResGen . La herramienta ResGen se incluye en el SDK de .NET Framework. Es una aplicacin de consola que lee el archivo de texto y produce una representacin binaria de la tabla con extensin resources . Si la tabla de cadenas del listado 21.8 se escribe en un archivo de texto llamado Listing218.txt, puede compilar la tabla de cadenas usando la siguiente lnea de comandos:
resgen Listing21-8.txt
Esto produce un archivo llamado Listing21-8.resources . Tras compilar un archivo resources para la aplicacin, se puede compilar en el ensamblado usando el argumento /res para el compilador de C#, como muestra la siguiente lnea de comandos:
csc /res:string.resources /out:test.exe test.cs
Esta lnea de comando ordena al compilador de C# que cree un ejecutable llamado test.exe a partir del archivo fuente de C# test.cs . Tambin le ordena incrustar en el ejecutable test.exe los recursos que encuentre en el archivo string.resources . Como los recursos estn incrustados en el ejecutable, slo se necesita enviar el ejecutable cuando se distribuye la aplicacin. El archivo de recursos binarios no es necesario en tiempo de ejecucin. Una vez que se han incrustado los recursos en la aplicacin, se pueden leer desde el cdigo de C#. El listado 21.9 es una modificacin del listado 21.7, en el que el mensaje del cuadro de mensajes se lee desde un recurso de cadenas.
Listado 21.9. Cmo leer desde un recurso de cadenas using using using using using System; System.Drawing; System.Windows.Forms; System.Resources; System.Reflection;
466
{ MainForm MyForm = new MainForm(); Application.Run(MyForm); } public MainForm() { Button MyButton = new Button(); Text = "Button Test"; MyButton.Location = new Point(25, 25); MyButton.Text = "Click Me"; MyButton.Click += new EventHandler(MyButtonClicked); Controls.Add(MyButton); } public void MyButtonClicked(object sender, Arguments) { ResourceManager FormResources = new ResourceManager("StringTable", Assembly.GetExecutingAssembly()); string Message; EventArgs
El listado 21.9 se compila con un recurso de tabla de cadenas cuyo archivo de texto contiene lo siguiente:
#============= # String Table #============= Message = The button has been clicked.
Este archivo de texto recibe el nombre de StringTable . txt y se compila formando un archivo de recursos binario llamado StringTable.resources mediante la siguiente lnea de comando:
resgen StringTable.txt
Este comando produce un archivo llamado StringTable.resources . Este recurso se vincula a la aplicacin cuando se compila el cdigo C# principal mediante la siguiente lnea de comando:
csc /res:StringTable.resources Listing21-9.cs
Se pueden leer recursos en las aplicaciones de C# usando una clase de .NET Framework llamada ResourceManager , que se incluye en un espacio de nom-
467
bres llamado S y s t e m . R e s o u r c e s . El cdigo del listado 21.9 crea un nuevo objeto ResourceManager para gestionar los recursos incrustados en el ejecutable. El constructor recibe dos argumentos: El nombre base del archivo de recursos binarios que contiene el recurso que se est abriendo. Se debe especificar este nombre, aunque no se necesita el archivo fsico porque el ensamblado agrupa 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 estn abriendo. Este parmetro es una referencia a un objeto de una clase llamada A s s e m b l y , que se encuentra en el espacio de nombres System. Reflection . Como los recursos que se estn abriendo estn incrustados en el ensamblado que se est ejecutando, si se llama al mtodo esttico GetExecutingAssembly() se devolver una referencia al ensamblado actual.
Tras inicializar el objeto ResourceManager, las cadenas se pueden abrir desde el administrador mediante un mtodo llamado GetString() . Este mtodo recibe un argumento de cadena: el nombre de clave de la cadena que se recupera. El mtodo devuelve el valor de la cadena nombrada por la clave.
Resumen
Este captulo estudia los fundamentos del proceso de desarrollo para la elaboracin de aplicaciones WindowsForms en C#. Tambin se estudian algunas clases elementales, como la clase Application , que gestiona la aplicacin
468
WindowsForms como un todo y la clase Form , que gestiona un formulario de la aplicacin. Tambin se hace un repaso a la arquitectura de las clases de controles WindowsForms y se examinan los atributos de ensamblado que pueden agregar informacin de versin y descriptiva al ensamblado. .NET Framework contiene un variado conjunto de clases para elaborar aplicaciones WindowsForms. El subsistema WindowsForms est compuesto por varias clases; por desgracia, las limitaciones de espacio no permiten una completa descripcin de todas ellas en este libro. Puede examinar la documentacin de cada clase WindowsForms . Use los conceptos de este captulo para comenzar a investigar todas las clases del espacio de nombres WindowsForms.
469
471
interfaz permite arrastrar y colocar controles en WebForms, que luego pueden ser programados en Visual C#. Al programar en Visual Studio .NET se puede separar el contenido en cdigo y en HTML de un Web Form. Esto hace que resulte muy sencillo separar la programacin lgica de la presentacin lgica, lo que nos permite concentrarnos en implementar la funcionalidad del proyecto, ms que en la presentacin de datos. En este captulo aprenderemos a crear una aplicacin Web ASP.NET mediante Visual C#. Mientras creamos la aplicacin, disearemos un WebForm que usa controles de servidor, como etiquetas, cuadros de texto, cuadros de listas, hipervnculos y botones. Por ltimo, aprenderemos a controlar los eventos generados por los controladores de servidor.
Presentacin de WebForms
Los WebForms son la base de una aplicacin basada en Web. La aplicacin Web los usa para interactuar con el usuario. Un WebForm puede incluir varios controles de servidor, como cuadros de texto, etiquetas, cuadros de listas, botones
472
de opcin, casillas de verificacin y botones, los cules facilitan la interaccin del usuario con la aplicacin. Un WebForm consta de dos componentes: la interfaz de usuario (IU) y la lgica de la aplicacin (aplicacin). La interfaz de usuario es el componente visual de un WebForm. Se compone de HTML y controles especficos de la aplicacin Web. La interfaz de usuario es el contenedor del texto y los controles que deben aparecer en la pgina Web. Se especifican en un archivo con la extensin .aspx . La lgica de programacin de una aplicacin Web de ASP.NET est en un archivo separado que contiene el cdigo encargado de controlar las interacciones del usuario con el formulario. Este archivo recibe el nombre de archivo de "cdigo oculto". Cuando se ejecuta un formulario escrito en C#, el archivo de cdigo oculto genera dinmicamente el resultado HTML de la pgina. El archivo de cdigo oculto de C# tiene una extensin .aspx.cs. La ventaja de separar el cdigo del contenido es que el programador no necesita concentrarse en la lgica que se usa para mostrar el resultado. El diseador Web puede controlar esta tarea.
473
son opacos. Es decir, el servidor no los procesa. Sin embargo, al convertir estos controles en controles de servidor HTML pueden quedar a la vista del servidor para que realice el proceso. Mediante el uso de atributos, como ID y RUNAT , se pueden convertir los controles HTML en controles de servidor HTML. Puede aadir estos controles a un WebForm usando la ficha HTML del cuadro de herramientas. Por otra parte, los controles de servidor son completamente transparentes para la aplicacin y permiten al programador controlar eventos del lado del servidor para gestionar la aplicacin Web. Aparte de los cuadros de texto convencionales, esta categora de controles tambin incluye los controles de validacin. Los controles de validacin son controles programables que ayudan a validar las entradas del usuario. Por ejemplo, se pueden usar estos controles para validar el valor de un campo o el patrn de caracteres introducido por el usuario. Para validar la entrada del usuario, es necesario adjuntar estos controles a los controles de la entrada.
Control Label
El control Label se usa para mostrar texto esttico en un WebForm. Los usuarios no pueden editar el texto de un control Label. Al aadir un control Label, el texto Label aparece como su ttulo. Sin embargo, asignando un valor a la propiedad Text del control, es posible modificar el ttulo del control. Se puede asignar un valor a las propiedades del control Label en tiempo de ejecucin en
474
el archivo de cdigo oculto (.cs file). Por ejemplo, si se quiere cambiar el texto de una etiqueta cuando un usuario pulsa un botn. Para ello, puede utilizar el siguiente cdigo:
Label1.Text="Welcome"
En el anterior cdigo. Label1 es el ID del control Label cuya identificacin quiere cambiar. Si quiere que el control Label desaparezca cuando un usuario pulse un botn, puede usar el siguiente cdigo:
Label1.Visible=False
Control TextBox
El control TextBox se usa para obtener informacin, como texto, nmeros y fechas, de los usuarios de un WebForm. Por defecto, un control TextBox es un control de una lnea que permite a los usuarios escribir caracteres en una sola lnea. Sin embargo, tambin se puede establecer el control TextBox como un control multilnea. Un cuadro de texto multilnea muestra varias lneas y permite el ajuste de texto. Un control TextBox tambin puede usarse para aceptar contraseas. Los controles TextBox utilizados para aceptar contraseas ocultan los caracteres escritos por los usuarios, mostrndolos como asteriscos (*). Puede establecer la apariencia de un control TextBox mediante sus propiedades, como BackColor o ForeColor. Tambin puede cambiar la propiedad TextMode de un control TextBox para determinar si un control TextBox acta como un cuadro de texto para aceptar una contrasea, una sola lnea de texto o varias lneas de texto.
475
2. En el cuadro de dilogo Editor de la coleccin ListItem, haga clic en Agregar para crear un nuevo elemento. Se crear un nuevo elemento y sus propiedades se mostrarn a la derecha del cuadro de dilogo. 3. Verifique que el elemento est seleccionado en la lista Miembros y a continuacin establezca las propiedades del elemento. Cada elemento es un objeto distinto y tiene las siguientes propiedades: Selected: representa un valor booleano que indica si el elemento est seleccionado. Text: representa el texto que se muestra para el elemento de la lista. Value: representa el valor asociado al elemento. El valor de un control no se muestra al usuario. Sin embargo, el servidor usa el valor para procesar la informacin del control. Por ejemplo, puede establecer la propiedad Text de un elemento como Nombre de Ciudad y la propiedad Value del cdigo postal de esa ciudad como identificacin nica. Cuando el servidor procesa la informacin representada por el campo Nombre de Ciudad, se puede hacer caso omiso del texto proporcionado por el cuadro de texto, y cualquier proceso se basar en el correspondiente valor del campo.
4. Especifique el texto que se mostrar al usuario. 5. Repita los pasos 2-4 para agregar los controles necesarios al control CheckBoxList. 6. Haga clic en Aceptar para cerrar el cuadro de dilogo Editor de la coleccin ListItem. TRUCO: La decisin de usar el control CheckBox o el control CheckBoxList depende de las necesidades especficas. El control CheckBox proporciona ms control sobre la presentacin de las casillas de verificacin de la pgina. Por ejemplo, se puede establecer la fuente y el color de las casillas de verificacin por separado o incluir texto entre las diferentes casillas de verificacin. Por otra parte, el control CheckBoxList es una mejor opcin si se necesitan agregar series de casillas de verificacin.
476
coleccin de botones de opcin. Los botones de opcin casi nunca se usan individualmente. Ms bien se usan dentro de un grupo. Un grupo de botones de opcin proporciona un conjunto de opciones mutuamente excluyentes. Esto significa que slo se puede seleccionar un botn de opcin en un grupo. Un conjunto de botones de opcin puede agruparse de estas dos maneras: Puede agregar un conjunto de controles RadioButton a la pgina y asignarlos a un grupo manualmente. Puede usar la propiedad GroupName para hacerlo. Puede agregar un control RadioButtonList a la pgina. Los botones de opcin en el control se agrupan automticamente, de modo que no es necesario agruparlos manualmente.
Tras aadir un control RadioButtonList al WebForm, hay que agregar los botones de opcin. Esto se puede hacer usando la propiedad Items del mismo modo que hicimos con el control CheckBoxList.
Control ListBox
El control ListBox representa una coleccin de elementos de lista. El control permite a los usuarios seleccionar uno o ms elementos de la lista. Se pueden aadir elementos a la lista individualmente mediante la propiedad Items. Tambin puede especificar si el usuario puede seleccionar varios elementos de la lista o si slo puede seleccionar un nico elemento, mediante la propiedad SelectionMode del control ListBox.
Control DropDownList
El control DropDownList permite a los usuarios seleccionar un elemento de un conjunto de elementos predefinidos (siendo cada elemento un objeto diferente con sus propias caractersticas). Se pueden agregar elementos a un control DropDownList mediante su propiedad Items. A diferencia del control ListBox, slo se puede seleccionar un elemento cada vez y la lista de elementos permanece oculta hasta que el usuario hace clic en el botn desplegable.
Control HyperLink
El control HyperLink permite a los usuarios moverse de un WebForm a otro dentro de una aplicacin. Tambin permite a los usuarios desplazarse hasta una URL que puede estar asociada con el control. Con el control HyperLink, el texto o una imagen pueden funcionar como un hipervnculo. Cuando un usuario hace clic en el control, se abre el WebForm de destino o la URL.
477
478
Propiedad ID Rows
Descripcin Representa el ID nico del control. Representa una coleccin de objetos TableRow. Un Control TableRow representa una fila de la tabla. Representa una coleccin de objetos TableCell. Un control TableCell representa una celda de la tabla. Representa el alineamiento vertical, como las partes superior e inferior de la celda. Representa el alineamiento horizontal, como los alineamientos derecho e izquierdo de la celda.
Cells
TableRow
VerticalAlign
TableCell
HorizontalAlign
TableCell
Control ImageButton
El control ImageButton permite a los programadores mostrar imgenes en un WebForm y gestionarlas durante el diseo o en tiempo de ejecucin. Este control representa un botn grfico, que mejora la apariencia del WebForm. Se puede establecer la propiedad ImageUrl para que apunte a una imagen especfica.
479
crear, procesar y emplear aplicaciones ASP. Antes de crear un proyecto de aplicacin Web necesitar asegurarse de que se cumplen en la plataforma de desarrollo los siguientes requisitos bsicos para la aplicacin Web: Deber tener acceso a un equipo que ejecute Microsoft IIS Server. Debe instalar IIS Server en una particin NTFS. Este tipo de particin mejora la seguridad y rendimiento del servidor. Tras cumplir estos requisitos, puede usar Visual Studio .NET para crear una aplicacin Web ASP.NET. Para ello, siga los siguientes pasos: 1. Agregue un proyecto de aplicacin Web ASP.NET a su aplicacin. 2. Cree la interfaz de usuario de la aplicacin Web. 3. Codifique la lgica de la aplicacin. En la siguiente seccin se indican los pasos para crear un nuevo proyecto.
Proyecto para crear una aplicacin con una interfaz de usuario de Windows. Nombre: Ubicacin: WindowsApplication3 e:\applications Examinar...
Figura 22.1. Puede seleccionar una o ms plantillas de empresa del cuadro de dilogo Nuevo proyecto.
480
TRUCO: Tambin puede pulsar la combinacin Control-Mays-N para abrir el cuadro de dilogo Nuevo proyecto. 2. Seleccione Proyectos de Visual C# de la lista Tipos de proyecto. 3. Seleccione Aplicacin Web ASP.NET de la lista Plantillas en el cuadro de dilogo Nuevo proyecto. 4. Escriba el nombre del proyecto en el cuadro Nombre. 5. Escriba el nombre de IIS Server en el cuadro Ubicacin o acepte la ubicacin por defecto. El cuadro de dilogo Nuevo proyecto puede verse completo en la figura 22.2.
Nuevo proyecto Tipos de proyecto:
Proyectos de Visual Basic Proyectos de Visual C# Proyectos de Visual C + + Proyectos de instalacin e implementacin Otros proyectos Soluciones de Visual Studio Aplicacin Web ASP.NET Proyecto para crear una aplicacin con una interfaz de usuario Web. Nombre: Ubicacin: Application1 http://localhost/WebApplication1 Examinar... servicio Web Biblioteca de A5P.NET controles Web Aplicacin Biblioteca de Biblioteca de pata Windows clases controles ... Plantillas:
Figura 22.2. Seleccione una plantilla y especifique el nombre de la aplicacin en el cuadro de dilogo Nuevo proyecto.
TRUCO: Si el servidor Web est instalado en su equipo, tambin puede escribir http://localhost en el cuadro Ubicacin. 6. Haga clic en Aceptar. Aparecer un cuadro de dilogo mientras Visual Studio .NET crea la nueva aplicacin Web ASP.NET. El cuadro de dilogo se muestra en la figura 22.3. NOTA: Quizs tenga que esperar unos instantes a que ASP.NET cree el proyecto. Tambin debe asegurarse, antes de crear la aplicacin, de que el servidor Web est funcionando. Para activar el servidor Web, seleccione Inicio>Ejecutar. En el cuadro de dilogo Ejecutar, escriba net start iisadmin y pulse Intro.
481
Cancelar
Tras crear un nuevo proyecto de aplicacin Web, el asistente para aplicacin Web crea automticamente algunos archivos de proyecto necesarios, como AssemblyInfo.cs, Web.config y Global.asax, junto al archivo principal de la pgina, WebForm1.aspx. La figura 22.4 muestra estos archivos en el explorador de soluciones.
Explorador de soluciones
Solucin 'UserRegApp' (1 proyecto) UserRegApp References AssemblyInfo.cs Global.asax UserRegApp.vsdisco Web.config WebForrn1.aspx
Figura 22.4. La ventana de proyecto muestra todos los archivos creados por el asistente de la aplicacin Web.
Un WebForm de Visual Studio .NET tiene dos vistas: Diseo y HTML: Vista Diseo. La interfaz de usuario de un WebForm se disea en vista Diseo. Esta vista ofrece dos diseos para el WebForm: diseo de cuadrcula y diseo de flujo: Diseo de cuadrcula. En el diseo de cuadrcula, se pueden colocar los controles en el WebForm segn las coordenadas de cada control. Diseo de flujo. En el diseo de flujo, se puede disear de forma lineal un WebForm, del mismo modo que se disea un documento de Microsoft Word, de arriba abajo.
Puede alternar entre los diferentes diseos haciendo clic con el botn derecho en el WebForm y seleccionando Propiedades. En la pgina Propiedades puede seleccionar el diseo que considere apropiado. Vista HTML. Esta vista representa la correspondiente sintaxis ASP.NET del WebForm. Para abrir la vista HTML, slo tiene que hacer clic en la
482
ficha HTML. Si la ficha HTML no est visible, puede hacer clic con el botn derecho del ratn y seleccionar Ver fuente HTML en el men contextual.
TRUCO: Al usar controles HTML debe convertirlos a controles de servidor con el fin de que estn disponibles para ser codificados en el servidor. Para ello, haga clic con el botn derecho en el control HTML requerido y seleccione la opcin Ejecutar como control del servidor en el men contextual. Este mtodo permite crear complicados WebForms de forma cmoda y rpida. Usar sintaxis de Visual C# para agregar los controles mediante programacin. Tambin puede agregar controles Web a un WebForm mediante la sintaxis de Visual C#. Slo se puede usar la sintaxis de C# en la vista HTML de la pgina (archivo .aspx). La sintaxis real depende del tipo de control que quiera aadir. Por ejemplo, la sintaxis que se usa para agregar un control de cuadro de texto HTML es la siguiente.
<input id=Text1 Type=text runat="server">
NOTA: Visual Studio .NET permite agregar controles de servidor ASP.NET mediante una etiqueta de lenguaje extensible para el anlisis de documentos (XML). La sintaxis usada para agregar un cuadro de texto ASP.NET es:
<asp:TextBox id=TextBox1 runat="server"></asp:TextBox>
Cada control tiene una propiedad ID que se usa para identificar unvocamente al control.
483
En la anterior sintaxis: Control_ID representa la propiedad ID del control. Property representa la propiedad del control. Value representa el valor asignado a la propiedad del control.
La figura 22.5 muestra un WebForm que contiene los habituales controles Web, como etiquetas, cuadros de texto, hipervnculos, botones de opcin, casillas de verificacin y botones. Como puede ver, el WebForm es un formulario de registro de usuario. El formulario est diseado para aceptar entradas de usuario desde varios controles. Tras rellenar el formulario, el usuario puede hacer clic en el botn Submit (Enviar) para completar el registro. El botn Submit abre otro WebForm que muestra un mensaje junto al nombre que el usuario introdujo en el control TextBox. Si el usuario hace clic en el botn Reset (Restablecer), la informacin que ha introducido el usuario se elimina de los controles de ese formulario.
WebForm1 - Microsoft Internet Explorer Archivo Edicin Ver Favoritos Herramientas Ayuda Favoritos Multimedia Ir Vnculos
State
Suscription
Submit
Reset
Listo
Intranet local
Figura 22.5. El formulario de registro de usuario muestra los controles comunes que se pueden agregar a un WebForm.
Para crear el formulario mostrado en la figura 22.5. realice los siguientes pasos:
484
1. Seleccione el formulario WebForm1.aspx y pulse F4 para que aparezca la ventana Propiedades. 2. En la ventana Propiedades, pulse el botn de puntos suspensivos para que aparezca la propiedad bgColor y seleccione Propiedades. Se abrir el cuadro de dilogo Selector de colores. 3. En el cuadro de dilogo Selector de colores, seleccione un matiz rosa y haga clic en Aceptar. El color del WebForm cambiar al color que ha especificado. 4. Agregue controles al formulario y cambie sus propiedades, como recoge la tabla 22.2.
Tabla 22.2. Controles que se pueden agregar a un WebForm
Control Label
Font Bold=True Size=Larger Etiquetas para Nombre, Correo electrnico, Estado y Suscripcin TextBox TextBox DropDownList ID=txtEmail ID=lstState Items=Arizona, California, Florida ID=lstOptions Items=Books, Magazines ID=BtnSubmit Text=Reset Button ID=BtnReset Text=Reset Junto al botn Submit El texto de cada etiqueta debe ser la misma que el ttulo deseado. ID=txtName Una bajo otra en el lado izquierdo de la pantalla
Junto a la etiqueta Name Junto a la etiqueta EMail Junto a la etiqueta State Junto a la etiqueta Suscription Bajo la etiqueta Suscription
CheckBoxList Button
485
La interfaz de su WebForm, como aparece en la figura 22.6, est lista. Agregar la funcionalidad de los botones Submit y Reset en la siguiente seccin. Sin embargo, antes de continuar, agregue a la aplicacin Web otro formulario que muestre los detalles sobre el usuario registrado cuando ste haga clic en el botn Submit. Para agregar el WebForm, siga estos pasos: 1. Seleccione Proyecto>Agregar WebForm. Se abrir el cuadro de dilogo Agregar nuevo elemento. TRUCO: Si no encuentra la opcin de men Agregar WebForm bajo el men Proyecto, haga clic en cualquier parte de la ventana Formulario y a continuacin seleccione la opcin de men.
UserRegApp - Microsoft Visual C# .NET [disear] - WebForm1.aspx* Archivo Edicin Ver Proyecto Generar Depurar Datos Formato Tabla Insertar Marcos Herramientas Ventana Ayuda
Name
State
Arizona
Submit
Reset
Diseo Listo
HTML
Figura 22.6. El WebForm debera tener este aspecto una vez completado.
2. Seleccione WebForm en la lista de Plantillas, especifique un nombre para el WebForm (o mantenga el nombre por defecto) y haga clic en Abrir para crear un nuevo WebForm. Pude usar el recin agregado WebForm para mostrar un mensaje al usuario. Por lo tanto, necesitar agregar un control Label al formulario. Llame a la etiqueta lblMessage. Tras agregar controles al formulario, debe responder a los eventos generados por los controles del formulario para trabajar con la interaccin del usuario. Por
486
ejemplo, si un usuario hace clic en el botn Submit, el formulario necesitar ser procesado y los datos de la base de datos tendrn que actualizarse. La siguiente seccin describe el procedimiento para controlar los eventos generados por los controles de un Web Form.
487
Servidor (cs)
Cdigo de la aplicacin
Enviar
Encontrar
Elemento
La tabla 22.3 describe los eventos ms comunes asociados a diferentes controles de servidor ASP.NET
Tabla 22.3. Eventos asociados a controles de servidor ASP.NET
Descripcin Tiene lugar cuando el foco sale del control. Tiene lugar cuando se hace clic en el control. Tiene lugar cuando se cambia la seleccin de la lista. Tiene lugar cuando se hace clic en el botn. Este evento hace que el formulario sea enviado al servidor.
Por defecto, en un WebForm slo el evento Click de los controles de servidor Button, LinkButton y ImageButton pueden hacer que se enve el formulario al servidor para ser procesado. En ese caso, el WebForm es devuelto al servidor. Cuando otros controles generan los eventos de cambio, son atrapados y ocultados. No hacen que el formulario se enve inmediatamente. Solamente cuando el formulario es devuelto mediante un clic en un botn, todos los eventos ocultos se desencadenan y son procesados. No hay una secuencia particular para procesar estos eventos de cambio en el servidor. Sin embargo, el evento Click
488
se procesa solamente despus de que todos los otros eventos de cambio hayan sido procesados.
Controladores de eventos
Cuando se desencadena un evento, ste necesita ser controlado para ser procesado posteriormente. Los procedimientos que se ejecutan cuando ocurre un evento reciben el nombre de controladores de eventos. Los controladores de eventos pueden ser creados automtica o manualmente. Cuando los eventos se controlan automticamente, al hacer doble clic sobre un control en la vista Diseo del WebForm (fichero .aspx) se crea un controlador de eventos. Por ejemplo, cuando se hace doble clic sobre el botn btnSubmit, se genera el siguiente cdigo. A continuacin puede escribir el cdigo en el controlador de eventos de la funcin generada por Visual Studio .NET:
Public void btnSubmit_Click(Object sender, System.EventArgs e) { }
En el anterior cdigo, el procedimiento btnSubmit_Click es el controlador de eventos del evento Click del botn. El procedimiento toma dos argumentos. El primero contiene el emisor de eventos. Un emisor de eventos es un objeto, como un formulario o un control, que puede generar eventos. El segundo argumento contiene informacin adicional asociada al evento, como las coordenadas de posicin x e y en las que se ha pulsado el botn del ratn. Para crear manualmente un controlador de eventos, seleccinelo de la lista emergente de la ventana Propiedades. Ya est preparado para implementar el control de eventos para el WebForm que aparece en la figura 22.7. Al hacer clic en el botn Submit, aparece una nueva pgina (en este caso WebForm2.aspx), que muestra un mensaje de bienvenida junto al nombre del usuario registrado. Para implementar esta funcionalidad debe escribir el siguiente cdigo en el evento Click del botn Submit del WebForm WebForm1.aspx:
private void BtnSubmit_Click(object sender, System.EventArgs e) { Response.Redirect("WebForm2.aspx?strName=" + txtName.Text); }
TRUCO: Para codificar el evento del botn Submit, haga doble clic en la vista Diseo. En el anterior cdigo, el mtodo Redirect de la clase HttpResponse redirige al usuario a la pgina WebForm2.aspx y pasa el valor del parmetro txtName a la pgina de destino.
489
Tras pasar el valor del cuadro de texto txtName, debe inicializar WebForm2 para controlar la cadena pasada desde el formulario de registro. Para hacerlo, WebForm2.aspx debe tener el siguiente cdigo en el evento Load:
private void Page_Load(object sender, System.EventArgs e) { lblMessage.Text="Hi! " + Request.QueryString.Get("strName"); }
TRUCO: Para codificar el evento Load del formulario WebForm2.aspx, haga doble clic en el formulario en vista Diseo. En el anterior cdigo, el ttulo de la etiqueta lblMessage en el archivo WebForm2.aspx es el valor que se asigna al valor almacenado en la variable strName. Cuando el usuario hace clic en el botn Submit de WebForm1.aspx, es redirigido a la pgina WebForm2.aspx, como muestra la figura 22.8.
WebForm1 - Microsoft Internet Explorer
Archivo Edicin Ver Favoritos Herramientas Ayuda Favoritos Multimedia Ir Vnculos
Bsqueda
http://localhost/UserRegApp/WebForm2.aspx?strName=John
Hi! John
Listo
Intranet local
Figura 22.8. Cuando un usuario es redirigido a otra pgina, el nombre del usuario se pasa en la cadena de consulta.
Cuando el usuario hace clic en el botn Reset, debe generarse un evento que elimine todos los controles rellenados por el usuario en WebForm1.aspx. Para implementar esta funcionalidad, codifique el evento Click del botn Reset como se indica a continuacin:
490
System.EventArgs e)
En el cdigo anterior, cuando se hace clic en el botn Reset del formulario de registro, el formulario se reinicia y vuelve a su estado original.
491
pueda ver todas las opciones que el usuario haya seleccionado con anterioridad. StateBag. La clase StateBag es el mecanismo de almacenamiento de los controles de servidor. Esta clase proporciona propiedades para almacenar informacin en pares clave-valor. Por ejemplo, si quiere almacenar datos especificados por el usuario para una pgina, puede usar una instancia de la clase StateBag para almacenar estos datos. Cada control de servidor incluye una propiedad EnableViewState. Cuando establece el valor de esta propiedad como t r u e , el estado del control se conserva en el servidor entre los viajes de ida y vuelta. As, si el usuario ha seleccionado una o ms opciones de una lista, las opciones se guardan en el servidor entre los viajes de ida y vuelta.
Resumen
En este captulo ha aprendido a crear una sencilla aplicacin Web mediante Visual C# en el entorno ASP.NET. Se han estudiado los fundamentos de ASP.NET y cmo se crean aplicaciones Web en Visual C#. ASP.NET incluye un entorno de tiempo de ejecucin separado que gestiona la ejecucin de las aplicaciones ASP.NET. Tambin incluye nuevos componentes de servidor, llamados WebForms, que encapsulan la funcionalidad de una pgina Web. Puede agregar uno o ms controles de servidor a un WebForm. Los controles de servidor son los responsables de mostrar los datos a los usuarios y procesar sus interacciones. Hemos creado un proyecto de aplicacin Web y le hemos agregado un WebForm. Al crear la aplicacin, usamos la plantilla ASP.NET para aplicaciones Web para crear una solucin y agregar un proyecto ASP.NET a la solucin. A continuacin, diseamos una pgina Web usando controles Web corrientes, como los controles que representan etiquetas, cuadros de texto, cuadros de listas, hipervnculos, botones y similares. Por ltimo, aprendimos a controlar los eventos que generan los controles en el WebForm.
492
23 Programacin
de bases de datos con ADO.NET
ADO.NET es la tecnologa ms moderna para el acceso de datos y forma parte de .NET Framework. ADO.NET utiliza y mejora las anteriores tecnologas de acceso a datos. La incursin de Microsoft en el acceso a datos universal comenz con la conectividad abierta de bases de datos (ODBC). La idea sobre la que se asienta esta tecnologa ODBC (crear un modo estndar de acceso a las bases de datos mediante programacin) ha sido usada en todas las tecnologas de acceso a datos posteriores procedentes de Redmond, Washington (donde est la sede de Microsoft). En el caso de ODBC, este mtodo estndar est ejemplificado en el API (Interfaz de programacin de aplicaciones) de ODBC. Cualquier proveedor de bases de datos que quiera garantizar el cumplimiento del estndar ODBC debe elaborar el software que convierta una llamada ODBC (hecha de acuerdo con el API) en una llamada de una base de datos nativa. Este software recibe el nombre de controlador ODBC y es el puente entre una aplicacin de cliente genrica y una base de datos especfica. Mediante este enfoque, los programadores de aplicaciones evitan tener que aprender a usar el API de base de datos especfico del proveedor. Todo lo que un programador necesita saber es cmo escribir aplicaciones cliente usando el API de ODBC. Este hecho mejora la productividad y permite escribir programas que pueden ser usados con diferentes bases de datos. Sin embargo, el API de ODBC fue diseado en un principio, sobre todo, con los programadores de C en mente, y era difcil de usar en otros lenguajes (como
495
Visual Basic). Esto condujo finalmente a la creacin de Objetos de datos ActiveX (ADO), una tecnologa de acceso a datos diseada para ser usada con cualquier lenguaje compatible con el modelo de componentes objetos (COM) de Microsoft. ADO presenta un modelo simple de objetos que convierte el acceso a datos en los programas MS Windows en una tarea sencilla. Adems, ADO introduce el concepto de conjuntos de datos sin conexin como un modo de transportar datos entre los niveles de una aplicacin distribuida. El API de bajo nivel detrs de ADO se llama OLE DB. Este API fue diseado por programadores de C++ y es lo que los distribuidores de bases de datos suelen usar para escribir proveedores de OLE DB (el termino ms usado para referirse a los controladores OLE DB, el software que convierte las llamadas ADO en llamadas a bases de datos nativas). Microsoft tambin ha escrito un proveedor de OLE para OBDC. Este proveedor permite hacer llamadas ADO a cualquier base de datos que cumpla con el estndar ODBC. Como ver en este captulo, ADO.NET mantiene un modelo de objetos similar al de ADO y mejora el concepto de conjuntos de registros sin conexin, proporcionando un modo de reunir ms informacin en un objeto ADO.NET llamado conjunto de datos. De hecho, ADO.NET fue diseado pensando en los datos sin conexin porque su falta de estado funciona mejor en las aplicaciones de Internet distribuidas. En este captulo aprender a usar ADO.NET para manipular datos. Si ya conoce ADO, muchos de los conceptos le sern familiares e incluso el cdigo le puede resultar conocido.
496
Tabla 23.1. DataSet y clases relacionadas Clase DataSet Descripcin Una cach en memoria de datos, que pueden consistir en varias DataTables relacionadas entre s. Est diseada para su uso sin conexin en las aplicaciones distribuidas. Un contenedor de datos, que puede componerse de varias DataColumns. Cada fila de datos se contiene en una DataRow. Una fila de datos concreta en una DataTable La definicin de una columna (nombre, datos, tipo, etc.) en una DataTable Una relacin entre dos DataTables de una DataSet, normalmente usada para representar relaciones de clases externas Una restriccin a una o ms DataColumns, usada para representar limitaciones como la exclusividad Asigna los nombres de columna desde la tabla de una base de datos hasta los nombres de columna de DataTable en el DataSet Asigna los nombres de tabla en una base de datos hasta los nombres de DataTables en DataSet Una vista personalizada de una DataTable que puede usarse en la clasificacin, filtrado y bsqueda.
DataTable
DataTableMapping DataView
El RecordSet de ADO evolucion gradualmente como el modo estndar de preparar los datos entre los distintos niveles de una aplicacin distribuida. El DataSet asume este papel en ADO.NET y proporciona varios mtodos para compatibilizar la cach en memoria con su fuente de datos en una base de datos. Estos mtodos incluyen A c c e p t C h a n g e s ( ) , G e t C h a n g e s ( ) , HasChanges(), HasErrors() y RejectChanges() . Adems, permiten recuperar cualquier cambio en forma de un DataSet modificado, examinar las modificaciones en busca de errores y decidir si aceptar o rechazar los cambios. Al final de este proceso puede actualizar los datos fuente en la base de datos con una simple llamada al mtodo Update() .
497
proveedores de OLE DB. Un segundo conjunto de clases ha sido optimizado para la base de datos insignia de Microsoft, SQL Server. Todos los nombres de las clases genricas comienzan por OleDb. Todos los nombres de las clases especficas de SQL Server empiezan con Sql. Cada clase genrica tiene su clase especfica SQL Server correspondiente. Por ejemplo, la clase que se usa para ejecutar las instrucciones SQL en SQL Server recibe el nombre de SqlCommand. La clase genrica recibe el nombre de OleDbCommand. Este captulo emplea las clases genricas incluso para acceder a la base de datos SQL Server. Cuando escriba sus propias aplicaciones que accedan a SQL Server, deber decidir si quiere emplear las clases especficas SQL Server, ms rpidas, o las clases genricas, que permiten intercambiar distribuidores cambiando la cadena de conexin. La eleccin se basa en velocidad o portabilidad. Las clases SQL Server llaman directamente al nivel nativo de la base de datos. Las clases genricas usan OleDb y atraviesan un nivel COM antes de llamar al nivel nativo de la base de datos. El coste de este nivel adicional supone un descenso en el rendimiento. Las clases DataSet se usan en conjuncin con el proveedor de OLE DB y el proveedor de SQL Server. La tabla 23.2 enumera las clases especficas de cada proveedor. Muchas de ellas le resultarn conocidas a alguien que ya ha trabajado con ADO.
Tabla 23.2. DataSet y clases relacionadas Clases de proveedores de SQL SqlCommand de OLE DB OleDbCommand
Un contenedor de clase para una instruccin SQL. La clase puede administrar instrucciones directas SQL como las instrucciones SELECT, UPDATE, DELETE o INSERT y una llamada al procedimiento almacenado. Usada para generar las instrucciones SQL SELECT, UPDATE, DELETE o INSERT. Una conexin a una base de datos. Un conjunto de instrucciones SELECT, UPDATE, DELETE o INSERT y una conexin a una base de datos que puede usarse para completar un DataSet y actualizar la base de datos subyacente. Un conjunto de registros de datos slo hacia adelante.
Descripcin
SqlCommandBuilder OleDbCommandBuilder
SqlDataConnection SqlDataAdapter
OleDbConnection OleDbDataAdapter
SqlDataReader
OleDbDataReader
498
Clases de proveedores
de SQL de OLE DB
Descripcin
SqIError
OleDbError
Un aviso o error devuelto por la base de datos, perteneciente a una coleccin Errors. El tipo de excepcin iniciado cuando ocurren errores en la base de datos. Un parmetro para un procedimiento almacenado. Una transaccin de base de datos.
SqIException
OleDbException
SqlParameter SqlTransaction
OleDbParameter OleDbTransaction
En la siguiente seccin estudiaremos el modo en el que estas clases trabajan con las clases comunes, DataSet y sus clases relacionadas, para realizar operaciones con las bases de datos comunes.
Adems, muchas funciones se han sacado del contexto de su clase. Se supone que las funciones estn incluidas en el mbito definido por la siguiente definicin de clase:
namespace ADOdotNET { class ADOdotNET ( // NOTA: Coloque aqu la funcin } }
Una vez hechos estos comentarios previos, podemos adentrarnos en ADO.NET. Una a una, esta seccin examina cada categora de operaciones que puede realizarse con ADO.NET:
499
Operaciones que no devuelven filas. Operaciones que slo devuelven una fila. Operaciones que slo afectan a una fila. Operaciones que devuelven varias filas. Operaciones que afectan a varias filas. Operaciones que devuelven datos jerrquicos.
500
Listado 23.2. Ejemplo de cadena de conexin para Microsoft Access private static string oleDbConnectionString { get { // NOTA: Se presupone que se instal // Microsoft Office Pro en el directorio por defecto. return "Provider=Microsoft.Jet.OLEDB.4.0;" +"Data Source=C:\\Program Files\\Microsoft Office\\Office\\Samples\\Northwmd.MDB"; } }
El segundo objeto que se usa para ejecutar una consulta es OleDbCommand. Su constructor recibe un argumento de cadena (el texto de la instruccin SQL que se quiere ejecutar) y un objeto O l e D b C o n n e c t i o n . La propiedad CommandType permite especificar si el comando que se ejecuta es un procedimiento almacenado, una consulta Access o texto simple. La consulta se realiza mediante el mtodo ExecuteNonQuery() . Los errores, como una infraccin de clave primaria, se notifican mediante excepciones. Tambin se pueden usar objetos Command para ejecutar comandos SQL que no devuelvan filas de datos, como en el ejemplo del listado 23.3:
Listado 23.3. Plantilla para usar un comando y ejecutar una instruccin SQL que no devuelve filas // Declare y asigne los valores apropiados para // oleDbConnectionString y strSQLStatement // Crea objetos OleDb OleDbConnection databaseConnection = new OleDbConnection(oleDbConnectionString); OleDbCommand databaseCommand = new OleDbCommand(strSQLStatement, databaseConnection); // NOTA: Slo se debe usar una de las dos instrucciones que se muestran a continuacin, NO AMBAS. // Si estamos tratando con una instruccin SQL (es decir, NO con un procedimiento almacenado), use: databaseCommand.CommandType = CommandType.Text; // Si estamos tratando con un procedimiento almacenado, use: databaseCommand.CommandType = CommandType.StoredProcedure; try { // Establece la conexin de base de datos databaseConnection.Open(); // Ejecuta el comando de SQL int numRows = databaseCommand.ExecuteNonQuery(); // Haz otra cosa, p. ej. informar sobre numRows } catch (Exception e) {
501
La invocacin de procedimientos almacenados con parmetros es un poco ms complicada. El listado 23.4 muestra el cdigo SQL para un procedimiento almacenado al que se quiere llamar.
Listado 23.4. Un procedimiento almacenado de SQL Server para insertar un registro
USE [Northwind] GO CREATE PROCEDURE [pc_insCustomers] (@CustomerID_1 [nchar](5), @CompanyName_2 [nvarchar](40), @ContactName_3 [nvarchar](30), @ContactTitle_4 [nvarchar](30), @Address_5 [nvarchar](60), @City_6 [nvarchar](15), @Region_7 [nvarchar](15), @PostalCode_8 [nvarchar](10), @Country_9 [nvarchar](15), @Phone_10 [nvarchar](24), @Fax_11 [nvarchar](24)) AS INSERT INTO [Northwind].[dbo].[Customers] ( [CustomerID], [CompanyName], [ContactName], [ContactTitle], [Address], [City], [Region], [PostalCode], [Country], [Phone), [Fax]) VALUES ( @CustomerID_1, @CompanyName_2, @ContactName_3, @ContactTitle_4, @Address_5, @City_6, @Region_7, @PostalCode_8,
502
La nica parte complicada es saber cmo definir y establecer los parmetros. Esto se hace mediante la coleccin Parameters. Como en cualquier coleccin, los nuevos miembros se crean con el mtodo Add() . El parmetro recin creado es devuelto y se puede establecer la direccin (tanto si el parmetro se usa slo para introducir datos, para salida de datos o para ambas operaciones) y el valor. Los parmetros del mtodo Add() son el nombre del parmetro del procedimiento almacenado, su nombre, su tipo de datos y su tamao. El listado 23.5 muestra el cdigo para todo el proceso.
Listado 23.5. Cmo llamar a un procedimiento almacenado con parmetros en ADO.NET
static void TestInsertWithSPStatement(string { // Establece la cadena de instruccin SQL string strSQLInsert = "[pc_insCustomers]"; customerID)
// Crea objetos OleDb OleDbConnection databaseConnection = new OleDbConnection(oleDbConnectionString); OleDbCommand insertCommand = new OleDbCommand(strSQLInsert, databaseConnection); // Estamos tratando con un procedimiento almacenado (es decir, // NO con una instruccin SQL) insertCommand.CommandType = CommandType.StoredProcedure; // Agregue cada parmetro (1 de 11) OleDbParameter param = insertCommand.Parameters.Add("@CustomerID_1", OleDbType.VarChar, 5 ) ; param.Direction = ParameterDirection.Input; param.Value = customerID; // Agregue cada parmetro (#2 de 11) param = insertCommand.Parameters.Add("@CompanyName_2", OleDbType.VarChar, 40); param.Direction = ParameterDirection.Input; param.Value = "Hungry Coyote Export Store"; // Agregue cada parmetro 3-10 // Etc. // Agregue cada parmetro (11 de 11) param = insertCommand.Parameters.Add("@Fax_11", OleDbType.VarChar, 24); param.Direction = ParameterDirection.Input; param.Value = "(503) 555-2376"; try
503
{ // Establezca la conexin de la base de datos databaseConnection.Open(); // Ejecute el comando SQL int numRows = insertCommand.ExecuteNonQuery(); // Informe de los resultados ConsoleWriteLine("Inserted {0} row(s).", numRows.ToString()); } catch (Exception e) { Console.WriteLine("****** Caught an exception: \n{0}", e.Message); } finally { databaseConnection.Close(); } }
504
La invocacin de este procedimiento almacenado es similar al cdigo usado para llamar al procedimiento almacenado que inserta una fila (vase el listado 23.3). Por supuesto, la direccin para los parmetros de salida se establece en ParameterDirection.Output. Tras ejecutar el procedimiento almacenado, puede usar la coleccin Parameters para recuperar los valores de los parmetros de salida, como muestra el listado 23.7.
Listado 23.7. Cmo recuperar un nico registro mediante parmetros de salida
static void TestSPWithOutParam(string customerID) { // Establezca las cadenas de la instruccin SQL string strSQLSelect = "[pc_getContact_ByCustomerID]"; // Cree objetos OleDb OleDbConnection databaseConnection = new OleDbConnection(oleDbConnectionString); OleDbCommand selectCommand = new OleDbCommand(strSQLSelect, databaseConnection); // Estamos tratando con un procedimiento almacenado (es decir, NO una instruccin SQL) selectCommand.CommandType = CommandType.StoredProcedure; // Agregue cada parmetro (1 de 3) OleDbParameter param = selectCommand.Parameters.Add("@CustomerID_ 1", OleDbType.VarChar, 5 ) ; param.Direction = ParameterDirection.Input; param.Value = customerID; // Agregue cada parmetro (2 de 3) param = selectCommand.Parameters.Add("@ContactName_2", OleDbType.VarChar, 30); param.Direction = ParameterDirection.Output; // Agregue cada parmetro (3 de 3) param = selectCommand.Parameters.Add("@ContactTitle_3", OleDbType.VarChar, 30); param.Direction = ParameterDirection.Output; try { // Establezca la conexin de la base de datos databaseConnection.Open(); // Ejecute el comando SQL selectCommand.ExecuteNonQuery(); // Informe de los resultados
505
string contactName = selectCommand.Parameters["@ContactName_2"].Value.ToString(); string contactTitle = selectCommand.Parameters["@ContactTitle_3"].Value.ToString(); Console.WriteLine("Contact name is {0}, title is {1}.", contactName, contactTitle); } catch (Exception e) { Console.WriteLine("****** Caught an exception:\n{0}", e.Message); } finally { databaseConnection.Close(); } }
Observe ahora los mtodos genricos de lectura de datos. El primero usa un objeto O l e D b D a t a R e a d e r . El objeto C o m m a n d tiene un mtodo ExecuteReader(), que devuelve un objeto OleDbDataReader. A continuacin puede usar el mtodo Read() para recorrer el contenido del DataReader. Read() devuelve T r u e cuando se encuentran datos durante la lectura y F a l s e en caso contrario. El listado 23.8 muestra cmo hacerlo. Observe que este ejemplo usa una instruccin SQL para acceder a un procedimiento almacenado slo para mostrar un modo alternativo de llamar a un procedimiento almacenado. Esto slo se hace con fines demostrativos, ya que es ms eficiente llamar a un procedimiento almacenado de la forma que se muestra en el listado 23.7.
Listado 23.8. Cmo recuperar un registro nico mediante DataReader
static void TestSelectWithDataReader(string customerID) { // Establezca las cadenas de instruccin SQL, dando por // sentado que customerID no contiene comillas string strSQLSelect = "EXEC [pc_getCustomer_ByCustomerID] @CustomerID_1='" + customerID + "'"; // Cree objetos OleDb OleDbConnection databaseConnection = new OleDbConnection(OleDbConnectionString); OleDbCommand selectCommand = new OleDbCommand(strSQLSelect, databaseConnection); // Estamos tratando con una instruccin SQL (es decir, NO con // un procedimiento almacenado) selectCommand.CommandType = CommandType.Text; try { // Establezca la conexin de la base de datos
506
databaseConnection.Open(); // Ejecute el comando SQL OleDbDataReader rowReader = selectCommand.ExecuteReader(); // Informe de los resultados if (rowReader.Read()) { string contactName = rowReader["ContactName"].ToString(); string contactTitle = rowReader["ContactTitle"].ToString(); Console.WriteLine("Contact name is {0}, title is {1}.", contactName, contactTitle); } else { Console.WriteLine("No rows found!"); } } catch (Exception e) { Console.WriteLine("****** Caught an exception:\n{0}", e.Message); } finally { databaseConnection.Close(); ) )
El otro mtodo genrico de recuperar datos es mediante el verstil objeto DataSet. Como el objeto DataSet fue diseado para ser usado independientemente de la fuente de datos que lo origin, no hay OleDbDataSet ni SqlDataSet, slo un DataSet. Un DataSet se usa en conjuncin con un DataAdapter. Un objeto DataAdapter se usa especficamente para almacenar datos (es decir, se usa OleDbDataAdaper) y contiene cuatro objetos de comando para realizar operaciones: InsertCommand SelectCommand UpdateCommand DeleteCommand
Tras seleccionar el objeto SelectCommand se puede emplear el mtodo Fill() para completar un DataSet. La siguiente seccin muestra cmo usar los otros tres comandos para modificar los datos que contiene un DataSet. Un DataSet contiene uno o ms objetos DataTable. Cada DataTable contie-
507
ne uno o ms objetos DataRow. Estos objetos DataRow se almacenan en la coleccin Rows del DataSet. Un objeto DataRow contiene uno o ms objetos DataColumn . Estos objetos DataColumn se almacenan en la coleccin Columns del DataRow. Las colecciones Rows y Columns son indizadas mediante el ndice y el nombre. De hecho, puede imaginar el objeto DataSet como una base de datos en memoria. El listado 23.9 muestra un programa de ejemplo que recupera un nico registro usando un objeto DataSet.
Listado 23.9. Cmo recuperar un registro nico mediante DataSet
static void TestSelectWithDataSet(string customerID) { // Establezca las cadenas de instruccin SQL string strSQLSelect = "EXEC [pc_getCustomer_ByCustomerID] @CustomerID_1='" + customerID + "'"; // Cree objetos OleDb OleDbConnection databaseConnection = new OleDbConnection(oleDbConnectionString); OleDbCommand selectCommand = new OleDbCommand(strSQLSelect, databaseConnection); OleDbDataAdapter dsCmd = new OleDbDataAdapter(); DataSet resultDataSet = new DataSet(); // Estamos tratando con una instruccin SQL (es decir, NO con un procedimiento almacenado) selectCommand.CommandType = CommandType.Text; try { // Establezca la conexin de la base de datos databaseConnection.Open(); // Ejecute el comando SQL dsCmd.SelectCommand = selectCommand; int numRows = dsCmd.Fill(resultDataSet, "Customers"); // Informe de los resultados if (numRows > 0) { string contactName = resultDataSet.Tables["Customers"]. Rows[0]["ContactName"].ToString(); string contactTitle = resultDataSet.Tables["Customers"]. Rows[0]["ContactTitle"].ToString(); Console.WriteLine ("Contact name is {0}, title is {1}.", contactName, contactTitle) ; } else { Console.WriteLine ("No rows found!");
508
} } catch { (Exception e)
Sin estas lneas, la posterior instruccin Update() funcionar mal porque InsertCommand no puede generarse automticamente. Las operaciones generadas automticamente UpdateCommand y DeleteCommand tienen los mismos requisitos. El listado 23.10 muestra todo el procedimiento en funcionamiento. Tras recuperar la estructura de la tabla Customers mediante un procedimiento almacenado (que aparece en el listado 23.11), se crea una nueva fila mediante una llamada al mtodo NewRow() . A continuacin se asigna un valor a cada columna de la nueva fila y esa fila recin completada se agrega a la coleccin Rows
509
mediante una llamada al mtodo AddRow() . Finalmente, este cambio se enva a la fuente de datos creada mediante una llamada del DataAdapter al mtodo Update() .
Listado 23.10. Cmo agregar un registro nico mediante un comando InsertCommand generado automticamente
static void Test.AutoInsertWithDataSet(string customerID) { // Establezca las cadenas de instruccin SQL, slo necesitamos los metadatos de modo que // ningn registro concuerde con este CustomerID. string strSQLSelect = "EXEC [pc_getCustomer_ByCustomerID] @CustomerID_1='???'"; // Crea objetos OleDb OleDbConnection databaseConnection = new OleDbConnection(oleDbConnectionString); OleDbCommand selectCommand = new OleDbCommand(strSQLSelect, databaseConnection); OleDbDataAdapter dsCmd = new OleDbDataAdapter(); // La siguiente lnea es clave para generar instrucciones automticamente!!! OleDbCommandBuilder custCB = new OleDbCommandBuilder(dsCmd); DataSet resultDataSet = new DataSet(); // Estamos tratando con una instruccin SQL (es decir, NO con un procedimiento almacenado) selectCommand.CommandType = CommandType.Text; try { // Establezca la conexin de la base de datos databaseConnection.Open(); // Ejecute el comando SQL dsCmd.SelectCommand = selectCommand; // Recupera la estructura de la tabla Customers int numRows = dsCmd.Fill(resultDataSet, "Customers"); // Cree una nueva fila DataRow workRow = resultDataSet.Tables["Customers"].NewRow(); // Complete los datos workrow workRow["CustomerID"] = customerID; // 1 work.Row["CompanyName"] = "Hungry Coyote Export Store"; // 2 workRow["ContactName"] = "Yoshi Latimer"; // 3 workRow["ContactTitle"] = "Sales Representative"; // 4 workRow["Address"] = "City Center Plaza 516 Main St."; // 5
510
workRow["City"] = "Elgin"; // 6 workRow["Region"] = "OR"; // 7 workRow["PostalCode"] = "97827"; // 8 workRow["Country"] = "USA"; // 9 workRow["Phone"] = "(503) 555-6874"; // 10 workRow["Fax"] = "(503) 555-2376"; // 11 resultDataSet.Tables["Customers"].Rows.Add(workRow); // Compatibilice los cambios con la fuente de datos dsCmd.Update(resultDataSet, "Customers"); // Informe de los resultados Console.WriteLine("Inserted 1
row."); } catch (Exception e) { Console.WriteLine(****** Caught an exception:\n{0}", e.Message); } finally { databaseConnection.Close(); } } Listado 23.11. Procedimiento almacenado para recuperar la estructura de la tabla Customers USE [Northwind] GO CREATE PROCEDURE [pc_getCustomer_ByCustornerID] (@CustomerID_1 [nchar](5)) AS SELECT [CustomerID], [CompanyName], [ContactName], [ContactTitle], [Address], [City], [Region], [PostalCode], [Country], [Phone], [Fax] FROM [Northwind].[dbo].[Customers] WHERE [CustomerID] = @CustomerID_1
Hay que definir mediante programacin un comando que realice la introduccin de datos. Ya aprendi a hacerlo con un procedimiento almacenado con
511
parmetros (por cierto, uno de los modos ms eficientes de realizar operaciones con datos) en el listado 23.5. Tras definir el comando y sus parmetros, todo lo que debe hacer es establecer el InsertCommand de DataAdapter, como muestra el listado 23.12. El resto del cdigo (crear una nueva fila, dar valores a las columnas, agregar la nueva fila y actualizar el cdigo fuente) es igual que el cdigo usado para un InsertCommand generado automticamente. Como se usa un enfoque igual a la creacin manual de un comando para UpdateCommand y DeleteCommand, la siguiente seccin slo muestra cmo usar los comandos generados automticamente.
Listado 23.12. Cmo agregar un registro nico mediante InsertCommand
static void TestDataSetInsertCommand(string customerID) { // Establezca las cadenas de instruccin SQL string strSQLSelect = "EXEC [pc_getCustomer_ByCustomerID] @CustomerID_1='???'"; string strSQLInsert = "[pc_insCustomers]"; // Cree objetos OleDb OleDbConnection databaseConnection = new OleDbConnection(oleDbConnectionString); OleDbCommand selectCommand = new OleDbCommand(strSQLSelect, databaseConnection); OleDbDataAdapter dsCmd = new OleDbDataAdapter(); DataSet resultDataSet = new DataSet(); // Estamos tratando con una instruccin SQL (es decir, NO con un procedimiento almacenado) selectCommand.CommandType = CommandType.Text; OleDbCommand insertCommand = new OleDbCommand(strSQLInsert, databaseConnection); insertCommand.CommandType = CommandType.StoredProcedure; insertCommand.CommandText = "[pc_insCustomers]"; insertCommand.Connection = databaseConnection; // Agregue cada parmetro (1 de 11) OleDbParameter param = insertCommand.Parameters.Add("@CustomerID_1", OleDbType.VarChar, 5 ) ; param.Direction = ParameterDirection.Input; param.SourceColumn = "CustomerID)"; // Agregue cada parmetro (2 de 11) param = insertCommand.Parameters.Add("@CompanyName_2", OleDbType.VarChar, 40); param.Direction = ParameterDirection.Input; param.SourceColumn = "CompanyName"; // Agregue cada parmetro 3-10 // Etc. // Agregue cada parmetro (11 de 11)
512
param = insertCommand.Parameters.Add("@Fax_11", OleDbType.VarChar, 24); param.Direction = ParameterDirection.Input; param.SourceColumn = "Fax"; try { // Establezca la conexin de la base de datos databaseConnection.Open(); // Ejecute el comando SQL dsCmd.SelectCommand = selectCommand; dsCmd.InsertCommand = insertCommand; int numRows = dsCmd.Fill(resultDataSet, "Customers"); // Cree una nueva fila DataRow workRow = resultDataSet.Tables["Customers"].NewRow(); // Complete los datos workrow workRow["CustomerID"] = customerID; // 1 workRow["CompanyName"] = "Hungry Coyote Export Store"; // 2 workRow["ContactName"] = "Yoshi Latimer"; // 3 workRow["ContactTitle"] = "Sales Representative"; // 4 workRow["Address"] = "City Center Plaza 516 Main St."; // 5 workRow["City"] = "Elgin"; // 6 workRow["Region"] = "OR"; // 7 workRow["PostalCode"] = "97827"; // 8 workRow["Country" ] = "USA"; // 9 workRow["Phone"] = "(503) 555-6874"; // 10 workRow["Fax"] = "(503) 555-2376"; // 11 resultDataSet.Tables["Customers"].Rows.Add(workRow); // Compatibilice los cambios con la fuente de datos dsCmd.Update(resultDataSet, "Customers"); // Informe de los resultados Console.WriteLine("Inserted 1
513
514
// Compatibilice los cambios con la fuente de datos dsCmd.Update(resultDataSet, "Customers"); Console.WriteLine("1 row updated!"); } else { Console.WriteLine("No rows found!"); } } catch (Exception e) { Console.WriteLine("****** Caught an exception:\n(0}", e.Message); } finally { databaseConnection.Close(); } }
Operaciones de borrado que afectan a las entidades de fila nica Por supuesto, no todas las operaciones que afectan a las entidades de fila nica deben realizarse mediante un DataSet. Tambin se puede usar un procedimiento almacenado o una instruccin SQL. El listado 23.14 muestra cmo usar una instruccin SQL para borrar una fila nica en una tabla.
Listado 23.14. Cmo borrar un registro nico mediante una instruccin SQL.
static void TestDeleteStatement(string customerID) { // Establezca las cadenas de instruccin SQL string strSQLDelete = "DELETE FROM Customers WHERE CustomerID = '" + customerID + "'"; // Cree objetos OleDb OleDbConnection databaseConnection = new OleDbConnection(oleDbConnectionString); OleDbCommand deleteCommand = new OleDbCommand(strSQLDelete, databaseConnection); // Estamos tratando con una instruccin SQL (es decir, NO con un procedimiento almacenado) deleteCommand.CommandType = CommandType.Text; try { // Establezca la conexin de la base de datos databaseConnection.Open();
515
// Ejecute el comando SQL int numRows = deleteCommand.ExecuteNonQuery(); // Informe de los resultados Console.WriteLine("Deleted {0} row(s).", numRows.ToString()); } catch (Exception e) { Console.WriteLine("****** Caught an exception:\n{0}", e.Message); } finally { databaseConnection.Close(); } }
El listado 23.15 finaliza con el estudio de los comandos generados automticamente, mostrndole cmo borrar una fila nica usando este sistema. Tras completar un DataSet, puede eliminar una fila con una llamada Delete(). Como siempre, se necesita una llamada Update() para fijar este cambio en la fuente de datos.
Listado 23.15. Cmo borrar un registro nico con un DeleteCommand generado automticamente. static void TestAutoDeleteWithDataSet(string customerID) { // Establezca las cadenas de instruccin SQL string strSQLSelect = "EXEC [pc_getCustomer_ByCustomerID] @CustomerID_1='" + customerID + "'"; // Cree objetos OleDb OleDbConnection databaseConnection = new OleDbConnection(oleDbConnectionString); OleDbCommand selectCommand = new OleDbCommand(strSQLSelect, databaseConnection); OleDbDataAdapter dsCmd = new OleDbDataAdapter(); // La siguiente lnea es clave para generar instrucciones automticamente!!! OleDbCommandBuilder custCB = new OleDbCommandBuilder(dsCmd); DataSet resultDataSet = new DataSet(); // Estamos tratando con una instruccin SQL (es decir, NO con un procedimiento almacenado) selectCommand.CommandType = CommandType.Text; try { // Establezca la conexin de la base de datos databaseConnection.Open();
516
// Ejecute el comando SQL dsCmd.SelectCommand = selectCommand; int numRows = dsCmd.Fill(resultDataSet, "Customers"); // Informe de los resultados if (numRows > 0) { resultDataSet.Tables["Customers"].Rows[0].Delete!); // Compatibiliza los cambios con la fuente de datos dsCmd.Update(resultDataSet, "Customers"); Console.WriteLine("1 row deleted!"); } else { Console.WriteLine("No rows found!"); } } catch (Exception e) { Console.WriteLine("****** Caught an exception:\n{0}", e.Message); } finally { databaseConnection.Close(); } }
517
[ContactName], [ContactTitle], [Address], [City], [Region], [PostalCode], [Country], [Phone], [Fax] FROM [Northwind].[dbo].[Customers]
// Cree objetos OleDb OleDbConnection databaseConnection = new OleDbConnection(oleDbConnectionString); OleDbCommand selectCommand = new OleDbCommand(strSQLSelect, databaseConnection); // Estamos tratando con procedimientos almacenados (es decir, NO con una instruccin SQL) selectCommand.CommandType = CommandType.StoredProcedure; try { // Establezca la conexin de la base de datos databaseConnection.Open(); // Ejecuta el comando SQL OleDbDataReader rowReader = selectCommand.ExecuteReader(); // Informe de los resultados while (rowReader.Read()) { string contactName = rowReader["ContactName"].ToString(); string contactTitle = rowReader["ContactTitle"].ToString(); Console.WriteLine("Contact name is {0}, title is {1}.", contactName, contactTitle); } } catch (Exception e) { Console.WriteLine("****** Caught an exception:\n{0}", e.Message); } finally
518
{ databaseConnection.Close(); } }
El uso de un objeto DataSet para recuperar un conjunto de registros tambin le resultar familiar (vase el listado 23.9). De nuevo, todo lo que tiene que hacer es aadir una iteracin para capturar todos los registros recuperados. Esto puede hacerse con un bucle for, como muestra el listado 23.18.
Listado 23.18. Cmo recuperar un conjunto de registros con DataSet.
static void TestSelectManyWithDataSet(string customerID) { // Establezca las cadenas de instruccin SQL string strSQLSelect = "[pc_getCustomers]"; // Cree objetos OleDb OleDbConnection databaseConnection = new OleDbConnection(oleDbConnectonString); OleDbCommand selectCommand = new OleDbCommand(strSQLSelect, databaseConnection); OleDbDataAdapter dsCmd = new OleDbDataAdapter(); DataSet resultDataSet = new DataSet(); // Estamos tratando con procedimientos almacenados (es decir, NO con una instruccin SQL) selectCommand.CommandType = CommandType.StoredProcedure; try { // Establezca la conexin de la base de datos databaseConnection.Open(); // Ejecute el comando SQL dsCmd.SelectCommand = selectCommand; int numRows = dsCmd.Fill(resultDataSet, "Customers"); // Informe de los resultados if (numRows > 0) { numRows = resultDataSet.Tables["Customers"].Rows.Count for (int i=0; i <= numRows - 1; i++) { string contactName = resultDataSet.Tables["Customers"].Rows[i]["ContactName"]. ToString(); string contactTitle = resultDataSet.Tables["Customers"].Rows[i]["ContactTitle"]. ToString();
519
{1}.",
} else { Console.WriteLine("No rows found!"); } } catch (Exception e) { Console.WriteLine("****** Caught an exception:\n(0)", e.Message); } finally { databaseConnection.Close(); } }
520
OleDbCommandBuilder custCB = new oleDbCommandBuilder(dsCmd); DataSet resultDataSet = new DataSet(); // Estamos tratando con una instruccin SQL (es decir, NO con un procedimiento almacenado) selectCommand.CommandType = CommandType.Text; try { // Establezca la conexin de la base de datos databaseConnection.Open(); // Ejecute el comando SQL dsCmd.SelectCommand = select Command; int numRows = dsCmd.Fill(resultDataSet, "Customers"); // Cree una nueva primera fila DataRow workRow = resultDataSet.Tables["Customers"].NewRow(); // Complete los datos workrow workRow["CustomerID"] = customerID1; // 1 workRow["CompanyName"] = "Hungry Coyote Export Store"; // 2 workRow["ContactName"] = "Yoshi Latimer"; // 3 workRow["ContactTitle"] = "Sales Representative"; // 4 workRow["Address"] = "City Center Plaza 516 Main St."; // 5 workRow["City"] = "Elgin"; // 6 workRow["Region"] = "OR"; // 7 workRow["PostalCode"] = "97827"; // 8 workRow["Country"] = "USA"; // 9 workRow["Phone"] = "(503) 555-6874"; // 10 workRow["Fax"] = "(503) 555-2376"; // 11 resultDataSet.Tables["Customers"].Rows.Add(workRow); // Cree una nueva segunda lnea workRow = resultDataSet.Tables["Customers"].NewRow(); // Complete los datos work row workRow["CustomerID"] = customerID2; // 1 workRow["CompanyName"] = "Hungry Coyote Export Store"; // 2 workRow["ContactName"] = "Yoshi Latimer"; // 3 workRow["ContactTitle"] = "Sales Representative"; // 4 workRow["Address"] = "City Center Plaza 516 Main St."; // 5 workRow["City" ] = "Elgin"; // 6 workRow["Region"] = "OR"; // 7 workRow["PostalCode"] = "97827"; // 8 workRow["Country"] = "USA"; // 9 workRow["Phone"] = "(503) 555-6874"; // 10
521
// 11
resultDataSet.Tables["Customers"].Rows.Add(workRow); // Compatibilice los cambios con la fuente de datos dsCmd.Update(resultDataSet, "Customers"); // Informe de los resultados Console.WriteLine("Inserted 2
522
ShipRegion, ShipPostalCode, ShipCountry FROM Orders SELECT TOP 5 OrderID, ProductID, UnitPrice, Quantity, Discount FROM [Order Details] GO
Para recuperar estos datos, emplee un DataReader de la misma forma que hizo para recuperar conjuntos de filas. Sin embargo, si compara el cdigo del listado 23.21 con el cdigo del listado 23.17, comprobar que hay un bucle adicional alrededor de la iteracin de filas. Este bucle adicional termina cuando NextResults() es F a l s e . Esto es lo que se debe saber para recuperar conjuntos de filas mltiples.
Listado 23.21. Cmo recuperar varios conjuntos de datos con DataReader
static void TestSelectHierWithDataReader() { // Establezca las cadenas de instruccin SQL string strSQLSelect = "[pc_getOrdersAndDetails]"; // Cree objetos OleDb OleDbConnection databaseConnection = new OleDbConnection(oleDbConnectionString); OleDbCommand selectCommand = new OleDbCommand(strSQLSelect, databaseConnection); // Estamos tratando con procedimientos almacenados (es decir, NO con una instruccin SQL) selectCommand.CommandType = CommandType.StoredProcedure; try { // Establezca la conexin de la base de datos databaseConnection.Open(); // Ejecute el comando SQL OleDbDataReader rowReader = selectCommand.ExecuteReader(); // Informe de los resultados for ( ; ; ) { while(rowReader.Read()) { string row = "";
523
for (int i=0; i <= rowReader.FieldCount - 1; i++) { row = row + rowReader[i] + ", "; } Console.WriteLine("Row is {0}", row.Substring(0, row.Length - 2)); } if (!rowReader.NextResult()) break; else Console.WriteLine("Next Results:"); ( } catch (Exception e) { Console.WriteLine("****** Caught an exception:\n{0}", e.Message); } finally { databaseConnection.Close(); } }
En la ltima seccin del cdigo est uno de los puntos fuertes del objeto DataSet: la recuperacin de datos relacionados. Como el DataSet fue diseado para trabajar como una base de datos en memoria, tiene toda la funcionalidad necesaria para tratar con las relaciones primarias y secundarias. Los siguientes dos listados ejemplifican la recuperacin de datos. El listado 23.22 muestra cmo se recuperan datos relacionados mediante una instruccin SQL y el listado 23.23 muestra cmo recuperar datos relacionados usando un objeto D a t a S e t . El listado 23.23 demuestra cmo tratar con este tipo de relaciones. De nuevo, se usa un procedimiento almacenado que devuelve datos de dos instrucciones Select. Las instrucciones Select estn relacionadas y se quiere conseguir el mismo resultado en ADO.NET como si se recuperasen los datos con una instruccin SQL igual a la que aparece en el listado 23.22.
Listado 23.22. Cmo recuperar datos con instrucciones SQL
SELECT Orders.OrderID, Orders.CustomerID, Orders.EmployeeID, Orders.OrderDate, Orders.RequiredDate, Orders.ShippedDate, Orders.ShipVia, Orders.Freight, Orders.ShipName, Orders.ShipAddress,
524
Orders.ShipCity, Orders.ShipRegion, Orders.ShipPostalCode, Orders.ShipCountry, [Order Details].ProductID, [Order Details].UnitPrice, [Order Details].Quantity, [Order Details].Discount FROM Orders INNER JOIN [Order Details] ON Orders.OrderID = [Order Details].OrderID
En la instruccin try del listado 23.23 se empieza asignando las tablas de datos a las tablas de origen usadas en la consulta SQL. El cdigo que le sigue, completando el DataSet, es el cdigo habitual incluido ms abajo. A continuacin viene la parte en la que se define la relacin entre las dos tablas. Al establecer las relaciones entre las claves externas en un sistema de gestin de bases de datos relacionales (RDBMS) se necesitan las claves primarias, y la base de datos en memoria no es diferente. La propiedad PrimaryKey recibe una matriz de objetos DataColumn. Tras establecer las claves primarias, se puede definir una relacin. El primer parmetro es el nombre de la relacin, que se usar ms tarde para recuperar los registros secundarios. A modo de demostracin, el ejemplo recupera slo la primera fila principal. A continuacin recupera todas las filas secundarias asociadas mediante el mtodo GetChildRows() usando el nombre de la relacin. A continuacin se puede usar un bucle que recorra la matriz de objetos DataRow para mostrar las filas secundarias.
Listado 23.23. Cmo recuperar datos relacionados con DataSet
static void TestSelectHierWithDataSet() ( // Establezca las cadenas de instruccin SQL string strSQLSelect = "[pc_getOrdersAndDetails]"; // Cree objetos OleDb OleDbConnection databaseConnection = new OleDbConnection(oleDbConnectionString); OleDbCommand selectCommand = new OleDbCommand(strSQLSelect, databaseConnection); OleDbDataAdapter dsCmd = new OleDbDataAdapter(); DataSet resultDataSet = new DataSet(); // Estamos tratando con procedimientos almacenados (es decir, NO con una instruccin SQL) selectCommand.CommandType = CommandType.StoredProcedure; try { dsCmd.TableMappings.Add("Orders", "Orders"); dsCmd.TableMappings.Add("Orders1", "Order Details");
525
// Establezca la conexin de la base de datos databaseConnection.Open(); // Ejecute el comando SQL dsCmd.SelectCommand = selectCommand; // Como no hay tablas en el DataSet antes de invocar el // mtodo Fill, // el OleDbDataAdapter crear automticamente las tablas // para el DataSet // y las completar con los datos devueltos. Si crea las // tablas antes de ejecutar // el mtodo FillDataSet, el OleDbDataAdapter simplemente // completar las tablas existentes. int numRows = dsCmd.Fill(resultDataSet, "Orders"); // Reduzca el nmero de puntos evitando las referencias a // las tablas DataTable orderTable = resultDataSet.Tables["Orders"]; DataTable detailsTable = resultDataSet.Tables["Order Details"]; // Establezca la clave primaria de las tablas orderTable.PrimaryKey = new DataColumn[] { orderTable.Columns["OrderID"] }; detailsTable.PrimaryKey = new DataColumn[] { detailsTable.Columns["OrderID"], detailsTable.Columns["Product ID"] }; // Establezca la relacin de clave externa entre las tablas resultDataSet.Relations.Add(new DataRelation("Order_Detail", new DataColumn[] { orderTable.Columns["OrderID"] }, new DataColumn[] { detailsTable.Columns["OrderID"] })); // Informe de los resultados // Muestre el pedido DataRow orderRow = orderTable.Rows[0]; Console.WriteLine("Order ID is {0}, date is {1}, Ship To is {2}.", orderRow["OrderID"], orderRow["OrderDate"], orderRow["ShipName"]); // Recupere las filas secundarias para el pedido usando el // nombre de la relacin DataRow[] detailRows = orderRow.GetChildRows("Order_Detail"); // Hacer algo con la coleccin de filas secundarias DataRow detailRow; for (int i=0; i <= detailRows.Length - 1; i++) { // Hacer algo con la fila detail detailRow = detailRows[i]; Console.WriteLine("Product ID is {0}, Quantity is {1}.",
526
detailRow["ProductID"], detailRow["Quantity"]); } } catch (Exception e) { Console.WriteLine("****** Caught an exception:\n{0}", e.Message); } finally { databaseConnection.Close(); } }
Resumen
Este captulo describe todos los tipos de operaciones que puede realizar en ADO.NET. Tambin se muestra lo sencillos y verstiles que son los objetos en el espacio de nombres System.Data, incluyendo el potente objeto DataSet, que funciona como una base de datos en memoria. Tambin describe cmo devolver filas con entidades nicas y cmo borrar y actualizar operaciones. Tenga en cuenta que la arquitectura de proveedor ADO.NET se refleja en las clases .NET Framework que admiten ADO.NET. Podemos usar las clases Sql para aprovechar el proveedor SQL Server de ADO.NET. Si su aplicacin slo va a admitir SQL Server, debe usar las clases SQL, porque el proveedor SQL Server para ADO.NET es ms eficiente y funciona mejor que el proveedor OLE DB cuando se trata de SQL Server. Si su aplicacin necesita incluir compatibilidad con otras bases de datos que no son SQL Server, es aconsejable elegir clases OleDb.
527
24 Cmo trabajar
con archivos y con el registro de Windows
Las operaciones de archivo son algo a lo que todo programador debe enfrentarse en un momento u otro. La clase System.IO contiene una cantidad ingente de mtodos para leer y escribir en y desde archivos. Esta clase simplifica la E/S de archivos y su manipulacin y brinda un gran control de acceso a archivos. De forma parecida a la E/S del pasado, el acceso al registro de Windows siempre ha sido una tarea muy pesada. Esta tarea requera muchas llamadas al API que reducan el rendimiento de la aplicacin y solan obligar al programador a escribir sus propias clases contenedoras para estas operaciones. Con .NET todo esto cambi. En este captulo aprender a leer y escribir datos en y desde archivos. El captulo estudia las operaciones de E/S que operan con texto normal y con datos binarios. Tambin estudia algunas operaciones de archivos tiles, como mover, renombrar y eliminar archivos. Por ltimo, aprender a supervisar el sistema de archivos para buscar cambios en archivos especficos y seguir recorriendo el registro de Windows.
529
archivos. En este captulo examinaremos dos de estas clases para mejorar su conocimiento de la E/S de archivos.
Acceso binario
Las clases BinaryReader y BinaryWriter son compatibles con el acceso binario a archivos. Estas clases permiten el acceso a archivos binarios y las operaciones binarias hacia y desde secuencias. Como se usan secuencias, las clases pueden ser muy flexibles y no tienen que tratar con detalles, como la posicin de la secuencia o el acceso a la misma. El siguiente apartado examina la clase de acceso BinaryWriter.
BinaryWriter
La clase BinaryWriter permite escribir tipos de datos primitivos en secuencias y, con el uso de las subclases, se pueden reemplazar los mtodos de esta clase y cumplir con los requisitos de la codificacin nica de caracteres. Como esta clase usa una secuencia subyacente, hay que trabajar con muy pocos mtodos y propiedades. La tabla 24.1 contiene las cinco propiedades y mtodos bsicos que ms usar para trabajar con la clase BinaryWriter.
Tabla 24.1. Miembros bsicos de la clase BinaryWriter
Funcin Permite el acceso a la secuencia subyacente BinaryWriter Cierra la clase BinaryWriter y la secuencia subyacente, eliminando todas las operaciones de escritura pendientes. Escribe todos los datos almacenados en el buffer en la secuencia subyacente y luego limpia los buffers. Establece la posicin actual dentro de la secuencia en uso. Este mtodo sobrecargado escribe un valor en la secuencia en uso. Actualmente admite 18 variaciones de tipos de datos.
Flush
Mtodo
Seek Write
Mtodo Mtodo
Para escribir datos en un archivo, antes hay que crear una secuencia de archiv o . Seguidamente se puede instanciar una nueva clase BinaryWriter pasndole la secuencia. Tras crear esta clase BinaryWriter, slo hay que llamar a su mtodo Write() y pasarle los datos que se deben escribir, como se puede ver en el listado 24.1.
530
Listado 24.1. Cmo escribir datos en una secuencia de archivo mediante la clase BinaryWriter static void Main(string[] args) { FileStream myFStream = new FileStream ("c:\\TestFile.dat", FileMode.OpenOrCreate, FileAccess.ReadWrite); BinaryWriter binWrit = new BinaryWriter(myFStream); string testString = "This is a test string."; binWrit.Write(testString); binWrit.Close(); myFStream.Close(); }
Al acabar con la clase BinaryWriter, hay que asegurarse de cerrarla y de cerrar la secuencia. Si no se cierra la clase BinaryWriter o la clase FileStream, se puede producir una prdida de datos.
BinaryReader
La clase BinaryReader, al igual que la clase BinaryWriter, se basa en un objeto FileStream para acceder a los archivos. Para comprender la clase BinaryReader, examine la aplicacin del listado 24.2, que lee la informacin de la cabecera de un archivo de mapa de bits. A partir de esta informacin binaria, se puede determinar el tamao horizontal y vertical del archivo de imagen, adems de su profundidad de color en bits.
Listado 24.2. Aplicacin BitmapSize using System; using System.IO; namespace BitmapSize { class Class1 { static void Main(string[] args) { long bmpWidth = 0; long bmpHeight = 0; int bmpPlanes = 0; int bmpBitCount = 0; string [] cma = Environment.GetCommandLineArgs(); if (cma.GetUpperBound(0) >= 1) { FileStream myFStream = new FileStream(cma[1], FileMode.Open, FileAccess.Read); BinaryReader binRead = new BinaryReader(myFStream); binRead.BaseStream.Position=0x12; bmpWidth = binRead.ReadInt32();
531
bmpHeight = binRead.ReadInt32(); bmpPlanes = binRead.ReadInt16(); bmpBitCount = binRead.ReadInt16(); Console.WriteLine("[{0}] {1}x{2} {3}bit", cma[1], bmpWidth, bmpHeight, bmpBitCount); binRead.Close(); myFStream.Close(); } } } }
Lo primero que se debe hacer en esta aplicacin es declarar algunas variables para que contengan la informacin que se lee desde el archivo de mapa de bits y, a continuacin, hay que almacenar todos los argumentos de lnea de comandos en una matriz de cadenas. En nuestro ejemplo se almacenan los argumentos de lnea de comandos en una matriz de cadenas porque uno de estos argumentos determina qu archivo debe procesarse. El elemento uno de la matriz de cadenas debe contener el nombre del archivo que debe procesarse. Tras determinar si existe un argumento de lnea de comandos, se puede crear un objeto FileStream pasando el nombre del archivo y el modo que quiere usar para abrir el archivo, como se muestra a continuacin:
FileStream myFStream = new FileStream(cma[1], FileAccess.Read); FileMode.Open,
Al igual que la clase BinaryWriter, se crea el objeto BinaryReader y se pasa el objeto FileStream que se quiere usar. En este punto ya est preparado para leer un archivo usando el modo binario. Tras examinar el diseo del archivo de mapa de bits, se sabe que la informacin a obtener empieza en la posicin 18 (posicin hexadecimal 12) del archivo. Al usar el objeto de la clase BinaryReader BaseStream , se puede acceder directamente al objeto FileStream. A partir de aqu, establece la propiedad Position del objeto a 0x12, que se posiciona en el archivo en la posicin desde la que se quiere leer. Cuando el puntero del archivo est en posicin, hay que leer dos valores long en el archivo, seguidos por dos valores enteros. Un valor long necesita cuatro bytes, de modo que se usa el mtodo ReadInt32 dos veces para recuperar los valores. A continuacin, se usa el mtodo ReadInt16 para recuperar los dos datos de tipo int del archivo. Observe en la tabla 24.2 la lista de los mtodos ms usados de la clase BinaryReader. Una vez que se ha recuperado la informacin del archivo, slo queda mostrar en la consola los valores almacenados. Tras compilar esta aplicacin, vaya a la ventana de consola y prubela con una imagen de mapa de bits, como muestra la figura 24.1.
532
Tabla 24.2. Mtodos ms usados dentro de la clase BinaryReader Mtodo Close PeekChar Descripcin Cierra el objeto BinaryReader y la secuencia base Lee el siguiente byte disponible de la secuencia pero no hace avanzar la posicin del puntero en el archivo Lee un valor booleano (True/False) de la secuencia Lee un solo byte de la secuencia. Tambin existe un mtodo ReadBytes para especificar el nmero de bytes que se deben leer. Lee un solo valor char de la secuencia. Tambin existe un mtodo ReadChars para especificar el nmero de c h a r s que se deben leer. Lee un valor entero (2 bytes) Lee un valor long (4 bytes) Lee un entero de ocho bytes con signo
ReadBoolean ReadByte
ReadChar
c:\ C:\WINDOWS\System32\cmd.exe
Figura 24.1. Al usar BinaryReader, puede observar el tamao de la imagen de un archivo de mapa de bits
533
archivo concreto, un grupo de archivos, un directorio o toda una unidad para buscar varios eventos, incluyendo los cambios en los archivos, eliminaciones de archivos, creaciones de archivos y cambios de nombre de archivos. Se empieza usando el objeto FileSystemWatcher en uno de estos dos modos. Se puede crear un objeto FileSystemWatcher usando cdigo y luego creando mtodos para controlar los diferentes eventos, o se puede usar el mtodo ms sencillo. El cuadro de herramientas de la interfaz de desarrollo de Visual Studio .NET tambin contiene un objeto FileSystemWatcher que se puede insertar en el proyecto haciendo doble clic sobre l. Las aplicaciones, como Microsoft Word y Microsoft Excel, supervisan los archivos que se estn usando por si se producen cambios en ellos desde fuera. A continuacin aparecer la opcin de volver a abrir el archivo de modo que pueda observar todos los cambios que se han hecho en l. Para explorar las caractersticas del objeto FileSystemWatcher se construye una aplicacin que supervisa todos los archivos de texto en la raz de la unidad C: y muestra la informacin importante en la ventana de la aplicacin.
Stop
Start
Figura 24.2. Arrastre y coloque controles en la interfaz File monitor como se muestra aqu
Debe agregar otro objeto ms al proyecto: FileSystemWatcher. Seleccione la ficha Componentes del cuadro de herramientas, como muestra la figura 24.3. Haga doble clic en FileSystemWatcher para agregar una instancia de este objeto al proyecto.
534
Figura 24.3. La ficha Componentes del cuadro de herramientas contiene el objeto FileSystemWatcher
Seleccione el componente fileSystemWatcher1 situado inmediatamente debajo de Form1 en el diseador de formularios. La propiedad EnableRaisingEvents activa el objeto y le permite empezar a buscar eventos del sistema de archivos. Como tiene los botones Start y Stop para conseguirlo, debe asegurarse de que el objeto no se activa cuando se ejecuta la aplicacin. Cambie la propiedad EnableRaisingEvents a F a l s e . La propiedad Filter permite asignar una mscara de archivo o un archivo que se debe observar. Como queremos ver todos los archivos de testo, escriba *.txt en la propiedad Filter. Por ltimo, necesita definir la carpeta para supervisar los archivos. Escriba C:\ en la propiedad Path para asegurarse de que slo examina los archivos que se encuentran en la raz de la unidad C:. Se puede supervisar toda la unidad asignando a la propiedad IncludeSubdirectories el valor T r u e , pero es probable que esto reduzca el rendimiento del sistema. Haga doble clic en el botn Start en Form1 y aada el cdigo del listado 24.3 al controlador de eventos Click.
Listado 24.3. Evento clic del botn Start private void btnStart_Click(object sender, System.EventArgs e) { fileSystemWatcher1.EnableRaisingEvents = true; btnStop.Enabled = true; btnStart.Enabled = false; }
535
El cdigo anterior asigna a la propiedad EnableRaisingEvents el valor T r u e , lo que activa el objeto FileSystemWatcher. A continuacin desactive el botn Start para que no se pueda volver a hacer clic en l y active el botn Stop. Ahora debe agregar algo de cdigo al botn Stop para desactivar FileSystemWatcher. Agregue el cdigo del listado 24.4 al evento Click del botn Stop.
Listado 24.4. Evento clic del botn Stop
private void btnStop_Click(object sender, System.EventArgs e) { fileSystemWatcher1.EnableRaisingEvents = false; btnStop.Enabled = false; btnStart.Enabled = true; }
FileSystemWatcher ya est operativo, pero debe agregar los controladores de eventos para capturar todos los eventos de archivos. Regrese al editor de formularios haciendo doble clic en Form1 en el Explorador de soluciones y haciendo clic en el objeto fileSystemWatcher1. En la ventana Propiedades, haga clic en el icono Eventos de la barra de tareas. Al hacer clic en los eventos que aparecen en la ventana, aparecer el editor de cdigo para que pueda modificar esos controladores de eventos. El listado 24.5 contiene el cdigo que debe introducir en los cuatro eventos.
Listado 24.5. Controladores de eventos FileSystemWatcher
private void fileSystemWatcher1_Deleted(object sender, System.IO.FileSystemEventArgs e) { listBox1.Items.Add("[" + e.Name + "] D e l e t e d " ) ; } private void fileSystemWatcher1_Renamed(object sender, System.IO.RenamedEventArgs e) { listBox1.Items.Add("[" + e.OldName + "] Renamed to " + e.Name); } private void fileSystemWatcher1_Changed(object sender, System.IO.FileSystemEventArgs e) { listBox1.Items.Add("[" + e.Name + "] C h a n g e d " ) ; } private void fileSystemWatcher1_Created(object sender, System.IO.FileSystemEventArgs e) { listBox1.Items.Add("[" + e.Name + "] Created"); }
536
Estos controladores de eventos son bastante simples: solamente muestran un mensaje en el cuadro de lista cuando se produce una operacin. Antes de comprobar esta aplicacin, es importante tener en cuenta algunas cosas. Como puede ver en cada una de estas funciones, los eventos Changed, Created y Deleted muestran los mismos datos; por tanto, tienen la misma firma. Lo extrao aqu es el evento Renamed. Como tres de los cuatro eventos pasan los mismos datos al procedimiento del evento, es posible usar un controlador de eventos que controle estos tres eventos, pero seguir necesitando que un controlador distinto se haga cargo del evento Renamed. Pulse F5 para ejecutar el programa y empezar a examinarlo como se indica a continuacin: 1. Cuando el programa se abra, haga clic en el botn Start. La aplicacin est supervisando la raz de la unidad C: buscando cambios en los archivos de texto. 2. Abra el Explorador de Windows. Haga clic con el botn derecho en la ventana del explorador y seleccione Nuevo>Documento de texto para crear un nuevo documento. 3. En el nuevo documento, observar una entrada en el cuadro de lista de su aplicacin File Monitor que indica que se ha creado un archivo. Abra este nuevo archivo de texto, agrguele algo de texto y gurdelo. De nuevo, ver entradas en la ventana de registro de la aplicacin. 4. Ahora intente renombrar el archivo y borrarlo a continuacin. Los resultados deberan ser similares a los que aparecen en la figura 24.4.
File Monitor
[new text document txt] Created [new text document txt] Changed [new text document txt] Changed [new text document txt] Renamed to brians.txt [brians.txt] Deleted
Stop
Start
Figura 24.4. Su aplicacin File monitor muestra la actividad del archivo en tiempo real
Debe ser consciente de que el evento Changed puede desencadenarse varias veces, porque FileSystemWatcher est examinando varias caractersticas del archivo. Si el tamao del archivo cambia, se desencadena el evento
537
Changed. Si la fecha de modificacin y la marca de hora del archivo cambian, el evento Changed se desencadena de nuevo y as sucesivamente. No se desanime si un evento se desencadena ms de una vez. Por lo general no es un problema de la aplicacin sino una garanta de que se ha establecido el NotifyFilters correcto.
Como est creando el control en tiempo de ejecucin, no puede ir a la ventana Propiedades y asignar valores a las diferentes propiedades segn sea necesario. En cambio, estas tareas se controlan desde dentro del evento Click del botn Start. Tras establecer las propiedades como en el anterior ejemplo del listado 24.6, debe crear controladores de eventos y dirigirlos a las funciones adecuadas. El listado 24.7 muestra el listado actualizado del evento Click del botn Start.
Listado 24.7. Cmo crear declaraciones de controladores de eventos en el evento Click del botn Start private void btnStart_Click(object sender, System.EventArgs e) { MyFileWatcher.Path = "c:\\"; MyFileWatcher.Filter = "*.txt"; MyFileWatcher.IncludeSubdirectories = false; MyFileWatcher.EnableRaisingEvents = true; this.MyFileWatcher.Renamed += new System.IO.RenamedEventHandler(this.MyFileWatcher_Renamed); this.MyFileWatcher.Changed += new System.IO.FileSystemEventHandler(this.MyFileWatcher_Changed); btnStart.Enabled = false; btnStop.Enabled = true; }
A continuacin, agregue cdigo al botn Stop y cree controladores de eventos para FileSystemWatcher. Los nombres de funcin para estos controladores de eventos deben coincidir con la declaracin que coloc en el evento click de
538
los botones Start. El listado 24.8 contiene el evento Stop Click y los controladores de eventos para los eventos Changed y Renamed.
Listado 24.8. Cmo crear controladores de evento para el objeto FileSystemWatcher
private void btnStop_Click(object sender, System.EventArgs e) { MyFileWatcher.EnableRaisingEvents = false; btnStop.Enabled = false; btnStart.Enabled = true; } private void MyFileWatcher_Changed(object sender, System.IO.FileSystemEventArgs e) { listBox1.Items.Add("[" + e.FullPath + "] Changed"); } private void MyFileWatcher_Renamed(object sender, System.IO.RenamedEventArgs e) { listBox1.Items.Add("[" + e.OldName + "] renamed to " + e.Name); }
539
Este cdigo usa el mtodo G e t C o m m a n d L i n e A r g s de la clase Environment para recuperar los argumentos de lnea de comandos que se pasan a la aplicacin. Si el nmero de argumentos no es igual a 2, simplemente muestra un mensaje de error, como se puede ver en la parte inferior del listado 24.9, y sale. En caso contrario, los argumentos de la lnea de comandos se almacenan en la matriz de cadenas cla para ser usados ms tarde. Al usar la clase FileInfo, debe crear en primer lugar un objeto FileInfo y pasarle el nombre del archivo con el que est trabajando. Al usar esta aplicacin, el argumento de la primera lnea de comandos es el nombre del archivo fuente. Como ste se almacena en el elemento 1 de la matriz de cadenas, simplemente pselo al objeto FileInfo. Para copiar un archivo usando esta clase, simplemente llame a su mtodo CopyTo junto con el nombre del archivo de destino (que est en el elemento 2 de la matriz de cadenas) y un valor booleano que indique si se debe sobrescribir un archivo con el mismo nombre. Abra el intrprete de comandos para probar este programa despus de compilarlo, como muestra la figura 24.5. Como puede ver, se agreg algo inesperado al programa. Una vez que el mtodo CopyTo se haya completado, se muestra un mensaje en la consola que indica que la operacin ha concluido y se muestran el nmero de bytes que se han copiado, gracias a la propiedad Length de la clase FileInfo.
540
tiene un valor en su atributo R e a d - O n l y , obtendr una excepcin. El ejemplo del listado 24.10 crea una implementacin de C# del comando D e l e t e de archivos. Despus de borrar el archivo, la utilidad muestra el nombre del archivo eliminado y sus atributos.
c:\ C:\WINDOWS\System32\cmd.exe C:\>cp xp.bmp xpcopy.bmp Copied 950814 bytes". C:\>
Cree un nuevo proyecto de aplicacin de consola de C# llamado rm e introduzca el cdigo del listado 24.10.
Listado 24.10. Uso de la clase FileInfo para eliminar archivos fcilmente using System; using System.IO; namespace rm { class Class1 { static void Main(string[] args) { string [] cla = Environment.GetCommandLineArgs(); if (cla.GetUpperBound(0) == 1) { FileInfo fi = new FileInfo(cla[1]); fi.Delete(); Console.WriteLine("File : " + cla[1]); Console.WriteLine("Attributes: " + fi.Attributes.ToString()); Console.WriteLine("File Deleted..."); } else Console.WriteLine("Usage: rm <filename>"); } } }
541
Como en los anteriores ejemplos, est almacenando los argumentos de lnea de comandos dentro de una matriz de cadenas. Si esa matriz no contiene el nmero correcto de elementos, solamente se mostrar un mensaje de error y finalizar. TRUCO: Con el mtodo Delete() de la clase FileSystemInfo puede eliminar directorios, adems de archivos. Tras invocar al mtodo Delete() de la clase FileInfo, puede mostrar al usuario el nombre del archivo y sus atributos, indicando que ha sido eliminado. Mediante la propiedad Attributes, puede determinar de un modo seguro, antes de eliminar el archivo, si tiene asignado su atributo Read-Only. En ese caso, puede avisar al usuario y/o eliminar el atributo Read-Only usando la propiedad Attributes junto con el enumerador FileAttributes. Una vez que su programa haya sido compilado, abra un intrprete de comandos y prubelo. Simplemente escriba rm seguido del nombre del archivo que quiere eliminar. Los resultados deberan ser similares a los de la figura 24.6.
c:\ C:\WINDOWS\System32\cmd.exe C:\>rm TempFile.dat File : TempFile.dat Attributes: Archive File Deleted... C:\>
Figura 24.6. El mtodo Delete() de la clase FileInfo muestra los atributos del archivo eliminado
542
traslad. Ninguna de estas indicaciones tiene un uso prctico, excepto para mostrar cmo se pueden obtener algunos atributos, como la hora en que se cre el archivo, mediante la propiedad CreationTime. Cree una nueva aplicacin de consola C# y llame al proyecto mv, como el comando de UNIX. El listado 24.11 muestra por completo la aplicacin.
Listado 24.11. Implementacin de File Move using System; using System.IO; namespace mv { class Class1 { static void Main(string[] args) { string [] cla = Environment.GetCommandLineArgs(); if (cla.GetUpperBound(0) == 2) { FileInfo fi = new FileInfo(cla[1]); fi.MoveTo(cla[2]); Console.WriteLine("File Created : " + fi.CreationTime.ToString()); Console.WriteLine("Moved to : " + cla[2]); } else Console.WriteLine("Usage: mv <source file> <destination file>"); } } }
543
Observe que en este ejemplo, el nombre del archivo de destino puede ser un nombre de archivo o un nombre de directorio. Si se especifica un nombre de directorio, el archivo se traslada. Si hay un nombre de archivo, el archivo es renombrado y/o trasladado. El mtodo MoveTo() bsicamente incorpora las funciones de copia y renombrado en un solo mtodo.
Para leer una clave del registro use el objeto RegistryKey. Para empezar a estudiar este objeto, examine el listado 24.12, una aplicacin que recupera dos fragmentos de informacin del registro.
Listado 24.12. Recupera el tipo de CPU y su velocidad del Registro using System; using Microsoft.Win32; namespace CPUInfo { class Class1 { static void Main(string[] args) { RegistryKey RegKey = Registry.LocalMachine; RegKey = RegKey.OpenSubKey( "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0"); Object cpuSpeed = RegKey.GetValue("~MHz"); Object cpuType = RegKey.GetValue("VendorIdentifier"); Console.WriteLine("You have a {0} running at {1}
544
Al instanciar un objeto de RegistryKey, hace que su valor sea igual a un miembro de la clase Registry . El anterior ejemplo asigna al objeto RegistryKey el valor del campo Registry.LocalMachine, que permite el acceso a la clave base HKEY_LOCAL_MACHINE. La tabla 24.3 tiene una lista de todos los campos pblicos de la clase Registry. Tras establecer el objeto RegistryKey , invoque a su mtodo OpenSubKey() y proporcione la clave que quiere abrir. En este caso concreto, debe dirigirse a la clave H K E Y _ L O C A L _ M A C H I N E \ H A R D W A R E \ DESCRIPTION\System\Central\Processor\0\ y leer dos valores de esa clave. Tenga en cuenta que debe incluir dos barras invertidas en la cadena para que no sean confundidas con un carcter de escape. Tras abrir la subclave, use las siguientes dos lneas de cdigo para recuperar los valores " ~MHz " y "VendorIdentifier" de esa subclave:
Object cpuSpeed = RegKey.GetValue("~MHz"); Object cpuType = RegKey.GetValue("VendorIdentifer");
Ahora tiene los valores almacenados en las variables adecuadas, de modo que puede mostrar la informacin en la ventana. Examine el programa desde una ventana de consola, como muestra la figura 24.8.
Tabla 24.3. Campos pblicos de la clase Registry Campo ClassesRoot Descripcin ClassesRoot define los tipos de documentos y las propiedades asociadas a esos tipos. Este campo empieza en la clave HKEY_CLASSES_ROOT del registro de Windows. CurrentConfig contiene informacin relativa al hardware de su equipo. Este campo empieza en la clave HKEY_CURRENT_CONFIG del registro de Windows. Aqu se almacenan todas las preferencias del usuario actual. Este campo empieza en la clave HKEY_CURRENT_USER del registro de Windows. DynData. LocalMachine contiene informacin para el equipo. Este campo empieza en la clave HKEY_LOCAL_ MACHINE del registro de Windows.
CurrentConfig
CurrentUser
DynData LocalMachine
545
Campo PerformanceData
Descripcin La clave base almacena informacin relativa al rendimiento de los diferentes componentes de software. Este campo empieza en la clave HKEY_PERFORMANCE_DATA del Registro de Windows. Esta clave base contiene informacin para la configuracin de usuario por defecto. Este campo empieza en la clave HKEY_USERS del registro de Windows.
Users
c:\ C:\WINDOWS\System32\cmd.exe C:\>cpuinfo You have a AuthenticAMD running at 1396 MHz. C:\>_
Figura 24.8. La clase RegistryKey simplifica la lectura de la informacin importante del registro
Si se est trabajando en equipos con varios procesadores puede obtener una lista de todos los procesadores enumerando la clave CentralProcessor. Hay una subclave en CentralProcessor para cada CPU del equipo.
546
Tabla 24.4. Miembros comunes de RegistryKey Nombre SubKeyCount ValueCount Close Tipo Propiedad Propiedad Mtodo Descripcin Esta propiedad recupera un recuento de las subclaves de la clave actual. Esta propiedad recupera un recuento del nmero de valores de la clave actual. Este mtodo cierra la clave actual. Si se han realizado cambios en la clave, los cambios se guardan en el disco. Este mtodo crea una nueva subclave o abre la subclave si ya existe. Este mtodo elimina una subclave. Este mtodo est sobrecargado y contiene un parmetro booleano que permite que se inicie una excepcin si no se puede encontrar la clave. Este mtodo elimina una clave y todas las subclaves secundarias de manera recurrente. Este mtodo elimina un valor de una clave. Este mtodo est sobrecargado y contiene un parmetro booleano que permite que se inicie una excepcin si no se puede encontrar el valor. Este mtodo devuelve una matriz de cadenas que contiene todos los nombres de las subclaves. Este mtodo devuelve un valor para una clave especfica. Este mtodo est sobrecargado y contiene un parmetro que admite un valor por defecto. Si no se puede encontrar el valor para la clave, se devuelve el valor por defecto que se especific. Este mtodo devuelve una matriz de cadenas que contiene todos los valores para la clave especificada. Este mtodo abre una subclave para un proceso (acceso de lectura y escritura). Este mtodo asigna un valor a una clave. Para asignar el valor por defecto a una clave, asigne el parmetro subKey a una cadena vaca.
CreateSubKey DeleteSubKey
Mtodo Mtodo
DeleteSubKeyTree
Mtodo
DeleteValue
Mtodo
GetSubKeyNames
Mtodo
GetValue
Mtodo
GetValueNames
Mtodo
OpenSubKey SetValue
Mtodo Mtodo
547
El listado 24.13 muestra una sencilla aplicacin que escribe dos valores en el Registro y a continuacin lee esos valores para mostrarlos.
Listado 24.13. Cmo escribir texto y un valor DWord en el Registro
using System; using Microsoft.Win32; namespace WriteRegValues { class Class1 { static void Main(string[] args) { RegistryKey RegKeyWrite = Registry.CurrentUser; RegKeyWrite = RegKeyWrite.CreateSubKey ("Software\\CSHARP\\WriteRegistryValue"); RegKeyWrite.SetValue("Success","TRUE"); RegKeyWrite.SetValue("AttemptNumber", 1); RegKeyWrite.Close(); RegistryKey RegKeyRead = Registry.CurrentUser; RegKeyRead = RegKeyRead.OpenSubKey ("Software\\CSHARP\\WriteRegistryValue"); Object regSuccessful = RegKeyRead.GetValue("Success"); Object regAttemptNumber = RegKeyRead.GetValue("AttemptNumber"); RegKeyRead.Close(); ((string)regSuccessful == "TRUE") Console.WriteLine("Succeeded on attempt # {0}", regAttemptNumber); else Console.WriteLine("Failed!"); } } } if
Tras crear un objeto RegistryKey, puede crear una nueva subclave con el mtodo CreateSubKey() . Asegrese de emplear dos barras invertidas al usar este mtodo para que el compilador no confunda los caracteres con una secuencia de escape. En este ejemplo, se crea una nueva clave bajo HKEY_CURRENT_USER. Almacene los valores en la subclave \Software\CSHARP\WriteRegistryValue. Una vez que la nueva clave est en su sitio, use el mtodo SetValue() para especificar el nombre del valor y el valor actual. Este ejemplo almacena texto en el valor S u c c e s s y un DWord en el valor AttemptNumber. Una vez asignados los valores, es aconsejable cerrar la clave por si se produce un corte de luz o algn fallo similar. En este punto, se han producido cambios en el registro. Si abre la aplicacin RegEdit y se dirige a la clave adecuada, debera ver los valores mostrados en la figura 24.9.
548
Como en el ejemplo anterior, crea un nuevo objeto RegistryKey y lee de nuevo los valores. Si el valor de Success es realmente True, se muestra la informacin en la pantalla, como se puede ver en la figura 24.10.
c:\ C:\WINDOWS\System32\cmd.exe C: \>writeregvalues.exe Succeeded on attempt # 1 C:\>_
Esta aplicacin muestra una sencilla tcnica para escribir valores en el registro. Este mtodo ha demostrado ser til para controlar la configuracin de los programas, guardar la ltima posicin y tamao de la interfaz de las aplicaciones y acciones similares. Las posibilidades son ilimitadas.
549
y recuperar todas las subclaves y valores debajo de ese punto inicial. Actualmente no hay ningn mtodo en .NET para enumerar las claves de registro. Deber crear sus propias funciones si las necesita. El conocimiento de la estructura de las claves que se desean enumerar hace que la tarea resulte mucho ms sencillo, ya que puede usar un simple bucle. Si no se conoce la estructura de las entradas del registro, necesitar crear una funcin que pueda llamar y pasar la clave inicial cada vez que es invocada. El listado 24.14 es un ejemplo de enumeracin de claves de registro. Este ejemplo inspecciona el registro en busca de una lista de todo el software que tiene instalado en su equipo. Este programa enumera cualquier aplicacin que aparezca en la seccin Agregar o quitar programas del Panel de control.
Listado 24.14. Cmo enumerar claves de Registro
using System; using Microsoft.Win32; namespace Installed { class Class1 { static void Main(string[] args) { RegistryKey myRegKey = Registry.LocalMachine; myRegKey = myRegKey.OpenSubKey ("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall"); String [] subkeyNames = myRegKey.GetSubKeyNames(); foreach (String s in subkeyNames) { RegistryKey UninstallKey = Registry.LocalMachine; UninstallKey = UninstallKey.OpenSubKey ("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\" + s); try { Object oValue = UninstallKey.GetValue("DisplayName"); Console.WriteLine(oValue.ToString()); } catch (NullReferenceException) { } } } } }
Tras crear un objeto RegistryKey, abra la subclave Uninstall, que contiene una lista de todos los programas instalados. A partir de aqu, use GetSubKeyNames, que devuelve una matriz de cadenas de todas las subclaves.
550
Ahora que tiene una lista de subclaves, use el operador foreach para recorrer todos los elementos de la matriz de cadenas de subclave. Al recorrer cada clave, se busca un valor llamado DisplayName. Este valor es el nombre que se muestra en la seccin Agregar o quitar programas del Panel de control. Recuerde que no todas las claves tendrn este valor. Por tanto, debe encapsular el mtodo GetValue con una instruccin try...catch para capturar todas las posibles excepciones. Cuando se encuentra un valor DisplayName, se recupera el valor y se muestra en la ventana. A continuacin, la instruccin foreach pasa a la siguiente clave de registro de la matriz de cadenas. Pulse F5 para probar la aplicacin. Probablemente vera una larga lista de aplicaciones desplazndose a medida que el programa inspecciona el registro (vase la figura 24.11). Una cosa que no se intenta en este programa es ordenar las aplicaciones alfabticamente. Los elementos del registro no se almacenan as, pero para solucionar esto, puede simplemente almacenar los resultados en una matriz de cadenas y llamar al mtodo Sort para ordenar el resultado de la manera deseada.
C:\ C:\WINDOWS\System32\cmd.exe AVerTV Microsoft Visual Studio .NET Enterprise Architect - Espaol WinAce Archiver 2.0 Winamp (remove only) Microsoft Windows Script Host WinISO 5.3 Compresor WinRAR WinZip Visual Studio .NET Enterprise Architect - Spanish Biblioteca de Consulta Microsoft Encarta 2002 Microsoft .NET Framework (Spanish) v1.0.3705 EA.com Matchup WebFldrs XP Adobe Illustrator 10 Visual Studio.NET Baseline - Spanish PowerDVD Neverwinter Nights McAfee VirusScan Microsoft Office XP Professional con FrontPage EA.com Update FilterSDK QuarkXPress Passport Microsoft FrontPage Client - Spanish C:\>
Figura 24.11. Cmo inspeccionar todas las aplicaciones instaladas con un enumerador de Registro
Resumen
.NET Framework ha reducido enormemente la cantidad de cdigo y tiempo necesario para tratar de forma eficiente con archivos y con el registro de Windows. Entre los muchos beneficios de .NET Framework, ahora tiene acceso a componentes como FileSystemWatcher que permite examinar un sistema de archivos para buscar cambios realizados en cualquier archivo. Sin embargo, debe tener
551
cuidado al escribir aplicaciones que traten con el registro de Windows, porque si elimina por error claves de registro puede hacer que su sistema se vuelva inestable o incluso que deje de funcionar.
552
555
Object
MarshalByRefObject FileSystemInfo
FileInfo DirectoryInfo Stream FileStream MemoryStream NetworkStream CryptoStream BufferedStream TextReader StreamReader StringReader TextWriter IndentedTextWriter StreamWriter StringWriter HttpWriter HtmlTextWriter
556
La clase base Stream contiene propiedades y mtodos que permiten a los invocadores trabajar con la secuencia de datos. .NET Framework dispone de varias clases que se derivan de la clase base Stream. Cada clase proporciona una implementacin especfica de una secuencia de datos usada para un entorno particular. La clase FileStream, por ejemplo, proporciona una implementacin que permite a los invocadores trabajar con secuencias de datos vinculadas a un archivo de disco. Del mismo modo, la clase NetworkStream proporciona una implementacin que permite a los invocadores trabajar con secuencias de datos a las que se accede mediante una red de comunicaciones.
557
E/S sncrona
El listado 25.1 muestra una E/S de secuencia sncrona. Crea un archivo y escribe 256 bytes de datos binarios en l. A continuacin, lee los 256 bytes del archivo y se asegura de que la lectura de datos coincida con los datos escritos.
Listado 25.1. E/S de archivos sncrona
using System; using System.IO; class FileTestClass { private FileStream BinaryFile; private byte [] ByteArray; public FileTestClass() { BinaryFile = new FileStream("test.dat", FileAccess.ReadWrite); ByteArray = new byte [256]; } public void WriteBytes() { int ArrayIndex; for (ArrayIndex = 0; ArrayIndex < 256; ArrayIndex++) ByteArray[ArrayIndex] = (byte)ArrayIndex; BinaryFile.Write(ByteArray, 0, 256); } public bool ReadBytes() { int ArrayIndex;
FileMode.Create,
558
BinaryFile.Seek(0, SeekOrigin.Begin); BinaryFile.Read(ByteArray, 0, 256); for (ArrayIndex = 0; ArrayIndex < 256; ArrayIndex++) { if(ByteArray[ArrayIndex] != (byte)ArrayIndex) return false; } return true; } } class MainClass { static public void Main() { FileTestClass FileTest = new FileTestClass(); bool ReadTest; FileTest.WriteBytes(); ReadTest = FileTest.ReadBytes(); if(ReadTest == true) Console.WriteLine("The readback test was successful."); else Console.WriteLine("The readback test failed."); } }
El listado 25.1 implementa dos clases C#: FileTestClass, que contiene el cdigo de E/S de la secuencia, y MainClass, que contiene el mtodo M a i n ( ) de la aplicacin. El mtodo Main() crea un objeto de la clase FileTestClass y pide al objeto que escriba y lea datos. La clase FileTestClass contiene un miembro privado que representa un objeto FileStream . El constructor de la clase crea un nuevo objeto FileStream mediante un constructor que acepta tres argumentos: La ruta de la secuencia de archivo con la que se va a trabajar. Una especificacin de modo para la operacin de archivos. Una especificacin de modo para el acceso a archivos.
La especificacin de modo para la operacin de archivos est representada por una enumeracin llamada FileMode. La enumeracin FileMode se incluye en el espacio de nombres System.IO de .NET y admite los siguientes miembros de enumeracin: Append, que ordena a la secuencia del archivo que abra el archivo indicado si lo encuentra. Si el archivo indicado existe, la clase de secuencia de archivos se inicializa para escribir datos al final del archivo. Si el archivo indicado no existe, la clase crea un nuevo archivo con el nombre especificado.
559
Create, que ordena a la secuencia de archivos que cree el archivo indicado. Si el archivo ya existe, se sobrescribe. CreateNew, que, como Create, ordena a la secuencia de archivos que cree el archivo indicado. La diferencia entre CreateNew y Create es el modo de trabajar con los archivos existentes. Si el archivo ya existe cuando se especifica CreateNew como modo de archivos, la secuencia de archivos inicia una excepcin de la clase IOException. Open, que ordena a la secuencia de archivos que abra el archivo indicado. OpenOrCreate, que ordena a la secuencia de archivos que cree el archivo indicado. Si el archivo ya existe, el objeto FileStream abre el archivo indicado. Truncate, que ordena a la secuencia de archivos que abra el archivo indicado y, a continuacin, lo trunca para que su tamao sea cero bytes. La especificacin del modo de acceso a archivos est representada por una enumeracin llamada FileAccess. La enumeracin FileAccess tambin se encuentra en el espacio de nombres System.IO de .NET y admite los siguientes miembros de enumeracin: Read, que especifica que la clase FileStream debe permitir el acceso de lectura al archivo especificado.
ReadWrite, que especifica que la clase FileStream debe permitir el acceso de lectura y escritura al archivo especificado. Write, que especifica que la clase FileStream debe permitir el acceso de escritura al archivo especificado. Se pueden escribir datos en el archivo, pero no se pueden leer.
El constructor FileTestClass del listado 25.1 crea una nueva secuencia de archivos que gestiona un archivo llamado test.dat. El archivo se abre en modo de creacin para el acceso de lectura y escritura. El mtodo WriteBytes() de FileTestClass se coloca en un buffer de 256 bytes que crea el constructor de la clase. Se ubica en el buffer de 256 bytes con valores que van desde 00 hexadecimal a FF hexadecimal. A continuacin se escribe el buffer en la secuencia mediante el mtodo de secuencia de archivos Write(). El mtodo Write() acepta tres argumentos: Una referencia al buffer de bytes que contiene los datos que se van a escribir. Un nmero entero que especifica el elemento de matriz del primer byte del buffer que se va a escribir. Un nmero entero que especifica el nmero de bytes que se van a escribir.
560
El mtodo Write() es s n c r o n o y no regresa hasta que los datos se han escrito realmente en la secuencia. El mtodo ReadBytes() de FileTestClass lee los 256 bytes escritos por WriteBytes() y compara los bytes con el patrn de bytes implementado por WriteBytes(). La primera operacin que realiza el mtodo ReadBytes() supone trasladar el puntero de la secuencia al principio del archivo. El puntero de las secuencias es un concepto importante y merece una especial atencin. Las secuencias admiten el concepto de posicionamiento de archivo. El puntero de la secuencia hace referencia a la posicin de la secuencia donde tendr lugar la prxima operacin de E/S. Por lo general, el puntero de la secuencia se sita al principio de la misma cuando se inicializa dicha secuencia. A medida que se leen o escriben datos en la secuencia, el puntero de la secuencia avanza hasta la posicin inmediatamente despus de la ltima operacin. La figura 25.2 refleja este concepto. Muestra una secuencia con seis bytes de datos.
Estado de la secuencia de archivos al inicializar el ob|eto
1 _
posicin
posicin
Cuando la secuencia se abre por primera vez, el puntero de la secuencia apunta al primer byte de la secuencia. Esto aparece reflejado en el diagrama superior de la figura 25.2. Imagine que el cdigo que gestiona la secuencia lee tres bytes del archivo. Se leen los tres bytes y el puntero de secuencia apuntara al byte inmediatamente despus de la ltima posicin que se ha ledo. Siguiendo con el ejemplo, el puntero de la secuencia apuntar al cuarto byte de la secuencia. Esto se refleja en la parte inferior del diagrama de la figura 25.2. La relacin con el cdigo 25.1 tiene que ver con el hecho de que se crea un archivo y se escriben 256 bytes en l. Cuando se han escrito los bytes, se leen. Sin
561
embargo, es importante recordar dos conceptos de posicionamiento de secuencias: El puntero del archivo se actualiza despus de cada operacin de lectura o escritura, para sealar una posicin inmediatamente posterior a la ltima operacin. Las operaciones de lectura y escritura empiezan en el byte al que hace referencia el puntero de la secuencia.
Cuando el cdigo del listado 25.1 crea la nueva secuencia de archivos, el puntero del archivo se sita al principio del archivo (vaco). Tras escribir los 256 bytes, el puntero del archivo se actualiza para hacer referencia a la posicin inmediatamente posterior a los 256 bytes. Si el cdigo va a leer los 256 bytes inmediatamente despus de la operacin de escritura, la operacin de lectura fallar porque el puntero de la secuencia apunta al final del archivo tras la operacin de escritura, y la operacin de lectura intentar leer 256 bytes a partir de esa posicin, pero no hay bytes disponibles en dicha posicin. El cdigo debe decir "antes de que empiece la operacin de lectura, vuelve a situar el puntero del archivo al principio de la secuencia para que la operacin de lectura pueda realizarse". Esta tarea la realiza un mtodo de la clase Stream llamado Seek() . El mtodo Seek() permite al cdigo trasladar el puntero de la secuencia a cualquier posicin disponible en la secuencia. El mtodo Seek() recibe dos parmetros: Un nmero entero long, que especifica un desplazamiento del puntero en bytes. Un valor de una enumeracin llamada SeekOrigin, que especifica el tipo de posicionamiento que debe usarse para realizar la operacin de desplazamiento del puntero.
La enumeracin SeekOrigin se declara en el espacio de nombres System.IO y admite los siguientes valores: Begin, que indica que la operacin de bsqueda debe realizarse respecto al principio de la secuencia. Current, que indica que la operacin de bsqueda debe realizarse respecto a la actual posicin de la secuencia. End, que indica que la operacin de bsqueda debe realizarse respecto al final de la secuencia.
El mtodo Seek() ajusta el puntero de la secuencia para que apunte a la posicin de secuencia a la que hace referencia la enumeracin SeekOrigin, que se desplaza el nmero especificado de bytes. El desplazamiento de bytes
562
usado en el mtodo Seek() puede ser positivo o negativo. El siguiente ejemplo usa un valor de desplazamiento positivo:
File.Seek(4, SeekOrigin.Begin);
La anterior lnea ajusta el puntero de la secuencia para que apunte cuatro bytes ms all del principio de la secuencia. Los valores de desplazamiento positivo desplazan el puntero de la secuencia hacia el final de la secuencia. El siguiente ejemplo usa un valor de desplazamiento negativo:
File.Seek(-2, SeekOrigin.End);
Este ejemplo ajusta el puntero de la secuencia para que apunte dos bytes antes del final de la secuencia. Los valores de desplazamiento negativos desplazan el puntero de la secuencia hacia el principio de la secuencia. El cdigo del listado 25.1 usa el siguiente cdigo de bsqueda antes de que se lean los 256 bytes:
BinaryFile.Seek(0, SeekOrigin.Begin);
Esta llamada ajusta el puntero hacia el principio de la secuencia. Cuando empieza la operacin de lectura, comienza leyendo desde el principio de la secuencia. El mtodo ReadBytes() usa el mtodo de FileStream llamado Read() para realizar una lectura sncrona de E/S en la secuencia. El mtodo Read() admite tres argumentos: Una referencia al buffer de bytes que se usar para contener la lectura de bytes de la secuencia. Un nmero entero que especifica el elemento de matriz del primer byte en el buffer que contendr la lectura de datos de la secuencia. Un nmero entero que especifica el nmero de bytes que se van a leer.
El mtodo Read() es sncrono y no regresa hasta que se han ledo realmente los datos de la secuencia. Cuando la operacin de lectura se completa, el cdigo comprueba el patrn de bytes de la matriz para asegurarse de que concuerda con el patrn de bytes que se escribi.
E/S asncrona
El listado 25.2 es una modificacin del listado 25.1 que refleja la E/S asncrona. A diferencia de la E/S sncrona, en la que las llamadas para leer y escribir operaciones no regresan hasta que la operacin est completa, las llamadas a operaciones de E/S asncronas regresan poco despus de ser llamadas. La operacin de E/S actual se realiza de forma oculta, en un subproceso distinto creado por la implementacin de los mtodos de E/S asncronos de .NET
563
Framework y cuando la operacin se ha completado se advierte al cdigo mediante un delegado. La ventaja de la E/S asncrona es que el cdigo principal no necesita depender de que se complete una operacin de E/S. La realizacin de largas operaciones de E/S en segundo plano evita que la aplicacin tenga que realizar otras tareas, como procesar los mensajes de Windows en aplicaciones de Windows Forms.
FileMode.Create,
564
AsyncResultImplementation = BinaryFile.BeginRead(ByteArray, 0, 256, ReadBytesCompleteCallback, null); } public void OnReadBytesComplete(IAsyncResult AsyncResult) { int ArrayIndex; int BytesRead; int Failures; BytesRead = BinaryFile.EndRead(AsyncResult); Console.WriteLine("Bytes read........: {0}", BytesRead); Failures = 0; for(ArrayIndex = 0; ArrayIndex < 256; ArrayIndex++) { if (ByteArray[ArrayIndex] != (byte)ArrayIndex) { Console.WriteLine("Read test failed for byte at offset {0}.", ArrayIndex); Failures++; } } Console.WriteLine("Read test failures: {0}", Failures); } public void WaitForReadOperationToFinish() { WaitHandle WaitOnReadIO; WaitOnReadIO = AsyncResultImplementation.AsyncWaitHandle; WaitOnReadIO.WaitOne(); } } class MainClass { static public void Main() { FileTestClass FileTest = new FileTestClass(); FileTest.WriteBytes(); FileTest.ReadBytes(); FileTest.WaitForReadOperationToFinish(); } }
El cdigo de las operaciones de escritura del listado 25.2 se controla de forma sncrona, y su cdigo es idntico al cdigo de las operaciones de escritura del listado 25.1. Sin embargo, la operacin de lectura es bastante diferente. El cdigo de las operaciones de lectura del listado 25.2 no empieza con una llamada al mtodo sncrono Read() de la secuencia, sino con una llamada al mtodo asncrono BeginRead(). Esta llamada admite cinco parmetros. Los
565
tres primeros concuerdan con los parmetros admitidos con el mtodo s n c r o n o Read(), pero los dos ltimos parmetros son nuevos: Una referencia al buffer de bytes que se usar para contener la lectura de bytes de la secuencia. Un nmero entero que especifica el elemento de matriz del primer byte en el buffer que contendr la lectura de datos de la secuencia. Un nmero entero que especifica el nmero de bytes que se van a leer. Datos especficos de llamada.
El delegado c a l l b a c k debe ser un objeto de una clase llamada AsyncCallback. La clase AsyncCallback se declara en el espacio de nombres System de .NET Framework y gestiona un mtodo que no devuelve nada y admite una referencia a un interfaz llamada IAsyncResult. El listado 25.2 crea una instancia de este delegado en el constructor de la clase FileTestClass:
ReadBytesCompleteCallback = new AsyncCallback(OnReadBytesComplete);
La clase FileTestClass del listado 25.2 incluye un nuevo mtodo llamado OnReadBytesComplete(), que se usa como mtodo delegado. El objeto Stream invoca a este delegado cuando se completa la operacin de lectura. La interfaz IAsyncResult, que se emplea como parmetro para el delegado AsyncCallback, se define en el espacio de nombres System de .NET Framework. Admite cuatro propiedades que pueden usarse para obtener ms informacin sobre la naturaleza de la operacin asncrona: AsyncState, que es una referencia al objeto que se proporcion como el ltimo parmetro del mtodo BeginRead() . Los mtodos de E/S asncronos permiten asociar datos con una operacin especfica del ltimo parmetro a un mtodo de E/S. En la propiedad AsyncState hay disponible una copia de estos datos. El listado 25.2 no necesita que los datos se asocien a la llamada, por lo que pasa un valor n u l l como ltimo parmetro para B e g i n R e a d ( ) . Como resultado, la propiedad AsyncState tambin tiene un valor n u l l . Quizs quiera usar estos datos para, por ejemplo, diferenciar una llamada de E/S de otra. Por ejemplo, puede usar la misma referencia de delegado en varias llamadas de E/S asncronas y quizs quiera pasar junto a ellas los datos que diferencian unas llamadas de otras. AsyncWaitHandle, que es una referencia a un objeto de clase WaitHandle. La clase WaitHandle se declara en el espacio de nombres System.Threading de .NET Framework. Este objeto encapsula una sincronizacin que sirve de clase base para elementos especficos de sincronismo, como mutex y semforos. El cdigo puede esperar en este
566
control para determinar cundo la operacin de lectura est realmente terminada. El cdigo del listado 25.2 hace precisamente eso. CompletedSynchronously, que es un booleano cuyo valor es True si la llamada BeginRead() se completa de forma s n c r o n a y False en caso contrario. Casi todas las implementaciones de secuencia devuelven F a l s e para esta propiedad cuando la interfaz hace referencia a una operacin de E/S asncrona. IsCompleted, que es un booleano cuyo valor es True si el objeto Stream ha completado la operacin asncrona. Hasta ese momento, la propiedad tiene un valor F a l s e . El cdigo puede destruir todos los recursos relacionados con secuencias despus de que la propiedad IsCompleted devuelve True. La llamada a BeginRead() tambin devuelve una implementacin de la interfaz IAsyncCallback. El cdigo del listado 25.2 captura la referencia de interfaz para su uso posterior. El mtodo AsyncCallback , que, en el listado 25.2, es el mtodo OnReadBytesComplete(), es invocado por el objeto Stream cuando se completa la operacin asncrona. La implementacin que se muestra en el listado 25.2 empieza con una llamada a EndRead(), que devuelve el nmero de bytes que se leen realmente desde la operacin. Este nmero debe ser igual al nmero de bytes que se solicit leer mediante BeginRead() . TRUCO: La llamada a EndRead() en el listado 25.2 se muestra para que se pueda encontrar el nmero de bytes afectados por la operacin asncrona. Si el cdigo no necesita este valor, no es necesario llamar a EndRead() . El resto de la implementacin del mtodo OnReadBytesComplete() comprueba el patrn de bytes ledos por la operacin de E/S y enva sus resultados a la consola. El mtodo M a i n ( ) del listado 25.2 agrega una nueva llamada de mtodo al cdigo del listado 25.1, que es para un mtodo privado del objeto FileTestClass llamado WaitForReadOperationToFinish() . Como la operacin de lectura asncrona es la ltima operacin del cdigo, la aplicacin puede salir antes de que se complete la operacin de lectura. Recuerde que el procesamiento de la operacin de E/S asncrona se realiza en un subproceso diferente. Si el proceso principal sale antes de que el subproceso de E/S pueda terminar, el cdigo de OnReadBytesComplete() no tiene oportunidad de terminar. El mtodo WaitForReadOperationToFinish() garantiza que la operacin se complete antes de regresar a su invocador. El mtodo WaitForReadOperationToFinish() usa el temporizador de espera en la implementacin de la interfaz IAsyncCallback para realizar
567
su trabajo. El mtodo llama al mtodo WaitOne() de WaitHandle para que espere hasta que el temporizador de espera est marcado. La llamada a WaitOne() no regresa hasta que el temporizador de espera est marcado. El objeto Stream marca el temporizador de espera slo despus de que la operacin de E/S se complete. Despus de que la llamada a WaitOne() regrese, puede estar seguro de que toda la operacin se ha completado.
568
public void ReadBytes() { WaitForWriteOperationToFinish(); BinaryFile.Seek(0, SeekOrigin.Begin); AsyncReadResultImplementation = BinaryFile.BeginRead(ByteArray, 0, 256, ReadBytesCompleteCallback, null); } public void OnReadBytesComplete(IAsyncResult { int ArrayIndex; int BytesRead; int Failures; AsyncResult)
BytesRead = BinaryFile.EndRead(AsyncResult); Console.WriteLine("Bytes read........: {0}", BytesRead); Failures = 0; for (ArrayIndex = 0; ArrayIndex < 256; ArrayIndex++) { if(ByteArray[ArrayIndex] != (byte)ArrayIndex) { Console.WriteLine("Read test failed for byte at offset {0}.", ArrayIndex); Failures++; } } Console.WriteLine("Read test failures: {0}", Failures); } public void WaitForReadOperationToFinish() { WaitHandle WaitOnReadIO; WaitOnReadIO = AsyncReadResultImplementation.AsyncWaitHandle; WaitOnReadIO.WaitOne(); } public void OnWriteBytesComplete(IAsyncResult { BinaryFile.EndWrite(AsyncResult); } private void WaitForWriteOperationToFinish() { WaitHandle WaitOnWriteIO; WaitOnWriteIO = AsyncWriteResultImplementation.AsyncWaitHandle; WaitOnWriteIO.WaitOne(); } } AsyncResult)
569
class MainClass static public void Main() { FileTestClass FileTest = new FileTestClass(); FileTest.WriteBytes(); FileTest.ReadBytes(); FileTest.WaitForReadOperationToFinish(); } }
El mtodo EndWrite() no devuelve un valor, a diferencia del mtodo EndRead() . Sin embargo, los dos mtodos se bloquean hasta que se termina la operacin de E/S.
Escritores y lectores
.NET Framework tiene muchas clases escritoras y lectoras que facilitan el trabajo con datos ms complejos que simples secuencias de bytes. Los lectores y los escritores encapsulan una secuencia y proporcionan un nivel de conversin que convierte los valores en sus secuencias de bytes equivalentes (para los escritores) y viceversa (para los lectores). Las clases lectoras y escritoras de .NET suelen tener un nombre que refleja el tipo de cambio de formato que realizan. Por ejemplo, la clase HtmlTextWriter escribe valores destinados a la informacin de respuestas de HTTP enviadas por ASP.NET, y la clase StringReader lee valores escritos usando su representacin de cadena. Las clases escritoras y lectoras tambin controlan varios esquemas de codificacin, algo imposible de realizar mediante objetos de secuencia de bajo nivel. Por ejemplo, las clases derivadas de la clase abstracta TextWriter, permiten al cdigo C# escribir texto y codificarlo en la secuencia mediante los algoritmos codificadores ASCII, Unicode, UTF7 o UTF8.
570
private BinaryWriter Writer; private FileStream BinaryFile; public FileTestClass() { BinaryFile = new FileStream("test.dat", FileAccess.ReadWrite); Writer = new BinaryWriter(BinaryFile); } public void WriteBinaryData() { Writer.Write('a'); Writer.Write(123); Writer.Write(456.789); Writer.Write("test string"); } } class MainClass { static public void Main() { FileTestClass FileTest = new FileTestClass(); FileTest.WriteBinaryData(); } }
FileMode.Create,
El cdigo del listado 25.4 tiene un diseo de clases igual al del listado 25.3. El cdigo contiene una clase MainClass y una clase FileTestClass. El constructor de la clase FileTestClass del listado 25.4 crea una secuencia de archivos y, a continuacin, un objeto BinaryWriter. El constructor del objeto BinaryWriter, que establece la relacin entre el escritor binario y la secuencia en la que escribe sus datos, recibe una referencia a la secuencia de archivos. En el listado 25.4, todos los datos escritos en el escritor binario terminan en la secuencia de archivos establecida en el constructor. El mtodo WriteBinaryData() escribe un carcter, un nmero entero, uno doble y una cadena en la secuencia subyacente. La clase BinaryWriter implementa varias sobrecargas de un mtodo llamado Write() . Las sobrecargas del mtodo Write() admiten la escritura en la clase escritora de los siguientes tipos de datos: Booleanos Bytes Matrices de bytes Caracteres Matrices de caracteres Valores decimales
571
Valores dobles Valores enteros cortos con y sin signo Valores enteros con y sin signo Valores enteros largos con y sin signo Sbytes Valores de coma flotante Cadenas
Si compila y ejecuta el cdigo del listado 25.4, se crea un archivo llamado test.dat . Puede examinar los contenidos del nuevo archivo en un editor hexadecimal para comprobar que las representaciones binarias de los valores se han escrito en el archivo.
572
ReadInteger = Reader.ReadInt32(); ReadDouble = Reader.ReadDouble(); ReadString = Reader.ReadString(); Console.WriteLine("Character: {0}", ReadCharacter); Console.WriteLine("Integer: {0}", ReadInteger); Console.WriteLine("Double: {0}", ReadDouble); Console.WriteLine("String: {0}", ReadString); } public void WriteBinaryData() { Writer.Write('a'); Writer.Write(123); Writer.Write(456.789); Writer.Write("test string"); } } class MainClass { static public void Main() { FileTestClass FileTest = new FileTestClass(); FileTest.WriteBinaryData(); FileTest.ReadBinaryData(); } }
A diferencia de la clase BinaryWriter, que contiene un mtodo sobrecargado para las operacin de escritura, la clase BinaryReader contiene un mtodo de lectura distinto para cada tipo de d a t o . El cdigo del listado 25.5 usa algunos de estos mtodos de lectura, como ReadChar() y ReadInt32(), para leer valores de la secuencia escrita en el mtodo WriteBinaryData() . Los valores que se leen de la secuencia se envan a la consola. Al ejecutar el listado 25.5 se obtiene el siguiente resultado en la consola.
Character: a Integer: 123 Double: 456.789 String: test string
573
envan a travs de la secuencia. Un buen ejemplo de esta tecnologa es la clase XmlWriter, que encapsula en elementos XML con formato correcto los datos que se envan a una secuencia. El resultado es un documento XML con formato correcto con el que puede trabajar cualquier procesador de documentos XML, como muestra el listado 25.6.
Listado 25.6. Cmo escribir XML con la clase XmlWriter
using System; using System.IO; using System.Xml; class XMLStreamWriterClass { private XmlTextWriter XmlWriter; public void WriteXML() { XmlWriter = new XmlTextWriter(Console.Out); XmlWriter.WriteStartDocument(); XmlWriter.WriteComment("This XML document was automatically generated by C# code."); XmlWriter.WriteStartElement("BOOK"); XmlWriter.WriteElementString("TITLE", "C# Bible"); XmlWriter.WriteElementString("AUTHOR", "Jeff Ferguson"); XmlWriter.WriteElementString("PUBLISHER", "Wiley"); XmlWriter.WriteEndElement(); XmlWriter.WriteEndDocument(); } } class MainClass { static public void Main() { XMLStreamWriterClass XMLStreamWriter = new XMLStreamWriterClass(); XMLStreamWriter.WriteXML(); } }
El cdigo del listado 25.6 crea una nueva instancia de la clase XmlWriter y la asocia a la secuencia de salida de la consola. El cdigo simplemente llama a varios mtodos de la clase XmlWriter para producir datos y los mtodos rodean a esos datos con nombres de elementos XML que se especifican cuando se llama al mtodo. Observe la siguiente lnea del listado 25.6:
XmlWriter.WriteElementString("AUTHOR", "Jeff Ferguson");
574
Esta llamada ordena a la clase XmlWriter que escriba un elemento XML llamado <AUTHOR> cuyo valor es <Brian Patterson>, al dispositivo de salida de la secuencia. La implementacin del mtodo proporciona automticamente la etiqueta de cierre XML. Al compilar y ejecutar el cdigo del listado 25.6, se enva el siguiente documento XML con formato correcto a la consola de la aplicacin:
<?xml version="1.0" encoding="IBM437"?> <!--This XML document was automatically generated by C# code.-> <BOOK> <TITLE>C# Bible</TITLE> <AUTHOR>Jeff Ferguson</AUTHOR> < PUBLISHER>Wiley</PUBLISHER> </BOOK>
Resumen
Las secuencias proporcionan compatibilidad con la E/S sncrona y asncrona en las aplicaciones de C#. Las secuencias trabajan en el nivel de bytes y necesitan que se lean y escriban bloques de bytes. Los lectores y escritores encapsulan secuencias y proporcionan acceso a los datos en un nivel superior. Puede usar lectores y escritores para trabajar con los tipos de datos estndar de C#, lo que permite que los lectores y los escritores hagan conversiones entre los valores de tipos de datos y sus representaciones de bytes. Su cdigo C# seguramente trabajar con lectores y escritores, ya que proporcionan compatibilidad para trabajar con los tipos de datos estndar sin tener que ocuparse de realizar la conversin entre un valor de un tipo de dato y su representacin binaria. Sin embargo, las secuencias tambin estn disponibles si cree que necesita trabajar con ellas directamente. Quizs tambin quiera trabajar con secuencias si los datos que est leyendo estn en un formato propietario que no es compatible con las clases lectoras y escritoras estndar de .NET Framework. Tambin puede considerar la posibilidad de crear sus propias clases lectoras, derivadas de las clases base TextReader o StreamReader y usarlas para leer la secuencia en formato propietario.
575
En Windows, el acceso mediante programacin al subsistema de grficos se consigui por primera vez usando los API GDI disponibles desde Windows 3.1. GDI ofreca a los programadores la posibilidad de controlar cualquier tipo de elemento del interfaz de usuario, y esta funcin ha sido reconstruida desde cero en .NET Framework. GDI+ ha reemplazado a GDI como el API que se usa para acceder a los subsistemas de grficos de Windows. Con GDI+ puede acceder a fuentes, manipular cualquier tipo de imagen y trabajar con formas en las aplicaciones de C#. Para conseguir una visin global de cmo usar GDI+ en sus aplicaciones, debe comprender el modo de empleo de los objetos Graphics, Pen, Brush y Color. Con estos cuatro objetos, puede conseguir casi cualquier cosa que necesite hacer con la GUI y las imgenes de .NET. Este captulo estudia estos objetos y le familiariza con el uso de GDI+ en C#. Las clases disponibles en GDI+ podran llenar un libro de mil pginas, por lo que debe seguir usando SDK como referencia para la ms compleja y menos usada funcionalidad grfica que no se trata en este captulo.
577
formas, trabajar con imgenes o mostrar texto. Visual Basic 6 y versiones anteriores incorporaban una limitada compatibilidad para trabajar con grficos, lo que dificultaba a los programadores de VB la tarea de escribir aplicaciones grficas personalizadas. Lo que VB haca era mantener un registro de cmo se dibujaban en pantalla los formularios y los objetos de los formularios. La propiedad AutoRedraw permita que los formularios dejasen a Windows mantener un registro de lo que estaba en la parte superior de las dems ventanas y, en caso de necesidad, dibujar de nuevo automticamente un formulario si otro estaba encima de l durante un cierto perodo de tiempo. No haca falta que se ocupara del proceso real de dibujar el formulario. En .NET, sucede todo lo contrario. El objeto G r a p h i c s no recuerda cundo fue dibujado ni qu fue lo que se dibuj. Por tanto, es necesario dibujar de nuevo los objetos tantas veces como resulte necesario si otras ventanas estn encima de una concreta. Esto puede parecer pesado, pero la variable PaintEventArgs del evento Paint de un formulario puede controlar el proceso perfectamente. Si el cdigo de dibujo se mantiene all, cada vez que Windows pinte el formulario se generarn correctamente los objetos. El siguiente fragmento de cdigo recibe una referencia a un objeto Graphics mediante la variable PaintEventArgs del evento Paint de un formulario:
private void Form1_Paint(object sender, System.Windows.Forms.PaintEventArgs p) { Graphics g = p.Graphics; }
Tambin puede crear un objeto G r a p h i c s mediante el mtodo CreateGraphics de un control o formulario. El siguiente cdigo muestra el mtodo CreateGraphics:
private void createManually() { Graphics g; g = this.CreateGraphics; }
El tercer y ltimo modo de crear un objeto Graphics es pasar un archivo de imagen directamente al objeto al instanciarlo, como muestra el siguiente cdigo al tomar una imagen de mapa de bits del sistema de archivos:
private void createFromFile() { Graphics g; Bitmap b; b = new Bitmap(@"C:\Enterprise.bmp"); g = Graphics.FromImage(b); }
578
Si ha estado agregando estos fragmentos a un formulario de Windows, es obvio que no sucede nada cuando se ejecuta alguno de dichos fragmentos. Para implementar realmente alguna funcionalidad, debe usar miembros de la clase Graphics para hacer que sucedan cosas. NOTA: Si crea un objeto Graphics mediante el mtodo CreateGraphics , debe llamar a Dispose en ese objeto despus de usarlo. As se asegura de que el objeto Graphics se eliminar de la memoria. La tabla 26.1 enumera las propiedades de la clase Graphics y la tabla 26.2 enumera los mtodos disponibles de la clase Graphics. La clase Graphics se incluye en el espacio de nombres System.Drawing, que se agrega como referencia por defecto cuando se crea una nueva aplicacin Windows Forms. Esto no significa que no se puedan usar objetos Graphics en ASP.NET; de hecho, en ASP.NET se pueden escribir aplicaciones de proceso de imgenes extremadamente depuradas usando objetos Graphics.
Tabla 26.1. Propiedades de la clase Graphics Propiedad Clip ClipBounds CompositingMode CompositingQuality Descripcin Obtiene o establece un objeto Region que delimita la regin de dibujo de este objeto Graphics Obtiene la estructura RectangleF que delimita la regin de recorte de este objeto Graphics Obtiene un valor que especifica cmo se dibujan las imgenes compuestas en el objeto Graphics Obtiene o establece la calidad de la generacin de las imgenes compuestas que se dibujan en el objeto Graphics Obtiene la resolucin horizontal de este objeto Graphics Obtiene la resolucin vertical de este objeto Graphics Obtiene o establece el modo de interpolacin asociado al objeto Graphics Obtiene un valor que indica si la regin de recorte de este objeto Graphics est vaca Obtiene un valor que indica si la regin visible de recorte de este objeto Graphics est vaca
579
Propiedad PageScale
Descripcin Obtiene o establece la relacin de escala entre las unidades universales y las unidades de pgina de este objeto Graphics Obtiene o establece la unidad de medida usada para las coordenadas de pgina de este objeto Graphics Obtiene o establece un valor que especifica cmo se desplazan los pxeles durante el procesamiento de este objeto Graphics Obtiene o establece el origen de la generacin de este objeto Graphics para la interpolacin y los pinceles de trama Obtiene o establece la calidad de la generacin del objeto Graphics Obtiene o establece el valor de la correccin gamma para la generacin de texto Obtiene o establece el modo de generacin para el texto asociado a este objeto G r a p h i c s Obtiene o establece la transformacin universal de este objeto Graphics Obtiene o establece el rectngulo delimitador de este objeto Graphics
PageUnit
PixelOffsetMode
RenderingOrigin
Tabla 26.2. Mtodos de la clase Graphics Mtodo AddMetafileComment BeginContainer Descripcin Agrega un comentario al objeto Metafile actual Guarda un contenedor Graphics con el estado actual de este objeto Graphics y abre y usa un nuevo contenedor de grficos. Limpia toda la superficie de dibujo y la rellena con el color de fondo especificado Dibuja un arco que representa una porcin de una elipse especificada por un par de coordenadas, un valor de altura y un valor de anchura Dibuja una curva de tipo Bzier definida por cuatro estructuras Point
Clear DrawArc
DrawBezier
580
Descripcin Dibuja una serie de curvas de tipo Bzier a partir de una matriz de estructuras Point Dibuja una curva de tipo cardinal cerrada definida por una matriz de estructuras Point Dibuja una curva de tipo cardinal mediante una matriz de estructuras Point especificada Dibuja una elipse definida por un rectngulo delimitador especificada por un par de coordenadas, un valor de altura y un valor de anchura Dibuja la imagen representada por el objeto Icon especificado en las coordenadas especificadas Dibuja la imagen representada por el objeto Icon especificado sin escalar la imagen Dibuja el objeto Image especificado en la posicin especificada y con el tamao original Dibuja el objeto Image especificado con su tamao original en la posicin especificada por un par de coordenadas Dibuja una lnea que conecta los dos puntos especificados por pares de coordenadas Dibuja una serie de segmentos de lnea que conectan una matriz de estructuras Point Dibuja un objeto GraphicsPath Dibuja una forma circular definida por una elipse especificada por un par de coordenadas, un valor de altura y un valor de anchura y dos lneas radiales Dibuja un polgono definido por una matriz de estructuras Point Dibuja un rectngulo especificado por un par de coordenadas, un valor de alto y un valor de ancho Dibuja una serie de rectngulos especificados por estructuras Rectangle Dibuja la cadena de texto especificada en la posicin especificada con los objetos Brush y Font especificados Cierra el contenedor de grficos activo y restaura el estado que tena este objeto Graphics al esta-
EndContainer
581
Mtodo
EnumerateMetafile
Enva los registros del objeto Metafile especificado, de uno en uno, a un mtodo de devolucin de llamada para su presentacin en un punto determinado Actualiza la regin de recorte de este objeto Graphics con el fin de excluir el rea especificada por una estructura Rectangle Rellena el interior de una curva de tipo cardinal cerrada, definida por una matriz de estructuras Point Rellena el interior de una elipse definida por un rectngulo de delimitacin especificado por un par de coordenadas, un valor de altura y un valor de anchura Rellena el interior de un objeto GraphicsPath Rellena el interior de una seccin de grfico circular definida por una elipse, determinada por un par de coordenadas, unos valores de anchura y altura y dos lneas radiales Rellena el interior de un polgono definido por una matriz de puntos, especificados por estructuras Point Rellena el interior de un rectngulo especificado por un par de coordenadas, un valor de anchura y un valor de altura Rellena el interior de una serie de rectngulos especificados por estructuras Rectangle Rellena el interior de un objeto Region Fuerza la ejecucin de todas las operaciones de grficos pendientes y devuelve inmediatamente el control sin esperar a que finalicen las operaciones Crea un nuevo objeto Graphics a partir del identificador especificado en un contexto de dispositivo Crea un nuevo objeto Graphics a partir del identificador especificado de una ventana Crea un nuevo objeto Graphics a partir del objeto Image especificado
ExcludeClip
FillClosedCurve FillEllipse
FillPath FillPie
FillPolygon
FillRectangle
FromHdc
FromHwnd Fromlmage
582
Descripcin Obtiene un identificador de la paleta actual de semitonos de Windows Obtiene el identificador del contexto de dispositivo asociado a este objeto Graphics Obtiene el color ms prximo a la estructura Color especificada Actualiza la regin de recorte de este objeto Graphics como la interseccin de la regin de recorte actual y la estructura Rectangle especificada Indica si el punto especificado por un par de coordenadas est contenido en la regin de recorte visible de este objeto Graphics
IsVisible
MeasureCharacterRanges Obtiene una matriz de objetos Region, cada uno de los cuales delimita un intervalo de posiciones de caracteres dentro de la cadena especificada MeasureString MultipIyTransform ReleaseHdc Mide la cadena especificada al dibujarla con el objeto Font especificado Multiplica la transformacin universal de este objeto Graphics y especificada en el objeto Matrix Libera un identificador de contexto de dispositivo obtenido mediante una llamada anterior al mtodo GetHdc de este objeto Graphics Restablece la regin de recorte de este objeto Graphics en una regin infinita Restablece la matriz de transformacin universal de este objeto Graphics en la matriz de identidades Restaura como estado de este objeto Graphics el estado representado por un objeto GraphicsState Aplica la rotacin especificada a la matriz de transformacin de este objeto Graphics Guarda el estado actual de este objeto Graphics e identifica el estado guardado con un objeto GraphicsState Aplica la operacin de cambio de escala especificada a la matriz de transformacin de este objeto Graphics, anteponindola a esta ltima
ResetClip ResetTransform
ScaleTransform
583
Mtodo SetClip
Descripcin Establece la regin de recorte de este objeto Graphics en la propiedad Clip del objeto Graphics especificado Transforma una matriz de puntos de un espacio de coordenadas a otro utilizando las transformaciones universal y de pgina actuales de este objeto Graphics Convierte la regin de recorte de este objeto Graphics usando las cantidades especificadas en las direcciones horizontal y vertical Antepone la conversin especificada a la matriz de transformacin de este objeto Graphics
TransformPoints
TranslateClip
TranslateTransform
Como puede ver, la clase Graphics proporciona todos los mtodos posibles que pueda necesitar para trabajar con cualquier tipo de elemento GUI. El listado 26.1 usa muchos de los mtodos de la clase Graphics para producir el resultado que se muestra en la figura 26.1. NOTA: Como no hay una propiedad AutoRedraw , todava necesita un modo de volver a dibujar un formulario si se le asigna un nuevo tamao. Si se usa el mtodo SetStyles y se pasa el estilo ControlStyles. ResizeRedraw correctamente, se llamar al mtodo Paint de un formulario para corregir su estilo. Tras la llamada, el objeto InitializeComponent de su formulario debe escribir SetStyle(ControlStyles .ResizeRedraw, true) para garantizar que se llamar al evento Paint cuando el formulario cambie de tamao. Busque SetStyle en .NET Framework SDK para aprender ms sobre lo que puede hacer con el mtodo SetStyle.
Listado 26.1. Cmo usar mtodos de la clase Graphics
private void drawLine() { /* crea un objeto Graphics que puede ser recuperado para cada una de las muestras */ Graphics g; g = this.CreateGraphics(); // Use el objeto Pen para crear una lnea Pen p;
584
p = new Pen(Color.Red, 50); /* DrawLine es un mtodo sobrecargado, pasa las coordenadas x1, y1, x2, y2 */ g.DrawLine(p, 100F, 100F, 500F, 100F);
// dibuje un icono del sistema de archivos Icon i; i = new Icon(@"C:\Desktop.ico"); // llame a DrawIcon y pase las coordenadas x e y g.DrawIcon(i, 150, 15); // dibuje un rectngulo Pen p2; p2 = new Pen(Color.PapayaWhip, 7 ) ; /* dibuje un rectngulo pasando x, y, altura y anchura */ g.DrawRectangle(p2, 50, 50, 100, 100); }
Si llama a este mtodo desde una aplicacin Windows Forms, su resultado se parecer a lo que aparece en la figura 26.1.
La clase Graphics no hace nada por s misma. Para crear lneas, rectngulos, imgenes y fuentes, debe usar otros objetos junto al objeto Graphics. El listado 26.1 crea un objeto Pen para usarlo en conjuncin con el objeto Graphics para dibujar una lnea roja en el formulario. Tambin crea un objeto Icon, que es usado por el objeto Graphics para dibujar el icono de escritorio en el formulario. Como ya se dijo antes, puede realizar numerosas tareas con GDI+: dibujar formas y lneas, manipular imgenes y trabajar con fuentes. Las siguientes secciones profundizan en este tema, describiendo cmo puede usar objetos como
585
Pen, Brush e Image en combinacin con miembros de la clase Graphics para aprovechar al mximo la inmensa coleccin de utilidades GDI+ de .NET.
586
Form1());
// sobrecarga el evento OnPaint protected override void OnPaint(PaintEventArgs p) { Graphics g = p.Graphics; g.DrawImage(img, 0, 0 ) ; } } }
Si se ejecuta esta aplicacin se producir un resultado parecido al que aparece en la figura 26.2.
Figura 26.2. Resultado del listado 26.2 usando un JPG con GDI +
El mtodo DrawImage usado para dibujar la imagen en el formulario tiene casi 20 constructores sobrecargados. Bsicamente, cada uno indica al mtodo cmo dibujar la imagen, mediante coordenadas o con la altura y anchura. Con un simple cambio en el mtodo DrawImage, puede rellenar todo el formulario con el mapa de bits. Si pasa la constante ClientRectangle a DrawImage, como muestra el fragmento de cdigo, obtendr un resultado con un aspecto similar al de la figura 26.3, con todo el mapa de bits rellenando la pantalla:
// sobrecarga del evento OnPaint protected override void OnPaint (PaintEventArgs p) {
587
Tambin puede devolver propiedades a una imagen sin mostrarla. El siguiente evento Load examina algunas de las propiedades disponibles de la imagen money.jpg que abrimos antes:
private void Form1_Load(object sender, System.EventArgs e) { MessageBox.Show (img.PhysicalDimension.ToString()); MessageBox.Show (img.Height.ToString()); MessageBox.Show (img.Width.ToString()); MessageBox.Show (img.RawFormat.ToString()); MessageBox.Show (img.Size.ToString()); }
La tabla 26.3 describe cada una de las propiedades disponibles para imgenes a travs de la clase Image.
Tabla 26.3. Propiedades de la clase Image
Image
Obtiene una matriz GUID que representa las dimensiones de los marcos de este objeto Image Obtiene la altura de este objeto Image
588
Propiedad HorizontalResolution Palette PhysicalDimension PixelFormat PropertyIdList PropertyItems RawFormat Size VerticalResolution Width
Descripcin Obtiene la resolucin horizontal, en pxeles por pulgada, de este objeto Image Obtiene o establece la paleta de colores de este objeto Image Obtiene la anchura y altura de este objeto Image Obtiene el formato de pxeles de este objeto Image Obtiene una matriz de los identificadores de propiedad almacenados en este objeto Image Obtiene una matriz de objetos PropertyItem que describe este objeto Image Obtiene el formato de este objeto Image Obtiene la anchura y altura de este objeto Image Obtiene la resolucin vertical, en pxeles por pulgada, de este objeto Image Obtiene el ancho de este objeto Image
Tambin puede usar varios mtodos de la clase Image, que le permite manipular imgenes de un modo prcticamente ilimitado. El siguiente cdigo voltea la imagen 90 grados:
img.RotateFlip(RotateFlipType.Rotate90FlipY);
La enumeracin RotateFlipType permite especificar cmo quiere girar o voltear una imagen sobre una superficie de grficos. La tabla 26.4 enumera los mtodos restantes de la clase Image que puede usar para manipular una imagen.
Tabla 26.4. Mtodos de la clase Image Mtodo Clone FromFile FromHbitmap FromStream Descripcin Crea una copia exacta de este objeto Image Crea un objeto Image a partir del archivo especificado Crea un objeto Bitmap a partir de un identificador de Windows Crea un objeto Image a partir de la secuencia de datos especificada
589
Mtodo GetBounds
Descripcin Obtiene un rectngulo delimitador para este objeto Image en las unidades especificadas
GetEncoderParameterList Devuelve informacin sobre los parmetros que admite el codificador de imgenes especificado GetFrameCount GetPixelFormatSize GetPropertyltem GetThumbnaillmage IsAlphaPixelFormat IsCanonicalPixelFormat IsExtendedPixelFormat RemovePropertyItem RotateFlip Save SaveAdd Devuelve el nmero de marcos de la dimensin especificada Devuelve la profundidad de color (nmero de bits por pxel) del formato de pxel especificado Obtiene el elemento de propiedad especificado de este objeto Image Devuelve la vista en miniatura de este objeto Image Devuelve un valor que indica si el formato de pxel de este objeto Image contiene informacin alfa Devuelve un valor que indica si el formato de pxel es cannico Devuelve un valor que indica si el formato de pxel es extendido Quita el elemento de propiedad especificado de este objeto Image Este mtodo gira, voltea o gira y voltea el objeto Image Guarda este objeto Image en el objeto Stream con el formato especificado Agrega la informacin del objeto Image especificado a este objeto Image. El objeto Encoder Parameters especificado determina cmo se incorpora la nueva informacin a la imagen existente Selecciona el marco que especifica la dimensin y el ndice Establece el elemento de propiedad especificado en el valor especificado
SelectActiveFrame SetPropertyltem
Como ha podido ver en esta seccin, la clase Image ofrece funciones muy slidas cuando se usan con un objeto Graphics. En la siguiente seccin aprender a usar lpices y pinceles para trabajar con imgenes y dibujar formas y lneas.
590
Figura 26.4. Dibujo de una elipse usando las propiedades Color y DashStyle
591
La tabla 26.5 recoge los valores posibles de la enumeracin usada para establecer el estilo de la lnea discontinua de la elipse.
Tabla 26.5. Enumeracin DashStyle Valor Custom Dash DashDot DashDotDot Dot Solid Descripcin Especifica un estilo de guin personalizado definido por el usuario Especifica una lnea formada por guiones Especifica una lnea formada por un modelo de guin y punto que se repite Especifica una lnea formada por un modelo de guin, punto y punto que se repite Especifica una lnea formada por puntos Especifica una lnea continua
Tambin puede personalizar lneas con las propiedades StartCap y EndCap usando la enumeracin LineCap, que tambin se incluye en el espacio de nombres System.Drawing.Drawing2D. El listado 26.3 muestra algunas variaciones que usa la enumeracin LineCap para dibujar diferentes tipos de lneas, cuyo resultado puede ver en la figura 26.5.
Listado 26.3. Cmo usar la enumeracin LineCap
protected override void OnPaint(PaintEventArgs e) { Graphics g = e.Graphics; Pen p = new Pen(Color.Brown, 15); // establece la flecha p.StartCap = LineCap.ArrowAnchor; p.EndCap = LineCap.ArrowAnchor; g.DrawLine(p, 30, 30, Width-50, 30); // extremos redondeados p.StartCap = LineCap.Round; p.EndCap = LineCap.Round; g.DrawLine(p, 30, 80, Width-50, 80); // delimitador redondo p.StartCap = LineCap.RoundAnchor; p.EndCap = LineCap.RoundAnchor; g.DrawLine(p, 30, 120, Width-50, 120); // tringulo
592
p.StartCap = LineCap.Triangle; p.EndCap = LineCap.Triangle; g.DrawLine(p, 30, 150, Width-50, 150); // delimitador cuadrado p.StartCap = LineCap.SquareAnchor; p.EndCap = LineCap.SquareAnchor; g.DrawLine(p, 30, 190, Width-50, 190); }
La figura 26.5 muestra el resultado de la ejecucin del cdigo anterior usando la enumeracin LineCap.
Si se ejecuta el cdigo anterior se produce una imagen como la que aparece en la figura 26.6. Se pueden crear varios tipos de pincel. Un SolidBrush, el que se us en el anterior ejemplo, rellena una forma con un color slido. El uso de un HatchBrush permite improvisar la apariencia de sus grficos. HatchBrush usa las enumeraciones HatchStyle y HatchFill para mostrar los diferentes tipos de patrones. El listado 26.4 dibuja algunas de las variaciones de HatchBrush mediante la enumeracin HatchStyle. Esta enumeracin tiene ms de 40 miembros, de
593
modo que merece la pena buscarla en el SDK de .NET Framework. Si alguna vez necesita crear algn tipo de patrn de dibujo, puede encontrar una ayuda vital.
Figure 26.6. Elipse slida creada con pincel Listado 26.4. Cmo usar la clase HatchBrush con HatchStyles
protected override void OnPaint(PaintEventArgs e) { Graphics g = e.Graphics; HatchBrush hb = new HatchBrush (HatchStyle.Plaid, Color.AntiqueWhite, Color.Black); g.FillEllipse(hb, 30, 30, Width-50, 30); HatchBrush hb2 = new HatchBrush (HatchStyle.LargeCheckerBoard, Color.AntiqueWhite, Color.Black); g.FillEllipse(hb2, 30, 80, Width-50, 30); HatchBrush hb3 = new HatchBrush (HatchStyle.DashedHorizontal, Color.AntiqueWhite, Color.Black); g.FillEllipse(hb3, 30, 130, Width-50, 30); HatchBrush hb4 = new HatchBrush (HatchStyle.ZigZag, Color.AntiqueWhite, Color.Black); g.FillEllipse(hb4, 30, 180, Width-50, 30);
594
Si se ejecuta el cdigo anterior se produce una imagen como la que aparece en la figura 26.7.
La tabla 26.6 describe cada uno de los tipos de lpiz disponibles en la enumeracin PenType que puede usar con la clase Brush. Ya hemos visto HatchFill y SolidColor en funcionamiento. Basndose en sus descripciones, probablemente pueda imaginar los otros tipos de pincel sin verlos en funcionamiento.
Tabla 26.6. Enumeracin PenType Miembro HatchFill LinearGradient PathGradient SolidColor TextureFill Descripcin Especifica un relleno de trama Especifica un relleno de degradado lineal Especifica un relleno de degradado del trazado Especifica un relleno slido Especifica un relleno de textura de mapa de bits
El trabajo con texto y fuentes tambin requiere emplear un objeto Brush junto con un objeto Graphics. Para usar texto, se crea una instancia de la clase Font, que se incluye en el espacio de nombres System.Drawing, se definen las propiedades aspecto, estilo y tamao de texto, y luego se llama al mtodo DrawString desde el objeto Graphics que contendr el pincel. El listado 26.5 dibuja la frase C# is cool en el formulario en uso y produce algo parecido a la imagen de la figura 26.8.
595
Listado 26.5. Cmo usar el mtodo DrawString protected override void OnPaint(PaintEventArgs e) { Graphics g = e.Graphics; e.Graphics.FillRectangle( new SolidBrush(Color.White), ClientRectangle); g.DrawString("C# is cool", this.Font, new SolidBrush(Color.Black), 15, 15); }
NOTA: En .NET las fuentes que se usan en un objeto Form se heredan de la misma forma. En este ejemplo, la propiedad Font del formulario recibe el valor 24, de modo que cuando se pasa el valor this.Font al mtodo Drawstring, se usa el tamao actual de fuente del formulario.
Figura 26.8. Cmo usar el mtodo Drawstring y la clase Font para producir texto
La tabla 26.7 enumera las propiedades disponibles de la clase Font. Al establecer o recuperar estas propiedades en sus objetos Font, puede controlar completamente el aspecto del texto en la pantalla.
Tabla 26.7. Font Class Properties Propiedad Bold FontFamily GdiCharSet GdiVerticalFont Height Italic Descripcin Obtiene un valor que indica si este objeto Font est en negrita Obtiene el objeto FontFamily asociado a este objeto Font Obtiene un valor de bytes que especifica el conjunto de caracteres GDI que utiliza este objeto Font Valor booleano que indica si este objeto Font se deriva de una fuente vertical de GDI Devuelve la altura de este objeto Font Obtiene un valor que indica si este objeto Font est en cursiva
596
Descripcin Obtiene el nombre del tipo de letra de este objeto Font Obtiene el tamao eme de este objeto Font en unidades de diseo Obtiene el tamao, en puntos, de este objeto Font Obtiene un valor que indica si este objeto Font especifica una lnea horizontal de tachado de la fuente Obtiene la informacin de estilo de este objeto Font Obtiene un valor que indica si este objeto Font est subrayado Obtiene la unidad de medida de este objeto Font
Resumen
GDI+ ofrece una consistente matriz de clases que le permite escribir cualquier tipo de soporte grfico en sus aplicaciones. Este captulo present una vista general de las funciones del GDI+, pero puede hacer muchas ms cosas con los espacios de nombres System.Drawing y System.Drawing.Drawing2D que no pueden explicarse en un slo captulo. Para manipular o crear grficos usando GDI+, primero debe crear un objeto Graphics que le proporciona una superficie en la que dibujar. Una vez creado el objeto Graphics, puede usar lpices, pinceles, mapas de bits o fuentes para procesar el tipo de imagen deseado.
597
Los servicios Web son, probablemente, el rasgo ms innovador y apasionante de la iniciativa .NET de Microsoft, y probablemente afecte al modo en que las empresas interactan mediante las aplicaciones de ordenador. Pero, qu es exactamente un servicio Web? A grandes rasgos, un servicio Web es simplemente un componente de servidor que puede ser invocado en Internet. Este componente de servidor normalmente realiza un servicio fundamental del negocio, como la autentificacin del usuario, la validacin de tarjetas de crdito, el clculo del precio de un seguro de derivados, la tramitacin de una solicitud de compra de acciones o el clculo del precio de un envo el mismo da. Obviamente, la lista de posibles servicios Web es tan variada como la lista de posibles oportunidades de negocio. Los servicios Web permiten a las aplicaciones invocar servicios de negocio mediante un mecanismo basado en estndares (usando XML y HTTP), y como ver en este captulo, el modo de realizar esto supone un importante avance en la interoperabilidad de las aplicaciones. La mayora de los estndares usados para crear servicios Web se realizan con XML. Si no est familiarizado con XML, puede leer una breve introduccin en el apndice de este libro. En este captulo se estudiar qu estndares de XML controlan la definicin y el uso de los servicios Web. A continuacin crearemos un servicio Web mediante Visual Studio .NET. Por ltimo, usaremos este servicio Web en un segundo proyecto de Visual Studio .NET.
599
600
muestra este servicio a quien corresponda. Por ejemplo, una institucin financiera desarrolla un sistema de validacin de tarjetas de crdito y lo muestra a los vendedores conectados. Mostrar un servicio Web significa publicar la URL que los usuarios necesitan para invocar al servicio Web. El consumidor puede usar el servicio expuesto enviando un mensaje de peticin SOAP a la URL publicada. Al recibir una peticin SOAP escrita en XML, el componente de servidor tras el servicio Web es invocado en el servidor del creador. Los resultados de esta invocacin toman el formato de un mensaje de respuesta SOAP y se envan de vuelta al consumidor del servicio. La figura 27.1 muestra los distintos elementos que toman parte en un servicio Web.
Peticin SOAP (XML) Internet Respuesta SOAP (XML) Consumidor de servicios Web Respuesta SOAP (XML) Creador del servicio Web Peticin SOAP (XML)
Invocacin
Componente de servidor Figura 27.1. Los servicios Web constan de dos partes: un consumidor y un creador
Los servicios Web usan un conjunto de estndares para definir cmo se realiza la interaccin entre cliente y servidor. Estos estndares definen el mecanismo de transporte que se va a usar y el formato y contenido de la interaccin. En el nivel de transporte, se usa el omnipresente protocolo de Internet HTTP. El servidor y el cliente se comunican entre s mediante mensajes XML. El contenido de estos mensajes tambin est estandarizado y debe cumplir las reglas de SOAP (ms adelante se explicarn estas reglas). La naturaleza de los servicios disponibles en un servicio Web puede ser descrita en un archivo XML cuyo contenido cumpla con las reglas del Lenguaje de descripcin de servicios Web (WSDL). Por ltimo, un cliente puede descubrir dinmicamente qu servicios Web ests expuestos en un servidor recuperando el archivo XML cuyo contenido cumple con las reglas de DISCO. Observe que an no se ha mencionado ninguna tecnologa especfica de compaas. En ninguna parte se ha presupuesto el sistema operativo del cliente o el servidor, el lenguaje de programacin usado para escribir el componente del ser-
601
vidor o el mecanismo usado para invocar el componente del servidor. Estas elecciones no tienen importancia para un servicio Web. Un cliente escrito en C# que se ejecuta en Windows XP, por ejemplo, puede invocar a un servicio Web escrito en Java ejecutndose en Sun Solaris. De hecho, un cliente no tiene modo de saber qu tecnologas se usan para mostrar el servicio Web. Las implicaciones de los servicios Web son enormes. Microsoft comenz la programacin basada en componentes con la introduccin de los controles OLE (OCX), una versin depurada de los innovadores conceptos presentados por los Controles de Visual Basic (VBX). Los OCX se basan en el Modelo de objetos de componentes de Microsoft (COM) y funciona perfectamente. Sin embargo, COM y su contrapartida distribuida, el Modelo de objetos de componentes distribuido de Microsoft (DCOM), tienen un alcance limitado. Aparte de la familia de sistemas operativos Windows, muy pocos sistemas admiten COM/DCOM. Adems, aunque la familia Windows tiene una amplia aceptacin para aplicaciones de escritorio, en la modalidad de servidor se usan una gran variedad de sistemas operativos. Por ejemplo, muchas variedades del sistema operativo UNIX, como Solaris y Linux, tienen una importante presencia. Los servicios Web eliminan la necesidad de decidir el sistema operativo que se ejecuta en el servidor al integrar dos aplicaciones Web. Al usar servicios Web puede ensamblar aplicaciones Web usando componentes de otros servidores. Por ejemplo, puede usar un servicio Web de una compaa de tarjetas para validar tarjetas de crdito, un servicio Web de una compaa distribuidora para determinar los gastos de envo y as sucesivamente. sta es la esencia y la oferta de los servicios Web: la siguiente generacin de programacin basada en componentes para la siguiente generacin de aplicaciones distribuidas.
602
servicio Web, y la herramienta se encarga de casi todo el trabajo. De hecho, la herramienta hace un trabajo tan bueno que nunca ver XML al construir un servicio Web y un consumidor de servicio Web. En realidad esto es exactamente lo que va a hacer en este captulo. Sin embargo, antes de empezar, observe el concepto que hace posible toda esta automatizacin: la programacin basada en atributos. La programacin basada en atributos es un potente concepto que permite a Visual Studio .NET automatizar una gran cantidad de pesadas tareas de programacin (como crear un documento WSDL para un servicio Web). Simplemente tiene que marcar un fragmento de cdigo, como una clase o un mtodo, de un modo especial para indicar lo que quiere hacer con l. Como resultado, Visual Studio .NET genera los archivos necesarios para implementar su funcin. Un breve ejemplo servir para mostrar cmo funciona, al transformar una clase en un servicio Web. El listado 27.1 muestra cmo puede implementar un sencillo juego. De acuerdo, este juego no es muy entretenido (el jugador siempre pierde), pero este captulo trata sobre la programacin de servicios Web, no sobre programacin de juegos. Observe los elementos necesarios para convertir este fragmento de cdigo en un servicio Web y lo que Visual Studio .NET genera durante este proceso. El principal objetivo de este ejercicio es que se haga una idea de la cantidad de trabajo necesario para convertir un fragmento de cdigo completo en un servicio Web usando Visual Studio .NET.
Listado 27.1. Un sencillo juego
namespace MyFirstWebService { public class GameWS { // Ejemplo de un sencillo juego // El juego de ejemplo devuelve la cadena "Sorry, you // lose!" // Para probar este juego, pulse F5 public string Play(string opponentName) { return "Sorry " + opponentName + ", you lose!"; } } }
El primer paso para convertir este fragmento de cdigo en un servicio Web es guardar el cdigo en un nuevo archivo llamado GameWS.asmx. A continuacin, realice estos cuatro pasos: 1. Agregue un ttulo que indique tres cosas: que el archivo contiene un servicio Web, el lenguaje que usa y la clase que contiene la implementacin:
< @ WebService Language="c#" Class="MyFirstWebService.GameWS" >
603
2. Agregue una directiva System.Web.Services inmediatamente debajo del ttulo del servicio Web:
using System.Web.Services;
3. Marque la clase como servicio Web y escoja el espacio de nombres XML asociado al servicio Web:
[WebService(Namespace="http://www.boutquin.com/GameWS/")] public class GameWS : System.Web.Services.WebService
El listado 27.2 muestra el resultado final. Tambin se han cambiado los comentarios para reflejar los cambios realizados al cdigo original.
Listado 27.2. Sencillo juego expuesto como servicio Web
<@WebService Language="c#" Class="MyFirstWebService.GameWS" > using System.Web.Services;
namespace MyFirstWebService { WebService(Namespace="http://www.boutquin.com/GameWS/")] public class GameWS : System.Web.Services.WebService { // EJEMPLO DE SERVICIO WEB // El mtodo Play() devuelve la cadena "Sorry, you lose!" // Para comprobar este servicio web, pulse F5 [WebMethod] public string Play(string opponentName) { return "Sorry " + opponentName + ", you lose!"; } } }
Al construir un proyecto de servicios Web en Visual Studio, se crea automticamente un archivo de descripcin de servicios que describe el servicio Web. Este archivo es un dialecto XML llamado Lenguaje de descripcin de servicios Web (WSDL). Un archivo WSDL tiene este aspecto:
<?xml version="1.0" encoding="UTF-8"?> <methods href='http://www22.brinkster.com/boutquin/ GameWS.asmx'> <method name='Play' href='Play'> <request>
604
WSDL describe las funciones que estn expuestas (el formato que se muestra es en realidad una simplificacin del formato real, pero los conceptos siguen siendo los mismos). Puede llamar al servicio mediante una URL (en este caso, w w w 2 2 . brinkster.com/boutquin/GameWS.asmx/Play?opponentName =Pierre) o enviar un mensaje XML con el formato apropiado a la URL (mediante una instruccin post o get HTTP). Este mensaje XML puede ser un mensaje SOAP como el siguiente:
<?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <Play xmlns="http://www.boutquin.com/GameWS/"> <opponentName>Pierre</opponentName> </Play> </soap:Body> </soap:Envelope>
Al invocar el servicio mediante la URL o enviando un mensaje SOAP, se produce una respuesta XML como la siguiente:
<?xml version="1.0" encoding="UTF-8"?> (string xmlns="http://www.boutquin.com/GameWS/">Sorry you lose!</string> Pierre,
Las siguientes secciones examinan las bases de SOAP y WSDL, tras lo cul pasaremos a estudiar los detalles de la creacin e invocacin de servicios Web.
605
Esta seccin hace una breve descripcin de este vocabulario XML. WSDL usa un espacio de nombres por defecto, x m l n s = " h t t p : / / s c h e m a s . xmlsoap.org/wsdl/" . Usa un elemento raz llamado definitions y contiene varias secciones. Una de estas secciones es la seccin de servicios donde, evidentemente, se describen los servicios. El siguiente fragmento es el esqueleto de un archivo WSDL. Un archivo WSDL real usa varias declaraciones de espacio de nombres, que aqu omitimos por simplicidad:
<?xml version="1.0" encoding="UTF-8"?>
<definitions <service
xmlns='http://schemas.xmlsoap.org/wsdl/'> name="GameWS">
</service> </definitions>
El primer atributo de una descripcin de servicio define la posicin desde la que se puede llamar al servicio. Esto se describe en el elemento address dentro de un elemento port (elemento y atributo son trminos de XML, como puede ver en el apndice). El siguiente ejemplo muestra el atributo binding:
<?xml version="1.0" encoding="UTF-8"?> <definitions xmlns='http://schemas.xmlsoap.org/wsdl/'> <service name="GameWS">
Si un servicio Web est expuesto mediante una instruccin post o get de HTTP, su posicin se almacena en un elemento http:address element:
<port name="GameWSHttpPost" binding="s0:GameWSHttpPost"> <http:address location="http://www22.brinkster.com/boutquin/ GameWS.asmx"/> </port>
A continuacin, debe definir los parmetros de entrada y salida. Puede hacerlo mediante los elementos de mensajes. En estos elementos se concede un nombre a cada mensaje; y en un mensaje, se describe cada parmetro (nombre y tipo de datos):
606
</message>
A continuacin puede asociar los mensajes con el punto final de destino usando un elemento portType. En portType, se usa el nombre que asign a este punto final de destino en el elemento port ; y por cada uno de los servicios Web, crea una etiqueta de operacin que contiene un elemento input y output que el mensaje usa como atributo:
<portType name="GameWSSoap"> <operation name="Play"> <input message="PlayInput"/> <output message="PlayOutput"/> </operation> </portType>
Por ltimo, el elemento binding describe los detalles especficos importantes del mecanismo de transporte usado. Un enlace SOAP, por ejemplo, debe especificar la accin de SOAP:
<binding name="GameWSSoap" type="s0:GameWSSoap"> <soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document"/> <operation name="Play"> <soap:operation soapAction="http://www.boutquin.com/ GameWS/Play" style="document"/> <input> <soap:body use="literal"/> </input> <output> <soap:body use="literal"/> </output> </operation> </binding>
607
parmetros) al servicio Web. SOAP tambin especifica cmo se devuelve la informacin desde el servicio Web (valores de devolucin y excepciones). Los mensajes SOAP siguen un formato estndar: un envoltorio que identifica el mensaje como un mensaje SOAP, un cuerpo que contiene la principal carga til y un ttulo opcional que ofrece informacin adicional sobre el mensaje. Puede usar el ttulo para pasar informacin que no es una parte propiamente dicha de la invocacin del servidor. Por ejemplo, puede pasar la fecha y hora de la solicitud o usar autentificacin en este ttulo. El cuerpo contiene un elemento cuyo nombre concuerda con el nombre del mtodo del servidor que se est invocando. Los elementos secundarios de este elemento de mtodo tienen nombres que concuerdan con los parmetros:
<?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Header> <!--La informacin adicional se coloca aqu -->
</soap:Header> <soap:Body> <MethodName> <Param1Name>value1</Param1Name> <Param2Name>value2</Param2Name> <!-- etc. --> </MethodName> </soap:Body> </soap:Envelope>
El mismo formato se usa para enviar una respuesta. Los nombres de parmetro (en este caso, Param1Name y Param2Name) de la respuesta son, por supuesto, los parmetros de salida; o "devolucin" cuando el mtodo slo devuelve un valor (por ejemplo <return>value</return>). Cuando se produce algn error, la informacin del error se enva de vuelta en una seccin de errores. La seccin de errores se encuentra en el cuerpo SOAP:
<soap:Fault> <faultcode>x00</faultcode> <faultstring>description<faultstring> <runcode>Yes<runcode> </soap:Fault>
608
Ya est preparado para construir un sencillo servicio Web que devuelva una lista de libros usando el procedimiento almacenado: 1. Abra Visual Studio .NET y seleccione Archivo>Nuevo proyecto. 2. Seleccione Servicio Web ASP.NET como el tipo de proyecto en el cuadro de dilogo Nuevo proyecto. 3. Escriba Bookseller como nombre del proyecto (vase la figura 27.2).
609
4. Renombre el servicio Web de Service1.asmx a Books.asmx. 5. Cambie a Vista de cdigo haciendo clic en la ficha Books.asmx.cs y cambie todas las apariciones de Service1 a Books (figura 27.3).
Figura 27.3. Puede cambiar el nombre del servicio por defecto a uno ms descriptivo
610
(Exception e)
611
La propiedad oleDbConnectionString contiene la cadena de conexin a la base de datos SQL Server. En cdigo de produccin, puede usar una cuenta con derechos de seguridad apropiados (y una contrasea) en lugar de la todopoderosa cuenta "sa". El mtodo getList() abre una conexin a la base de datos SQL Server y recupera un conjunto de datos que contiene la lista de libros invocando al comando pc_getBooks. Como puede ver, usar ADO.NET es muy sencillo. 8. Eso es todo! Tambin puede agregar a la clase una declaracin de espacio de nombres, como muestra el siguiente ejemplo:
[WebService(Namespace="http://microsoft.com/webservices/")] public class Books : System.Web.Services.WebService
Los espacios de nombres son un modo de evitar la duplicidad de nombres. Se usa un nico prefijo para distinguir entre servicios Web con el mismo nombre. Por lo general, se usan direcciones URL como base para estos nombres nicos. Esto est en concordancia con el modo en que se usan las URL en los espacios de nombres XML, como se describe en el apndice. 9. Guarde el proyecto y pulse F5 para comprobar el servicio Web. Ahora que ha creado un servicio Web, intente crear una aplicacin cliente que use este servicio Web.
612
4. Seleccione Proyecto>Agregar referencia Web . 5. Haga clic en Referencias Web en Servidor local para que Visual Studio .NET detecte automticamente los servicios Web disponibles en el servidor local. 6. Seleccione http://localhost/BookSeller/BookSeller .vsdisco y haga clic en Agregar referencia. Ha importado la informacin necesaria para llamar a este servicio Web. 7. En el modo Diseo, agregue un control Label a la parte superior de la pgina y un control DataGrid debajo de la etiqueta y cambie a la vista de cdigo. 8. Agregue una declaracin using a la pgina ASP.NET: esto indica al compilador que va a usar el cdigo del servicio Web.
using BookRetailer.localhost;
9. Agregue el siguiente cdigo al mtodo Page_Init. En este ejemplo, se establece el texto Label y luego se rellena el DataGrid de un modo rpido y sencillo (esta pgina no ganar ningn premio por su apariencia):
private void Page_Init(object sender, EventArgs e) { // // CODEGEN: ASP.NET Windows Form Designer necesita esta // llamada. // InitializeComponent(); // Agregado por PGB Label1.Text = "Available Books"; Books books = new BookRetailer.localhost.Books(); DataSet bookList = books.getList(); DataGrid1.DataSource = bookList.Tables["Books"].DefaultView; DataGrid1.DataBind(); // Fin de la adicin PGB }
10. Guarde y ejecute el proyecto (utilice F5 como tecla de mtodo abreviado). Aparecer una pantalla como la que se muestra en la figura 27.4. Reflexionemos sobre lo que hemos logrado. Hemos creado un servicio Web (que puede ejecutarse en un servidor conectado a Internet). En esta seccin, cre una pgina Web (que puede ejecutarse en un servidor diferente) que usa este servicio Web para recuperar una lista de libros del primer servidor.
613
Figura 27.4. Un servicio Web en funcionamiento, tras haber recuperado una lista de libros del proveedor de servicio Web
Resumen
En este captulo ha estudiado los estndares XML que hay tras los servicios Web. Vimos las dos tecnologas opcionales que toman parte en la definicin de servicios Web: UDDI para el mecanismo de descubrimiento y WSDL para la descripcin de servicio. Tambin estudiamos el formato de mensaje que se usa durante la invocacin real de un servicio Web: SOAP. Creamos un sencillo servicio Web usando Visual Studio. Por ltimo, creamos un segundo proyecto que usaba el servicio Web que haba construido anteriormente.
614
La llegada de Internet y las intranets corporativas han llevado al desarrollo de las aplicaciones distribuidas. Una aplicacin distribuida puede acceder a la informacin de diferentes fuentes de datos que pueden estar dispersas en varias localizaciones geogrficas. Visual Studio .NET lleva las aplicaciones distribuidas a nuevos niveles al permitirle usar servicios Web y clientes de servicios Web. Los servicios Web de ASP.NET son servicios basados en XML que estn expuestos en Internet y a los que tienen acceso otros servicios Web y los clientes de servicios Web. Un servicio Web muestra mtodos Web a los que tienen acceso los clientes del servicio Web, que implementan la funcionalidad del servicio Web. Antes de Visual Studio .NET, la programacin ASP se realizaba usando VBScript. Sin embargo, con la llegada de Visual Studio .NET, puede usar dos lenguajes para la programacin ASP: Visual C# y Visual Basic .NET. Visual C# permite escribir cdigo ASP.NET para servicios y aplicaciones Web. Por ltimo, aprender a implementar la aplicacin Web mediante Visual Studio .NET. En este captulo aprender a usar C# para crear aplicaciones ASP.NET. Empezaremos creando un servicio Web en Visual C#. Tras ello, crearemos un cliente de servicio Web en C#, que en realidad es una aplicacin Web que usa el servicio Web.
617
618
varchar(n)
619
Tipo de dato
Descripcin a un tipo de datos v a r c h a r depende del tamao de los datos, a diferencia de los tipos de datos char, en los que la memoria asignada est predefinida.
Datetime Money
Se usa para almacenar datos de fecha y hora. Se usa para almacenar datos monetarios que requieren gran precisin.
TRUCO: Cada tabla debe tener al menos una columna que identifica unvocamente una fila (a la que llamaremos registro) de la tabla. Esta columna es la clave primaria de la tabla. Por ejemplo, la columna ProductID de una tabla Products identifica cada fila unvocamente y por tanto es la clave primaria. No puede haber dos valores iguales en una clave primaria.
Tras crear la base de datos, puede agregarle tablas. Agregue la tabla Products a la base de datos Sales mediante la siguiente sintaxis:
Create Table Products ( ProductID VarChar (4) Primary Key, ProductName VarChar (20), UnitPrice Integer, QtyAvailable Integer )
620
ADVERTENCIA: Para que la insercin tenga xito, los valores de la columna deben proporcionarse en el mismo orden que las columnas de la tabla. Adems, si el tipo de datos de una columna es char, varchar o datetime, debe especificar los valores entre comillas. Modificar un registro: Para modificar un registro de una tabla SQL Server, use la instruccin Update:
Update Products Set UnitPrice=75 Where ProductID="P010"
El anterior cdigo actualiza el precio por unidad del registro cuya identificacin de producto es P010 hasta 75. Eliminar un registro: Para eliminar un registro de una tabla, use la instruccin Delete . Por ejemplo, para eliminar un registro de la tabla Products con la identificacin de producto P011, puede especificar la siguiente instruccin:
Delete From Products where ProductID="P011"
621
adecuados. Puede crear un procedimiento almacenado usando la instruccin Create Procedure. Use el siguiente cdigo para crear un procedimiento almacenado que acepte ProductID como parmetro y devuelva el precio por unidad del registro que concuerde con el ProductID:
Create Procedure ProductPrice (@id char As Select UnitPrice From Products Where ProductID=@id Return (4))
El procedimiento requiere un parmetro, @id, en el momento de la ejecucin. Los procedimientos almacenados son particularmente tiles cuando se necesitan realizar varias tareas consecutivas en una base de datos. Por ejemplo, cuando quiera cancelar la reserva de un pasajero, querr calcular la tarifa que debe devolverse al cliente y borrar su reserva de la tabla de reservas. Al mismo tiempo, tambin deber actualizar el estado de los otros pasajeros que puedan estar en lista de espera para entrar en la lista de pasajeros. En lugar de especificar consultas de SQL cada vez que quiera cancelar una reserva, puede usar un procedimiento almacenado para cancelar la reserva de un pasajero. ADVERTENCIA: Cada procedimiento almacenado debe terminar una instruccin Return. Para ejecutar el procedimiento anterior para que muestre el precio del producto con la identificacin ID P010, use el siguiente cdigo:
Execute ProductPrice "P010"
622
4. En el editor de consultas, introduzca las siguientes instrucciones para crear la base de datos Sales y agregar la tabla Products a la base de datos:
Create database Sales GO Use Sales Create Table Products ( ProductID VarChar (4) Primary Key, ProductName VarChar (20), UnitPrice Integer, QtyAvailable Integer ) GO
5. Seleccione Query>Execute para ejecutar la consulta. Tras ejecutar la consulta, la estructura de la base de datos est creada. Ahora estamos listos para crear el servicio Web (la primera de las aplicaciones ASP.NET que crear este captulo). El servicio Web que se crea en este captulo agrega registros a la tabla Products de la base de datos Sales que creamos anteriormente en esta misma seccin.
623
NOTA: Puede que tenga que esperar bastante tiempo mientras Visual Studio .NET crea el servicio Web. Una vez que Visual Studio .NET ha creado el servicio Web, puede configurarlo para que gestione datos en el servidor. Esto lo haremos en la siguiente seccin.
Los pasos para agregar el control SqlDataAdapter son los siguientes: 1. Seleccione Ver>Cuadro de herramientas para abrir el cuadro de herramientas. 2. En el cuadro de herramientas, haga clic en Datos para activar la ficha Datos. 3. Arrastre el control SqlDataAdapter desde el cuadro de herramientas hasta el Diseador de componentes. 4. Al arrastrar el control SqlDataAdapter desde el cuadro de herramientas, se inicia el asistente para la configuracin del adaptador de datos. En la pantalla de bienvenida del asistente, haga clic en Siguiente. 5. Aparecer el cuadro de dilogo Elegir la conexin de datos del asistente, como se ilustra en la figura 28.1. Haga clic en Nueva conexin para crear una nueva conexin usando el controlador OleDbDataAdapter. 6. Se abrir el cuadro de dilogo Propiedades de vnculo de datos. Por defecto, este cuadro de dilogo tiene seleccionada la ficha Conexin. Especifique el nombre del servidor SQL en el cuadro de dilogo Seleccione o escriba un nombre de servidor.
624
7. Seleccione el nombre de usuario y la contrasea para conectarse al servidor SQL Server y seleccione la base de datos Sales en la lista desplegable Seleccione la base de datos en el servidor. La figura 28.2 muestra la pantalla Propiedades de vnculos de datos completa. Haga clic en Aceptar.
Figura 28.2. Conecte con el origen de datos usando el cuadro de dilogo Propiedades de vnculo de datos
625
8. El adaptador de datos que ha configurado aparecer en el cuadro de dilogo Elegir la conexin de datos. Haga clic en Siguiente para continuar. 9. Se abrir el cuadro de dilogo Elegir la conexin de datos. Para usar una consulta SQL para recuperar datos de la base de datos, mantenga la opcin por defecto, Usar instrucciones de SQL, y haga clic en Siguiente. 10. Se abrir el cuadro de dilogo Generar las instrucciones SQL. En este cuadro de dilogo, escriba la consulta Select * from Products y haga clic en Siguiente. 11. Se abrir el cuadro de dilogo Ver resultados del asistente. Este cuadro de dilogo resume las opciones que ha seleccionado en los anteriores cuadros de dilogo del asistente. Haga clic en Finalizar para dar por finalizado el asistente de configuracin del adaptador de datos. Tras completar el asistente de configuracin del adaptador de datos, el control SqlDataAdapter est configurado para su aplicacin. Como se puede apreciar en la figura 28.3, los controles sq1DataAdapter1 y sqlConnection1 se han agregado a su aplicacin.
A continuacin, agregue el control SqlCommand al servicio Web. El control SqlCommand se usa para especificar comandos que necesitan ser ejecutados en
626
el origen de datos. Siga estos pasos para agregar un control SqlCommand a su aplicacin: 1. Arrastre el control SqlCommand desde el cuadro de herramientas al diseador de componentes. 2. Abra la ventana Propiedades y seleccione sqlConnection1 para la propiedad de conexin del control SqlCommand. A continuacin, debe generar un conjunto de datos para almacenar los datos que recuperen los controles de datos: 1. Para generar el conjunto de datos, seleccione el control SqlCommand1 que agreg en los pasos anteriores y seleccione la opcin de men Datos>Generar conjunto de datos. 2. Se abrir el cuadro de dilogo Generar conjunto de datos. En este cuadro de dilogo, ya est seleccionada la tabla Products de la base de datos. Seleccione la casilla Agregar este conjunto de datos al diseador y haga clic en Aceptar para generar el conjunto de datos y agregarlo al diseador de componentes. Los cuatro controles que ha agregado al servicio Web ahora son visibles para el diseador de componentes. Ahora necesita codificar los mtodos del servicio Web, como ver en la siguiente seccin.
627
Tras introducir la lnea de cdigo anterior, el espacio de nombres del servicio Web es http://ServiceURL.com/products/ y se ha agregado una descripcin al servicio Web. A continuacin, escriba el cdigo para agregar productos a la tabla Products. Este cdigo necesita escribirse justo debajo de la declaracin del servicio Web. El cdigo del mtodo AddProduct, que agrega detalles del producto a la tabla Products, es el siguiente:
[WebMethod(Description="Specify the product ID, product name, unit price, and quantity to add it to the Sales catalog")] public string AddProduct(string PID, string ProductName, int Price, int Qty) try { ProductName=ProductName.Trim(); if (Price<0) return "Please specify a valid value for price"; if (Qty<0) return "Please specify a valid value for quantity"; sqlConnection1.Open(); sqlCommand1.CommandText="INSERT INTO Products(ProductID, ProductName, UnitPrice, QtyAvailable) VALUES ( ' " + PID + "', '" + ProductName + "', '" + Price + "', '" + Qty + "')"; sqlCommand1.ExecuteNonQuery(); sqlConnection1.Close(); return "Record updated successfully"; } catch (Exception e) { return e.Message; } }
El cdigo anterior usa el control sqlConnection1 para agregar un registro a la tabla Products. Al agregar registros a un servicio Web, se usan los valores que la funcin AddProduct proporciona como parmetros. Mientras codifica el mtodo AddProduct del servicio Web, puede codificar otros mtodos para recuperar detalles de producto de la base de datos Products o realizar otras acciones personalizadas. Para probar el servicio Web tras agregarle los mtodos, siga estos pasos: 1. Seleccione Generar>Generar solucin para generar el servicio Web. 2. Para empezar a depurar el servicio Web, seleccione la opcin de men Depurar>Iniciar. El servicio Web se abre en Internet Explorer. El primer cuadro de dilogo muestra los mtodos que codific para su servicio Web.
628
Para comprobar el mtodo AddProduct, siga estos pasos: 1. Haga clic en AddProduct en el cuadro de dilogo Servicio Web Service1. 2. Se abrir el cuadro de dilogo AddProduct, como muestra la figura 28.4. En este cuadro de dilogo, puede invocar la funcin AddProduct tras proporcionar los parmetros requeridos para probar su resultado.
Figura 28.4. Especifique los parmetros para la funcin AddProduct para comprobarla
3. Escriba P002, PDA, 100 y 200 como los parmetros para el PID, ProductName, Price y Qty respectivamente y haga clic en Invoke para probar su resultado. 4. Cuando se logra agregar el registro al servicio Web, se consigue un resultado como el que se muestra en la figura 28.5. Tras probar su servicio Web, puede crear un cliente de servicio Web para acceder al servicio Web.
629
Figura 28.5. Este resultado se consigue cuando se logra agregar un registro a un servicio Web
Para crear un cliente de servicio Web slo tiene que seguir estos pasos: 1. Cree un nuevo proyecto de Aplicacin Web ASP.NET. 2. Agregue una referencia Web a la aplicacin Web. 3. Implemente los mtodos del servicio Web en la aplicacin Web. La siguiente seccin estudia detenidamente cada uno de estos pasos.
630
Figura 28.6. Los servicios Web disponibles se muestran en el cuadro de dilogo Agregar referencia Web
La referencia al servicio Web que se agrega aparece en el explorador de soluciones. Por defecto, recibe el nombre localhost. Puede cambiar el nombre del
631
servicio Web por un nombre de su eleccin. En la figura 28.7, recibe el nombre OS1 .
Para disear el formulario, puede agregar los controles que aparecen en la tabla 28.2. Tras disear el WebForm, escriba el cdigo para el evento Click del botn Submit. Cuando el usuario haga clic en el botn Submit, el cdigo del evento Click har lo siguiente:
632
1. Crear una instancia del servicio Web. 2. Llamar al mtodo AddProduct del servicio Web, pasando como parmetros al WebForm los valores introducidos por el usuario.
Tabla 28.2. Controles de formularios Web Tipo de control Label Propiedades cambiadas Se han agregado seis etiquetas: el encabezamiento principal de la pgina, Product ID, Product Name, Price, Quantity y Message, respectivamente. Se ha cambiado el Id. de la ltima etiqueta a LabelMessage y se ha limitado su texto. ID=PID ID=Product.Name ID=Price ID=Quantity ID=Submit Text=Submit
3. Mostrar el valor devuelto desde el mtodo AddProduct en la etiqueta LabelMessage. A continuacin se muestra el cdigo del evento Click del botn Submit que cumple las tareas indicadas anteriormente: TRUCO: Para introducir el cdigo del evento Click del botn Submit, haga doble clic en el botn Submit en la vista diseo.
private void Submit_Click(object sender, System.EventArgs e) { OS1.Service1 Web1=new OS1.Service1(); LabelMessage.Text=Web1.AddProduct(PID.Text, ProductName.Text, Convert.ToInt32(Price.Text), Convert.ToInt32 (Quantity.Text)); }
4. Seleccione Depurar>lnicio para ejecutar el servicio Web. El resultado de la aplicacin Web aparece en la figura 28.9. Especifique valores en los cuadros de texto Product ID, Product Name, Price y Quantity y haga clic en Submit. La aplicacin Web agregar la informacin
633
requerida a la base de datos Sales y aparecer un mensaje en la etiqueta LabelMessage, como muestra la figura 28.10.
Figura 28.10. Puede agregar un registro a la base de datos Sales mediante la aplicacin Web
634
La siguiente seccin describe cmo crear un proyecto de implementacin para una aplicacin Web ASP.NET.
635
1. En el Explorador de soluciones, haga clic en la solucin OrdersWebApplication . Desde el men emergente que aparece, seleccione Agregar>Nuevo proyecto. 2. En el cuadro de dilogo Nuevo proyecto que se abre, seleccione Proyectos de instalacin e implementacin en el cuadro Tipos de proyecto. 3. En el cuadro Plantillas, seleccione Proyecto de programa de instalacin Web. Escriba como nombre del proyecto WebSetupDeploy y haga clic en Aceptar. El nuevo proyecto se agregar a la ventana del explorador de soluciones. Por defecto, se abre el Editor del sistema de archivos para el proyecto de implementacin. El Editor del sistema de archivos le ayuda a agregar archivos al proyecto de implementacin. 4. En el Editor del sistema de archivos (el cuadro a la izquierda), seleccione Carpeta de aplicacin Web . 5. Para agregar el resultado de OrderWebApplication al proyecto de implementacin, seleccione Accin>Agregar>Resultados de proyectos. Se abrir el cuadro de dilogo Agregar grupo de resultados del proyecto. 6. En el cuadro de dilogo Agregar grupo de resultados del proyecto, seleccione OrderWebApplication en la lista desplegable Proyectos, si es necesario. 7. Mantenga presionada la tecla Control y seleccione Resultado principal y Archivos de contenido de la lista de archivos. Las opciones seleccionadas aparecen en la figura 28.11.
Figura 28.11. El cuadro de dilogo Agregar grupo de resultados del proyecto muestra las opciones que ha seleccionado
636
8. Haga clic en Aceptar para cerrar el cuadro de dilogo Agregar grupo de resultados del proyecto. 9. En la ventana del editor del sistema de archivos, haga clic con el botn derecho del ratn en la Carpeta de aplicacin Web. En el men desplegable, seleccione la opcin Propiedades para abrir la ventana Propiedades . 10. Escriba OrderWebApplication como nombre del directorio virtual en la propiedad VirtualDirectory y cierre la ventana Propiedades. 11. Seleccione la opcin de men Generar >Generar solucin. Se ha creado un archivo Microsoft Installer (MSI) para su proyecto. Puede hacer doble clic en el archivo para instalar su aplicacin en un equipo.
Resumen
El papel de C# en ASP.NET es muy til para los servicios y aplicaciones Web. Puede crear un servicio Web mediante la sintaxis del lenguaje C# en ASP.NET.
637
Tambin puede crear una aplicacin Web que acceda al servicio Web y aproveche su funcionalidad. Cuando crea una aplicacin en ASP.NET, primero disea la base de datos que debe usar la aplicacin. A continuacin, crea un nuevo proyecto en ASP.NET mediante la Aplicacin Web de ASP.NET o las plantillas de proyecto servicio Web ASP.NET. Tras crear un proyecto en ASP.NET, use los controles de datos proporcionados por Visual Studio .NET para conectar con un origen de datos y utilizarlo. Si ha creado un servicio Web, tambin puede crear una aplicacin Web para utilizar los mtodos Web proporcionados por el servicio Web.
638
29 Cmo
construir controles personalizados
En este captulo aprender a crear controles personalizados. Estos controles pueden tomar la forma de un componente visible que puede agregarse a un formulario o simplemente ser un objeto de clase encapsulado en una DLL. En cada instancia, estos controles aprovechan su conocimiento de la reutilizacin de cdigo cuando se empieza a empaquetar y distribuir su funcionalidad como un control que puede ser usado una y otra vez.
641
Propiedades
Las propiedades se usan principalmente para establecer y recuperar parmetros de control. Cuando a las propiedades se les conceden los accesos de lectura y de escritura, se componen de dos funciones llamadas get y set. Dado que una propiedad debe llamar a un mtodo cuando se le asignan o se recuperan de ella valores, puede realizar clculos u otras funciones tiles inmediatamente. En este aspecto, son muy diferentes de los campos. Un ejemplo de propiedad puede ser la propiedad BackColor. Al usar esta propiedad, puede especificar el color que se va a usar como color de fondo de un control o comprobar cul es el actual color de fondo. Las propiedades se implementan fcilmente en un proyecto usando el sencillo asistente de Visual Studio .NET. En la ventana Vista de clases, haga clic con el botn derecho del ratn en la clase del control y escoja Agregar>Agregar clase. El asistente, que aparece en la figura 29.1, le permite establecer el tipo de acceso, el tipo de propiedad (int, bool y similares), el nombre de la propiedad, los descriptores de acceso y los modificadores.
Figura 29.1. El asistente para agregar propiedades permite agregar una propiedad a su proyecto
642
{ } }
En este cdigo faltan algunos elementos bsicos. Cuando se establece el valor de una propiedad, la informacin no se almacena. Debe crear antes una variable de clase privada que pueda ser usada por esta propiedad para almacenar y recuperar el valor. En este caso, simplemente debe incluir la siguiente declaracin en la parte superior de su declaracin de clase:
private int m_TestProperty;
Desde el mtodo set de su propiedad, puede asignar los datos de su variable privada usando la palabra clave value:
set { m_TestProperty = value; }
Una vez que hemos almacenado el valor de la propiedad en la variable de clase privada, debe asegurarse de que el mtodo Get devuelve el valor correcto al ser usado. El siguiente cdigo logra esto; simplemente devuelve el valor de la variable privada del bloque get, como muestra el siguiente fragmento:
get { return }
m_TestProperty
Tambin es til describir las propiedades de los componentes existentes en su control. Por ejemplo, imagine que ha creado un control que contiene una etiqueta. Su control debe contener una propiedad Font que obtenga y establezca la fuente del componente de la etiqueta de su control. Para permitir que el contenedor manipule la fuente de su etiqueta, simplemente cree una propiedad como la siguiente:
public Font CurrentFont { get { return label1.Font; } set { label1.Font = value; } }
Este cdigo crea una propiedad llamada CurrentFont, que devuelve la fuente actual del control label cuando se le solicita. Cuando el contenedor
643
desea cambiar la fuente, simplemente tiene que asignar ese valor a la fuente actual del control de la etiqueta.
Mtodos
Un mtodo se define como una funcin que forma parte de una clase. Los mtodos se implementan del mismo modo que en muchos otros lenguajes, como C++, C, VB y similares. Los mtodos suelen usarse para iniciar una accin dentro de un control. Un ejemplo podra ser el mtodo Start del control Timer. Puede agregar un mtodo a un control de una de estas dos formas. Puede usar el asistente para mtodos de C#, al que se puede acceder mediante el Explorador de soluciones mientras se est en la Vista de clases, o puede simplemente agregar una funcin pblica al proyecto. El asistente para mtodos de C#, que se muestra en la figura 29.2, permite especificar el nombre del mtodo, el tipo devuelto, el mtodo de acceso y la informacin de parmetros de una forma muy sencilla.
Figura 29.2. El asistente para mtodos de C# crea la estructura bsica del cdigo necesario para el mtodo
El inconveniente del asistente para mtodos es que slo proporciona tipos simples de devolucin. Por ejemplo, imagine que quiere un mtodo que devuelva un valor de tipo Font. El cuadro combinado Tipo de valor devuelto no ofrece el tipo Font como opcin. Cuando haga pruebas con el asistente aprender que puede introducir sus propios tipos en estos cuadros; sus opciones no se limitan a las que le ofrecen los cuadros. Las opciones que presentan slo son los tipos ms usados para que el programador inexperto no se confunda. Despus de todo, si el asistente mostrara
644
todas las opciones posibles, tardaramos mucho tiempo en desplazarnos por toda la lista en busca del tipo deseado. .NET Framework tambin aporta a los mtodos algunas funcionalidades muy necesarias. Ahora los mtodos pueden sobrecargarse. Es decir, pueden tener el mismo nombre mientras su lista de parmetros sea diferente.
Campos
Los campos son simples variables pblicas definidas en un control. La aplicacin contenedora puede asignar al campo un valor determinado y recuperarlo. Como slo son variables, no pueden tener funciones asociadas a ellos. Los campos no pueden realizar clculos ni llamar a mtodos cuando se les asigna un valor o se recupera de ellos un valor. Hay dos tipos de campos: de instancia y estticos. Un campo es, por defecto, un campo de instancia. Un campo de instancia puede contener sus propios datos en cada objeto que lo contenga. Cuando un campo es declarado como esttico, contiene los mismos datos en todas las instancias del objeto. Un campo esttico no suele ser aconsejable porque otras instancias del objeto pueden cambiar el valor y las instancias restantes no se percatarn de ello. Los campos proporcionan elementos de cdigo de bajo nivel, pero tambin estn limitados en trminos de funcionalidad. Puede asignar y recuperar los valores de un campo, pero al hacerlo no puede desencadenar otras acciones dentro del control. Los campos son tiles cuando se necesita asignar parmetros de operacin a un control.
Eventos
Para definir un evento, antes debe hacer una declaracin de clase pblica como la siguiente:
public event EventHandler Expired;
Puede usar eventos con controles de Windows de uno de estos dos modos: puede hacer clic en el icono Event en la ventana Propiedades y hacer doble clic en el evento para agregar el controlador automticamente, o puede crear un controlador de eventos usted mismo. Un evento bsico se declara como un tipo EventHandler, pero tiene muchos ms tipos de eventos entre los que elegir y cada uno tiene una firma nica. El parmetro de cada evento contiene una estructura System.EventArgs. Esta estructura permite al control pasar informacin al evento, definiendo as muchos aspectos, como qu desencadena el evento, qu control hace que se desencadene el evento, y otros similares. Antes de escoger un tipo de evento para un control, asegrese de que conoce los diferentes tipos, adems de los parmetros que pasa cada uno.
645
Una vez definidos los requisitos, puede empezar a crear su control. Abra Visual Studio .NET y cree un nuevo proyecto de biblioteca de control de Windows. Llame a este proyecto CountDown, como muestra la figura 29.3.
646
1. Cuando Visual Studio haya creado la estructura bsica de su control, se le mostrara un UserControl. sta ser la interfaz visual para su control CountDown. Aqu es donde se coloca cualquier elemento visual que quiera que el usuario vea o con el que quiera que interacte. 2. Lo primero que debe hacer es colocar un control Label en UserControl, como muestra la figura 29.4. 3. Cambie la propiedad Anchor de este control Label a Top.Left. Bottom.Right. Esto asegura que cuando el control cambie de tamao, la etiqueta tambin lo haga. Debe tener mucho cuidado cuando agregue componentes existentes a un control, porque muchos elementos pueden cambiar de aspecto cuando el usuario coloca un control en el formulario. 4. Ahora debe agregar un control Timer al proyecto (desde el cuadro de herramientas). El control Timer permite contar un segundo cada vez hasta que crea que el tiempo se ha agotado. El aspecto externo del control ya est completo. Ahora debe agregar el cdigo que apoye a este control.
5. En el listado 29.1 declar una variable de miembro para que contuviese el nmero de segundos desde los que el temporizador de cuenta atrs empezara a contar. Tambin debe declarar un EventHandler para que se dispare cuando el control llegue al final de la cuenta atrs. Coloque el siguiente cdigo inmediatamente debajo de la declaracin de clase de UserControl :
647
Listado 29.1. Miembros pblicos private int m_Seconds; public event EventHandler Expired;
6. La variable m_Seconds no necesita estar expuesta al contenedor de controles, sino slo al propio control, por lo que vamos a declarar la variable como privada. Al declarar un EventHandler pblico, Expired aparece en la ventana de eventos, de modo que puede programar este evento con slo hacer doble clic en l. 7. En el evento Tick del objeto Timer, debe restar un segundo del nmero total de segundos. Luego puede comprobar si el nmero total de segundos ha llegado a cero. En caso afirmativo, debe detener el temporizador y desencadenar el evento Expired. Despus, puede actualizar el control Labe1 para que muestre el valor del control CountDown Timer en ese momento. En la ventana Propiedades, haga clic en el cuadro combinado Selector de objetos y seleccione el control Timer. A continuacin haga clic en el botn Eventos de la barra de herramientas en la parte superior de la ventana Propiedades. Ver todos los eventos que muestra el control Timer. Haga doble clic en el evento Tick. Coloque el cdigo del listado 29.2 en el controlador de eventos Tick del control Timer.
Listado 29.2. El evento Tick actualiza el control private void timer1_Tick(object sender, System.EventArgs e) { m_Seconds -= 1; if (m_Seconds <= 0) { timer1.Stop(); Expired(this, e ) ; } label1.Text = m_Seconds.ToString(); }
8. Su control tambin necesita un mtodo public() que permita que el contenedor inicie el control para que empiece a contar segundos. El listado 29.3 define el intervalo del control Timer (en milisegundos) y luego inicia el control Timer. Una vez que el temporizador ha sido iniciado, debe actualizar el control Label, porque no se actualizar hasta un segundo despus de que el temporizador desencadene el evento Tick por primera vez. 9. Para agregar un mtodo al control, cambie la vista del explorador de soluciones a vista de clase haciendo clic en la ficha Vista de clase. Expanda la vista de clase hasta que encuentre las clases de controles. Haga clic con el botn derecho del ratn, seleccione Agregar y luego Agregar mto-
648
10. Llame a este mtodo Start y asegrese de que es un mtodo pblico. Puede dejar todos los otros valores que se ofrecen por defecto. Tras crear el nuevo mtodo, agregue el cdigo del listado 29.3 al listado del cdigo del mtodo.
Listado 29.3. Mtodo Start
public void Start() { timer1.Interval = 1000; timer1.Start(); label1.Text=m_Seconds.ToString(); }
11. Un mtodo Stop() complementa al mtodo Start(). El mtodo Stop() debe detener en primer lugar al control Timer para que el evento Tick no vuelva a desencadenarse. Tambin debe asignar el valor de la propiedad Seconds a la variable de miembro m_Seconds . Esto garantiza que si el control se inicia de nuevo, lo har desde el principio y no desde donde se encontraba cuando fue detenido. Cree un nuevo mtodo llamado Stop(), del mismo modo que cre el mtodo Start() . Agregue el cdigo del listado 29.4 a este nuevo mtodo.
Listado 29.4. Mtodo Stop
public void Stop() {
649
An no ha creado la propiedad Seconds. La propiedad Seconds permite conseguir y establecer el nmero total de segundos desde los que el control empezar la cuenta atrs. Una propiedad se crea de una manera muy parecida a un mtodo. 1. Desde la vista de clase del Explorador de soluciones, haga clic con el botn derecho y escoja Agregar y luego Agregar propiedad. El asistente para propiedades de C#, mostrado en la figura 29.6, se abrir.
Figura 29.6. En el asistente para propiedades de C#, llame a esta propiedad Seconds. Asegrese de que esta propiedad tenga acceso pblico y de que el tipo de la propiedad sea int
2. Queremos que la aplicacin contenedora de los controles pueda asignar un valor a esta propiedad o leer un valor de ella. Por tanto, queremos que el descriptor de acceso sea get/set. Agregue el cdigo del listado 29.5 al cdigo creado por el asistente para propiedades de C#.
Listado 29.5. Propiedad Seconds public int Seconds { get { return m_Seconds; } set {
650
m_Seconds = value; } }
Preste especial atencin a la palabra clave value. Cuando se pasa un valor a una propiedad (por ejemplo, set), puede ser recuperado mediante la palabra clave value. Hasta ahora, su control no es muy sofisticado, ni presenta buen aspecto, pero funciona. En este momento, puede crear el nuevo control seleccionando Generar solucin en el men Generar. Ahora genere una prueba de carga que use este control y que permita comprobar todos los mtodos, propiedades y eventos del control.
651
6. Haga clic en el botn Examinar y busque el control temporizador CountDown. Cuando lo encuentre, seleccinelo y mrquelo. Haga clic en Aceptar. 7. En la lista de controles, ver este control; marque el cuadro junto a l. Ahora puede hacer clic en Aceptar en este formulario. En el Cuadro de herramientas, ver el control temporizador CountDown en la lista. 8. Haga doble clic en el control para agregarlo a su proyecto. Mientras est en el cuadro de herramientas, agregue tambin dos botones a su formulario. El primer botn se usa para iniciar el temporizador de cuenta atrs y el segundo botn se usa para detener el control Countdown. Alinee y establezca las propiedades de texto como se muestra en la figura 29.7.
9. Haga doble clic en el botn Start de su aplicacin para que pueda empezar a codificar. Agregue el cdigo del listado 29.6 al evento Click del botn Start.
Listado 29.6. Iniciar el control private void button1_Click(object sender, System.EventArgs e) { cdTimer1.Seconds=60; cdTimer1.Start(); }
10. El botn Start sirve slo para una cosa: preparar e iniciar el control CountDown. Para empezar, establezca en 60 el nmero de segundos y luego llame al mtodo Start del control. 11. Tambin necesita un modo de detener el control. Haga doble clic en el botn Stop para insertar cdigo en el evento Click de este botn. Inserte el siguiente cdigo del listado 29.7 en el evento Click del botn Stop.
Listado 29.7. Detener el control private void button2_Click(object sender, System.EventArgs e) { cdTimer1.Stop(); }
652
12. El evento Expired del control CountDown debe realizar alguna accin que le informe de que la cuenta atrs ha llegado a cero. En la ventana Propiedades, haga clic en el botn Eventos en la barra de herramientas. A continuacin, haga doble clic en el evento Expired. Es en este cdigo en el que debe codificar esa accin especial. No intente nada extravagante; simplemente vamos a cambiar el ttulo de su formulario a Done. Inserte el cdigo del listado 29.8 en el evento Expired del control CountDown.
Listado 29.8. Cdigo del evento Expired
private void cdTimer1_Expired(object sender, System.EventArgs e) { this.Text="Done!"; }
Su control y su aplicacin de prueba ya estn listos para ser ejecutados. Pulse F5 para ejecutar la aplicacin. Cuando se abra la aplicacin, haga clic en el botn Start para iniciar la cuenta atrs. Su aplicacin de prueba deber empezar a contar hacia atrs. Cuando la cuenta atrs llegue a cero, el ttulo del formulario deber cambiar gracias al evento Expired.
653
sual Studio crear el cdigo que ser la estructura bsica mostrado en el listado 29.9.
Listado 29.9. Cdigo creado para una biblioteca de clases por Visual Studio
using System; namespace Temperature { /// <summary> /// Descripcin breve de Class1. /// </summary> public class Class1 { public Class1() { // // TODO: Agregar aqu la lgica del constructor // } } }
Visual Studio ha creado un espacio de nombres, una clase y un constructor de clase. La clase y el constructor de clase son pblicos. Esto no es una coincidencia. Cualquier aplicacin que use esta biblioteca necesita acceder tanto a la clase como a los miembros especficos dentro de la clase. 1. Para empezar nuestro ejemplo, elimine la clase y el constructor de clase y reemplcelos por el cdigo del listado 29.10.
Listado 29.10. La clase y el constructor Calc
public class Calc { public Calc() { } }
2. La clase Calc es donde colocar el mtodo de clculo del efecto de viento. De esta forma, estamos seguros de que nuestro clculo del efecto de viento queda separado de los mtodos de conversin que pronto agregar. Inserte el cdigo del listado 29.11 en la clase Calc.
Listado 29.11. Clculo del efecto de viento actual
public double WindChill(double DegreesF, double WindSpeedMPH) { double WindRaised; WindRaised = System.Math.Pow(WindSpeedMPH, .16);
654
3. Tambin agregaremos algunos mtodos que no son clculos, sino conversiones. Por tanto, deber aadir una nueva clase. Esta nueva clase se llamar Conversion, como se puede ver en el listado 29.12. Agregue la nueva clase y el constructor de clase al proyecto.
Listado 29.12. Agregar una segunda clase a la biblioteca de clases
public class Conversion { public Conversion() { } }
4. Inicie la nueva clase con una funcin que calcule la temperatura en grados Fahrenheit a partir de la temperatura en grados Celsius. Es poco probable que el valor devuelto o el parmetro sean valores enteros, de modo que es importante que ambos sean valores double. El listado 29.13 contiene el listado completo de la funcin. Agregue este cdigo a la clase Conversion .
Listado 29.13. Conversin de Celsius a Fahrenheit
public double CelcToFahr(double Celsius) { return (9/5) * (Celsius + 32); }
5. Como ha incluido una funcin de conversin de Celsius a Fahrenheit, es lgico incluir la conversin inversa. Agregue el mtodo del listado 29.14 a la clase Conversion.
Listado 29.14. Conversin de Fahrenheit a Celsius
public double FahrToCelc(double Fahrenheit) { return (5/9) * (Fahrenheit - 32); }
Con esto finaliza la funcionalidad que queremos incluir en el objeto de la biblioteca, de modo que ya puede crear la DLL seleccionando Generar solucin en el men Generar. Una vez que todo est en su sitio, observe cmo se utilizan los mtodos pblicos que hay en su interior: 1. Cree un nuevo proyecto de aplicacin de consola y llmelo DLLTest. En el men Proyecto, seleccione Agregar referencia y a continuacin haga
655
clic en el botn Examinar. Busque la DLL que acaba de crear y haga doble clic sobre ella. Ya puede hacer clic en Aceptar para salir de la ventana Agregar referencia. 2. Tras agregar una referencia, puede simplemente crear una nueva variable del tipo adecuado y hacer referencia al mtodo que prefiera. El listado 29.15 contiene el cdigo fuente completo de la aplicacin DLLTest.
Listado 29.15. Cdigo fuente de la aplicacin DLLTest
using System; namespace DLLTest { /// <summary> /// Descripcin breve de Class1. /// </summary> class Class1 { static void Main(string[] args) { Temperature.Calc WCMethod = new Temperature.Calc(); Temperature.Conversion ConvMethod = new Temperature.Conversion(); Console.WriteLine("Wind chill at 50 degrees with 35 MPH : {0} Degrees", WCMethod.WindChill(50, 35)); Console.WriteLine("32 Degrees Fahrenheit to C e l s i u s : {0} Degrees", ConvMethod.FahrToCelc(32)); Console.WriteLine("0 Degrees Celsius to Fahrenheit: {0} Degrees", ConvMethod.CelcToFahr(0)); } } }
El acceso a los mtodos es una tarea muy sencilla: cree nuevas variables del tipo Temperature.Conversion y Temperature.Calc y luego acceda a sus mtodos pblicos. Esta aplicacin de ejemplo calcula el efecto del viento con una temperatura de 50 grados y una velocidad del viento de 35 millas por hora; a continuacin calcula la temperatura en grados Celsius y Fahrenheit. El resultado de esta aplicacin se muestra en la figura 29.8.
Resumen
.NET presenta algunas innovaciones extremadamente importantes en la forma de construir controles. A medida que cree controles para sus propios proyectos o para implementar en la Web, ir descubriendo algunos avances en la programacin, como el objeto State, que le ahorrarn grandes cantidades de tiempo.
656
c:\ C:\WINDOWS\System32\cmd.exe C:\Source\DLLTest\bin\Debug>dlltest Wind chill at 50 degrees with 35 MPH: 41.425231535302 Degrees 32 Degrees Fahrenheit to Celsius: 0 Degrees 0 Degrees Celsius to Fahrenheit: 32 Degrees C:\Source\DLLTest\bin\Debug>_
657
La red inalmbrica
El concepto de acceso a Internet mediante dispositivos mviles lleva mucho tiempo entre nosotros pero ha tardado en ser aceptado. Las herramientas adecuadas para crear contenidos Web han sido bastante escasas. .NET Framework, junto con el Mobile Internet Toolkit de Microsoft, permite crear emocionantes aplicaciones Web que se pueden usar en distintos tipos de dispositivos mviles. Estos dispositivos mviles incluyen dispositivos Windows CE, dispositivos Pocket PC y muchos telfonos mviles. Evidentemente, la mayora de los dispositivos mviles estn muy limitados en comparacin con los navegadores Web a los que estamos acostumbrados. No es slo que los dispositivos mviles dispongan
659
de menos espacio para el contenido, muchos de estos dispositivos carecen de color o, en alguna otra forma, de la capacidad para mostrar grficos. Este captulo empieza estudiando el software que necesita, junto con los emuladores adecuados que puede usar para probar su cdigo, en caso de que no tenga acceso a un dispositivo mvil con acceso a Internet.
Emuladores
Los emuladores permiten escribir aplicaciones para dispositivos y probarlas sin tener que comprar realmente uno de los dispositivos. Hay emuladores para telfonos mviles, PDA, equipos de escritorio y dispositivos intermedios. Los siguientes apartados tratan sobre algunos de los emuladores ms populares, que puede descargar y empezar a escribir aplicaciones para ellos.
Nokia
En la pgina Web de Nokia (http://www.nokia.com), puede descargarse un emulador de telfono Nokia para probar sus aplicaciones Web para telfonos mviles. Esta descarga es de casi 22MB y requiere el entorno de ejecucin Java, lo que aade otros 5MB a su descarga. El emulador Nokia actualmente le permite elegir entre uno de los tres diferentes modelos Nokia que ofrece para que realice sus pruebas. Esta aplicacin es un valioso recurso para probar sus aplicaciones.
Pocket PC
El emulador de Pocket PC se incluye en el Microsoft Embedded Devices Toolkit. Es un excelente emulador si no dispone de un Pocket PC, ya que Pocket Internet
660
Explorer admite muchas ms funcionalidades que la mayora de los dispositivos mviles del mercado actual. Tenga en cuenta que el tamao real de este conjunto de herramientas es superior a los 300MB. Si no posee una conexin a Internet de alta velocidad, es mejor que use Microsoft Mobile Explorer, del que le hablamos en la siguiente seccin.
Figura 30.1. La nueva opcin Mobile Web Application se agreg cuando instal Mobile Internet Toolkit.
Esta aplicacin de ejemplo toma su ao de nacimiento para calcular cul es su edad actual (o la que debera ser). 1. Llame al proyecto YourAge y cuando Visual Studio cree la estructura de su aplicacin, ver un formulario Web Mobile en blanco llamado Form1. 2. Haga clic con el botn derecho del ratn en este formulario y seleccione Copiar. Ahora haga clic con el botn derecho del ratn debajo del formulario y seleccione Pegar. Esta aplicacin necesita dos formularios:
661
uno que adquiere la informacin del usuario y otro que muestra los resultados. 3. Una vez que tenga los dos formularios, coloque un control Button, un Label y un TextBox en Form1, y coloque un control Label y un Link Label en Form2. Coloque estos controles como se muestra en la figura 30.2. Una vez situados los controles, debe asignar todas las propiedades adecuadas. La tabla 30.1 contiene las propiedades de cada control. Colquelas adecuadamente antes de seguir adelante.
Tabla 30.1. Propiedades del control YourAge Formulario Form1 Form1 Form1 Form2 Form2 Control Label Text Box Button Label Link Label Propiedad Name Label1 txtDOB Command1 IblCurrentAge Link1 Propiedad Text Year of Birth <Empty> Show Age <Empty> Try Again
Debe establecer una propiedad antes de empezar a codificar. Seleccione el control Link Label y asigne la propiedad NavigateUrl a Form1. Esto asegura que, cuando se hace clic en el vnculo, el usuario es enviado de vuelta al Form1 del proyecto. Antes de empezar con el cdigo necesario para su programa, examine el cdigo oculto de estos formularios Web Mobile. En la parte inferior del diseador de formularios hay dos botones con la etiqueta Diseo y HTML. Haga clic en el
662
botn HTML para mostrar el cdigo HTML oculto de estos formularios, que debe ser igual que el cdigo del listado 30.1.
Listado 30.1. Cdigo HTML oculto de los formularios Web YourAge <@Register TagPrefix="mobile" Namespace="System.Web.UI.MobileControls" Assembly="System.Web.Mobile, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" > <@Page language="c#" Codebehind="MobileWebForm1.aspx.cs" Inherits="YourAge.MobileWebForm1" AutoEventWireup="false" > <meta name="GENERATOR" content="Microsoft Visual Studio 7.0"> <meta name="CODE_LANGUAGE" content="C#"> <meta name="vs_targetSchema" content="http:// schemas.microsoft.com/Mobile/Page"> <body Xmlns:mobile="http://schemas.microsoft.com/Mobile/ WebForm"> <mobile:Form id="Form1" runat="server"> <mobile:Label id="Label1" runat="server">Year of Birth:</ mobile:Label> <mobile:TextBox id="txtDOB" runat="server"></ mobile:TextBox"> <mobile:Command id="Command1" runat="server">Show Age</ mobile:Command> </mobile:Form> <mobile:Form id="Form2" runat="server"> <mobile:Label id="lblCurrentAge" runat="server"></ mobile:Label> <mobile:Link id="Link1" runat="server" NavigateUrl="#Form1">Try Again!</mobile:Link> </mobile:Form> </body>
Este cdigo es el estndar de HTML combinado con algunos elementos que estn definidos por el espacio de nombres MobileControls. Esta unin de la pgina HTML y la clase .NET MobileControls puede apreciarse en la lnea que sigue a Namespace. Cuando empiece a codificar, observe que varios espacios de nombres ya han sido agregados al proyecto por este motivo. Ahora est listo para empezar a codificar su aplicacin Web mvil. Este proyecto necesita que haya cdigo en un lugar. Haga doble clic en el botn que anteriormente colocamos en Form1. El listado 30.2 contiene el cdigo que debe insertar tras el evento clic de este botn.
Listado 30.2. Cmo calcular la edad en el evento Click private void Command1_Click(object sender, System.EventArgs e) { int iDOB = System.Convert.ToInt16(txtDOB.Text); int YearsOld; if (iDOB > 1000)
663
YearsOld = 2002 - iDOB; else YearsOld = 100 - iDOB + 2; lblCurrentAge.Text = YearsOld.ToString() + " years old."; ActiveForm = Form2; }
En esta parte del cdigo deber realizar algunas tareas. En primer lugar, debe convertir en un valor entero el ao que se introduce mediante la clase Convert. A continuacin, dependiendo del tamao de ese ao (dos o cuatro dgitos), calcule la edad en consecuencia, usando como ao actual 2002. Una vez que se ha calculado la edad, asigne la edad y una cadena a su etiqueta lblCurrentAge. Su aplicacin ya es funcional, pero aunque ha asignado el resultado a la etiqueta adecuada, todava no se muestra la pantalla que contiene la informacin. En la ltima lnea del listado 30.2, asigne ActiveForm a Form2. Esto carga Form2 en el navegador del usuario para mostrar los resultados. Puede ejecutar esta aplicacin pulsando F5, y si todo el cdigo se ha realizado correctamente, se abrir el navegador Web que tenga por defecto. Para ver esta aplicacin en un dispositivo mvil, ejecute su emulador preferido e introduzca la URL adecuada. La figura 30.3 muestra la aplicacin ejecutndose en un emulador de Pocket PC.
Tras introducir su ao de nacimiento, haga clic en el botn Show Age y se le mostrar el Form2, como muestra la figura 30.4. Felicidades por su primera aplicacin Web mvil. Aqullos que usaron Internet Explorer o el emulador de Pocket PC para hacer su prueba, se estarn preguntando cmo se mostrar la aplicacin Web en un telfono mvil. La figura 30.5 muestra la misma aplicacin ejecutndose en el emulador de telfono mvil Nokia.
664
Cmo puede ver, la pgina presenta un aspecto muy parecido al que mostraba en los anteriores emuladores, excepto en algunos detalles de desplazamiento entre opciones. Como los Pocket PC son dispositivos en los que se seala y se hace clic, el desplazamiento se realiza mucho ms suavemente. El telfono mvil, por otra parte, realiza todos sus desplazamientos entre opciones usando sus, aproximadamente, 20 botones. Es decir, no puede simplemente hacer clic en un botn o un vnculo para avanzar; debe usar los botones para desplazarse hacia arriba o abajo y seleccionar los vnculos antes de poder enlazar.
665
En un Pocket PC, puede conseguir 17 x 34 caracteres, mientras que en un telfono mvil Nokia 7110 slo puede conseguir 4 x 22, como muestra la figura 30.6. No todas las propiedades de esta clase funcionan en todos los dispositivos. El archivo de ayuda de Mobile Internet Toolkit de Microsoft contiene una tabla de
666
funciones de los dispositivos que define qu propiedades funcionarn normalmente con HTML, cHTML y con WML.
El cuadro de herramientas de Visual Studio .NET tambin tiene un control llamado DeviceSpecific que puede ser situado en un formulario para que realice ciertas tareas (dependiendo del dispositivo con el que se est comunicando). Esto se consigue mediante filtros y reduce considerablemente el esfuerzo que sera necesario para codificar todos los distintos contextos posibles.
667
cin Web mvil y coloque un control Calendar en el Form1 del proyecto. Cuando ejecute esta nueva aplicacin mediante su navegador por defecto o mediante el emulador de Pocket PC, ver un calendario mensual completo que permite hacer clic en cualquier da de la semana. En la figura 30.7 se muestra esta aplicacin ejecutndose en un Pocket PC.
Cmo puede mostrarse este calendario en un dispositivo mucho ms pequeo, como por ejemplo un telfono mvil? El control Calendar sabe en qu tipo de dispositivo va a ser mostrado y cambia su interfaz de usuario en consecuencia. La figura 30.8 muestra esta misma aplicacin Web ejecutndose en un emulador de telfono mvil. Ya no ve el calendario, sino que ve la fecha actual seguida de dos opciones. Estas opciones le permiten introducir directamente una fecha o buscarla semana a semana y luego por cada da de la semana. Este tipo de comportamiento no exige ningn tipo de programacin, lo que libera al programador de mucho trabajo.
668
Si intenta ver esta misma pgina con el emulador de telfono mvil Nokia, recibir un mensaje de error indicando que no se pudo cargar la pgina. Esto obviamente es debido a que la pgina no es de un tipo que se pueda ver en una pantalla en blanco y negro de un tamao tan pequeo. Sin embargo, se puede solventar este problema. Puede convertir su imagen JPG en una imagen de mapa de bits de dos colores (blanco y negro), formato conocido como de mapa de bits inalmbrico (WBMP), y que el dispositivo mvil
669
podr procesar. En el evento Page Load del Form1, puede comprobar la propiedad Device.PreferredRenderingType para ver qu imagen debe ser cargada. Si esta propiedad devuelve wml11, debe ajustar la propiedad ImageUrl del control Image a WBMP picture; en caso contrario, puede mostrar el original.
Estos 11 elementos se agregan a su lista. Por tanto, debera de ver siete elementos en la primera pgina, junto a un vnculo Next a una segunda pgina que contiene cuatro elementos. Ejecute la aplicacin en su dispositivo mvil preferido. Como se esperaba, los primeros siete elementos aparecen en la primera pgina, junto a un vnculo hacia la siguiente pgina, como muestra la figura 30.10.
670
Kim Pack Timothy Hyde Donna Malone Joshua Trueblood Staci Springer Chris Stephens Amy Sherman Next
El usuario puede hacer clic en el botn Next para ver la segunda pgina. En la segunda pgina, no tiene cuatro elementos, como se esperaba, sino siete elementos, debido a que el control List continu nuevamente con los primeros al terminar con los ltimos, como muestra la figura 30.11.
Steve Million Jim Mattingly Ryan Boyles Scott Leathers Kim Pack Timothy Hyde Donna Malone Previous Next
Como acabamos de demostrar, cuando la paginacin est activada se pueden aprovechar varias propiedades, como Page, que le permite especificar un nmero de ndice de la pgina que va a ver.
671
Resumen
En este captulo hemos estudiado los diferentes aspectos del Mobile Internet Toolkit, que permite implementar contenidos Web en dispositivos mviles. Hemos construido varias aplicaciones que demuestran cmo los controles Web mviles cambian dinmicamente su modo de presentacin en tiempo de ejecucin, dependiendo del dispositivo mvil en el que se estn ejecutando. Tambin hemos repasado los modos de detectar las funciones de los dispositivos para aprovechar las caractersticas propias de cada tipo de dispositivo. Aunque el captulo slo ha explicado brevemente algunas de estas caractersticas, ya dispone de un excelente punto de partida para crear contenidos Web muy dinmicos para ser implementados en dispositivos mviles.
672
Parte V
C# y .NET Framework
673
31 Cmo
trabajar con ensamblados
El cdigo creado para aprovechar las ventajas de .NET Framework se compila en una unidad de empaquetado llamada ensamblado. Los ensamblados son el corazn de la implementacin del cdigo y una estrategia de seguridad para .NET Framework, de modo que es importante entender lo que son y su modo de comportamiento. En este captulo estudiaremos los ensamblados y cmo escribir cdigo C# para trabajar con la informacin que contienen. .NET Framework consta de una clase llamada Assembly que hace que trabajar con ensamblados sea algo muy sencillo. Este captulo presenta los entresijos de la clase Assembly.
Ensamblados
Los ensamblados pueden contener cdigo, recursos o una combinacin de ambos. El cdigo de un ensamblado debe contener las instrucciones reales del Lenguaje intermedio de Microsoft (MSIL) que pueden ser ejecutadas por el Entorno comn de ejecucin (CLR) y un manifiesto que describa el contenido del cdigo. Los manifiestos contienen tipos y ms informacin descriptiva que describe el cdigo al CLR. Los ensamblados tambin forman los lmites del cdigo encerrado entre ellos. Los ensamblados forman los lmites de tipos, en los que cualquier tipo
675
que pueda usarse en cualquier cdigo .NET procede de un solo ensamblado, y los tipos con el mismo nombre procedentes de diferentes ensamblados son, de hecho, tipos diferentes. Los ensamblados tambin forman un lmite de seguridad por medio del cul todo el cdigo del ensamblado tiene el mismo conjunto de informacin de seguridad, restricciones y permisos. Los ensamblados se empaquetan mediante el archivo ejecutable portable de Win32 y pueden ser empaquetados como DLL o EXE. Cualquier cdigo generado por un compilador compatible con el CLR y compilado en un ejecutable de consola, un ejecutable Windows o una biblioteca, se empaqueta en un ensamblado. Este paquete forma una unidad de implementacin para un conjunto de tipos del ensamblado. No piense que slo se considera ensamblado al cdigo .NET basado en DLL. Cualquier paquete de cdigo .NET, recurso o metadato que tenga como objetivo un ejecutable o una biblioteca es un ensamblado, incluso si el paquete tiene la forma de un ejecutable. Las aplicaciones WinForms, por ejemplo, son ensamblados .NET vlidos, igual que las bibliotecas de clases basadas en DLL. Tenga en cuenta que el compilador de C# tambin puede generar mdulos, pero esos mdulos no son ensamblados. Los mdulos son fragmentos de cdigo (y probablemente recursos) compilados que se convertirn en un ensamblado ms tarde. Los mdulos pueden contener MSIL y metadatos que describen los tipos que se encuentran en el mdulo, pero no contienen un manifiesto. El CLR no puede abrir ni ejecutar mdulos y, por tanto, stos no pueden ser considerados ensamblados.
676
static void Main() { Assembly EntryAssembly; EntryAssembly = Assembly.GetEntryAssembly(); foreach(AssemblyName Name in EntryAssembly.GetReferencedAssemblies()) Console.WriteLine("Name: {0}", Name.ToString()); } }
El listado 31.1 presenta muchos conceptos importantes. En primer lugar, presenta una clase .NET llamada Assembly, que se incluye en el espacio de nombres System.Reflection de .NET. La clase Assembly es la clase mediante la cul cualquier cdigo .NET puede examinar y trabajar con los contenidos de un ensamblado .NET. Si necesita trabajar con un ensamblado .NET, debe usar la clase Assembly para examinar los contenidos del ensamblado. El segundo concepto importante del listado 31.1 es el de ensamblado de entrada. El ensamblado de entrada es el ensamblado que se ejecuta en primer lugar en el proceso. Para ejecutables, como el ejecutable de consola creado por el listado 31.1, el ensamblado de entrada es el ensamblado que contiene el punto de entrada de la funcin. Normalmente, el punto de entrada recibe el nombre Main() en los ensamblados basados en ejecutables, aunque en C# se puede cambiar mediante el argumento /main y especificando otro punto de entrada para el ensamblado. El acceso al ensamblado de entrada se realiza mediante un mtodo esttico de la clase Assembly llamado GetEntryAssembly() . Este mtodo devuelve una instancia de un objeto Assembly que hace referencia al ensamblado de entrada. El tercer concepto importante del listado 31.1 es el de que un ensamblado puede contener ensamblados a los que se hace referencia. La informacin de los ensamblados a los que se hace referencia se obtiene mediante una llamada a un mtodo de ensamblado llamado GetReferencedAssemblies() . Este mtodo devuelve una matriz de objetos de una clase llamada AssemblyName. Los objetos de AssemblyName describen completamente el nombre de un ensamblado o se pueden convertir en una sencilla cadena mediante el conocido mtodo ToString() . Obtener una representacin de cadena de un nombre de ensamblado facilita el que las aplicaciones muestren informacin del nombre del ensamblado en las interfaces de usuario. Con esta informacin resulta sencillo adivinar lo que hace el listado 31.1. El cdigo obtiene una referencia al ensamblado de entrada y enva los nombres de los ensamblados a los que se hace referencia a la consola. Si se compila y ejecuta el cdigo del listado 31.1, se enva la siguiente informacin a la consola:
Name: mscorlib, Version=1.0.3300.0, PublicKeyToken=b77a5c561934e089 Culture=neutral,
Cmo sabe .NET Framework que el listado 31.1 hace referencia al ensamblado mscorlib? Esa informacin se almacena en el manifiesto del ensamblado
677
del Listado 31.1. Para ver esta informacin, inicie la herramienta ILDASM de .NET Framework y abra el ensamblado del listado 31.1 en ella. Haga doble clic en la entrada del manifiesto en el rbol que aparece, y se mostrar el manifiesto para el ensamblado en una ventana distinta, como muestra la figura 31.1.
El manifiesto contiene una entrada con la etiqueta .assembly extern. Esa entrada del manifiesto describe un ensamblado externo del que depende el ensamblado que contiene el manifiesto. Esta entrada registra que el ensamblado que contiene este manifiesto depende de la versin 1.0.3300.0 de un ensamblado externo llamado mscorlib. .NET Framework debe leer este manifiesto y cargar los ensamblados dependientes en el espacio del proceso que se est ejecutando en ese momento. NOTA: El ensamblado mscorlib contiene informacin vital para clases como System.Object y siempre se le hace referencia sin ningn argumento especial de compilacin. El resto de ensamblados pueden referenciarse usando la opcin /r para el compilador de lnea de comando de C# o con el elemento de men Agregar referencias de Visual Studio .NET.
678
Como mnimo, todos los ensamblados contienen un nombre, una versin y una referencia cultural. Sin embargo, los ensamblados pueden contener una clave pblica. Un ensamblado que contenga los cuatro fragmentos de informacin tiene un nombre seguro. Slo los que contienen nombres seguros pueden almacenarse en la cach de ensamblados local. La cach de ensamblados global (GAC) es una coleccin basada en disco de ensamblados .NET a los que puede acceder cualquier fragmento de cdigo .NET del equipo que contiene el GAC. .NET Framework busca un ensamblado en el directorio del ensamblado de entrada cuando debe cargar un ensamblado. Este esquema de implementacin es sencillo; sin embargo, puede crear varias copias de un ensamblado en un volumen de disco para los ensamblados ms utilizados, ya que cada ensamblado necesita ser copiado en cada ensamblado de entrada que necesite el ensamblado al que se hace referencia. .NET Framework incluye la GAC para simplificar las cosas, de modo que los ensamblados ms utilizados, como los ensamblados incluidos en .NET Framework, pueden ser colocados en un equipo una vez y ser referenciados varias veces. .NET Framework comprueba la GAC cuando busca un ensamblado. Cuando .NET Framework se instala en un equipo, el proceso de configuracin nstala una extensin del intrprete de comandos del Explorador de Windows que hace que la GAC aparezca como una carpeta de Windows estndar. El directorio base de Windows, que es C:\WINDOWS en casi todos los equipos, contiene un ensamblado de llamada de carpetas en los equipos que tienen instalado .NET Framework. Esta carpeta muestra los contenidos de la GAC, con informacin de los nombres seguros en las columnas, como muestra la figura 31.2. Puede colocar ensamblados con diferentes nombres seguros uno junto al otro en la cach de ensamblados global, aunque los nombres de segmento coincidan. Por ejemplo, la versin 1.0.0.0 de un ensamblado llamado assembly.dll puede estar instalada en la cach de ensamblados global junto a la versin 2.0.0.0 de un ensamblado tambin llamado assembly.dll. El cdigo que hace referencia a un ensamblado con nombre seguro tiene el nombre seguro del ensamblado escrito en el manifiesto, y siempre se enlaza al ensamblado con ese nombre seguro aunque otros ensamblados con algunos componentes del nombre seguro sean iguales. Si un ensamblado de entrada hace referencia a la versin 1.0.0.0 de assembly.dll, por ejemplo, .NET Framework siempre carga la versin 1.0.0.0 de assembly.dll en el espacio de proceso del ensamblado de entrada, aunque existan otras versiones de assembly.dll en la GAC. La operacin de asignar la informacin que compone un nombre seguro es tan sencilla como agregar algunos atributos a uno de los archivos de cdigo fuente para un proyecto. Estos atributos pueden agregarse a un archivo fuente que contenga cdigo C# o pueden agregarse a un archivo distinto que slo contenga los atributos en s.
679
NOTA: VS.NET aade un archivo fuente llamado AssemblyInfo.cs a los nuevos proyectos de C# y coloca los atributos necesarios para los ensamblados de nombre seguro de ese archivo fuente. El nombre de archivo puede cambiarse una vez que se ha creado, y seguir funcionando siempre que el archivo renombrado siga siendo parte de la creacin del proyecto.
Para hacerlo ms sencillo, el compilador de C# genera un nmero de revisin automticamente si se usa un asterisco en lugar de un nmero de revisin:
[assembly: AssemblyVersion("1.0.0.*")]
680
Esta sintaxis indica al compilador de C# que debe asignar el nmero de revisin que prefiera al ensamblado. El compilador de C# calcula el nmero de segundos entre la medianoche y la hora en que compil su cdigo, divide el nmero entre dos y usa el resto de la divisin como base para generar un nmero de revisin nico (a esto se le llama operacin mdulo 2, ya que una operacin de mdulo calcula el resto de la divisin entre los dos operandos). Esto permite generar un nmero de versin nico para cada compilacin. Para facilitar an ms las cosas, el compilador de C# genera automticamente un nmero de compilacin y un nmero de revisin si se usa un asterisco como nmero de compilacin:
[assembly: AssemblyVersion("1.0.*")]
Esta sintaxis indica al compilador de C# que debe asignar el nmero de compilacin y el nmero de revisin que prefiera al ensamblado. Adems del clculo automtico del nmero de revisin descrito, el compilador de C# tambin calcula un nmero de compilacin usando el nmero de das entre el 1 de enero del 2000 y el da en que se produjo la compilacin.
La cadena tambin puede especificar el idioma y el pas para los que se ha diseado el ensamblado. El formato de la cadena que especifica la informacin del idioma y el pas debe estar registrado en el Internet RFC 1766, que tiene la forma idioma-pas:
[assembly: AssemblyCulture("en-US")]
TRUCO: El estndar RFC 1766 recibe el nombre de "Etiquetas para la identificacin de idiomas" y est disponible en la direccin de Internet http://www.ietf.org/rfc/rfc1766.txt . Slo los ensamblados basados en DLL deben especificar la informacin de referencia cultural. Los ensamblados basados en EXE deben usar una cadena vaca para la informacin de referencia cultural. Si se especifica informacin de
681
referencia cultural para un ensamblado basado en EXE, se produce un error del compilador de C#.
Esta lnea de comando indica a la utilidad sn que debe generar un nuevo par de claves de firma digital y enviar las claves a un archivo binario llamado KeyPair.snk. Despus este archivo se utiliza como argumento para un atributo llamado AssemblyKeyFile:
[assembly: AssemblyKeyFile("KeyPair.snk")]
La extensin .snk no es imprescindible. Se puede usar cualquier extensin para el archivo clave generado por la utilidad de nombre seguro.
682
Si se compila y ejecuta el cdigo del listado 31.2 se enva la siguiente informacin a la consola: Location: C : \ D o c u m e n t s and S e t t i n g s \ U s e r \ M y Documents\Listing31-2\bin\Debug\Listing31-2.exe Code Base: file:///C:/Documents and Settings/User/ My Documents/Listing31-2/bin/Debug/Listing31-2.exe Escaped Code Base: file:///C: /Documents%20and%20 Settings/User/My%20Documents/Listing31-2/bin/Debug/ Listing31-2.exe Loaded from GAC: False
La informacin de la ruta vara segn la ubicacin real del cdigo cuando se ejecuta, pero los resultados son bsicamente los mismos: la propiedad Location hace referencia a una posicin en el disco, y las propiedades CodeBase y EscapedCodeBase hacen referencia a la ubicacin del ensamblado como URI.
683
Los ensamblados basados en DLL, en cambio, no suelen tener puntos de entrada. Estos ensamblados generalmente contienen recursos o tipos usados por otros fragmentos de cdigo y son pasivos, en el sentido de que esperan a ser llamados antes de que se ejecute algn cdigo del ensamblado. La clase Assembly contiene una propiedad llamada EntryPoint. La propiedad EntryPoint es un valor de un tipo llamado MethodInfo, que se incluye en el espacio de nombres System.Reflection de .NET. La clase MethodInfo describe los detalles especficos de un mtodo, y al llamar a ToString() sobre un objeto de tipo MethodInfo se devuelve una cadena que describe el tipo devuelto, el nombre y los parmetros del mtodo. La propiedad EntryPoint es nula si la referencia del ensamblado no tiene un punto de entrada, y devuelve un objeto MethodInfo si la referencia del ensamblado tiene un punto de entrada, como se aprecia en el listado 31.3.
Listado 31.3. Cmo trabajar con un punto de entrada de un ensamblado using System; using System.Reflection; public class MainClass { static void Main(string[] args) { Assembly EntryAssembly; EntryAssembly = Assembly.GetEntryAssembly(); if (EntryAssembly.EntryPoint == null) Console.WriteLine("The assembly has no entry point.") ; else Console.WriteLine(EntryAssembly.EntryPoint.ToString()); } }
En el sencillo ejemplo del listado 31.3, la propiedad EntryPoint nunca es nula, pero siempre conviene comprobar la posibilidad de que se produzca un valor nulo, especialmente con fragmentos de cdigo ms complicados.
684
cas de carga devuelve un objeto Assembly que hace referencia a un ensamblado cargado. La primera tcnica de carga de ensamblado usa un mtodo de ensamblado esttico llamado Load() . El mtodo Load() recibe una cadena que proporciona el nombre del ensamblado que se va a cargar. Si no se encuentra el ensamblado nombrado, el mtodo Load() inicia una excepcin. En cambio, el mtodo LoadWithPartialName() busca el directorio de la aplicacin y la GAC del ensamblado especificado, usando toda la informacin de nombre disponible para el invocador. El listado 31.4 muestra la diferencia entre estos dos mtodos.
Listado 31.4. Cmo cargar ensamblados dinmicamente con Load() y LoadWithPartialName() using System; using System.Reflection; using System.IO; public class AssemblyLoader { private Assembly LoadedAssembly; public AssemblyLoader (string LoadedAssemblyName, PartialName) { try {
Console.WriteLine("+--------------------");
bool
Console.WriteLine("| Loading Assembly {0}", LoadedAssemblyName); Console.WriteLine("+--------------------"); if (PartialName == true) LoadedAssembly = Assembly.LoadWithPartialName(LoadedAssemblyName); else LoadedAssembly = Assembly.Load(LoadedAssemblyName); WritePropertiesToConsole(); } catch(FileNotFoundException) { Console.WriteLine("EXCEPTION: Cannot load assembly."); } } private void WritePropertiesToConsole() { Console.WriteLine("Full Name: {0}", LoadedAssembly.FullName); Console.WriteLine("Location: {0}", LoadedAssembly.Location); Console.WriteLine("Code Base: {0}", LoadedAssembly.CodeBase);
685
Console.WriteLine("Escaped Code Base: {0}", LoadedAssembly.EscapedCodeBase); Console.WriteLine("Loaded from GAC: {0}", LoadedAssembly.GlobalAssemblyCache); } } public class MainClass { static void Main(string[] { AssemblyLoader Loader;
args)
Loader = new AssemblyLoader("System.Xml, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", false); Loader = new AssemblyLoader("System.Xml", false); Loader = new AssemblyLoader("System.Xml", true); } }
El listado 31.4 muestra una clase llamada AssemblyLoader, cuyo constructor recibe un nombre de ensamblado y un indicador booleano que especifica si el ensamblado nombrado debe cargarse usando un nombre parcial. El constructor carga el ensamblado y a continuacin llama a un mtodo privado para escribir en la consola algunas de las propiedades de designacin y de ubicacin del ensamblado cargado. El mtodo Main() del listado 31.4 crea nuevos objetos de la clase AssemblyLoader e intenta cargar el ensamblado de .NET Framework System.XML, que se encuentra en la GAC, de varios modos. Si se ejecuta el listado 31.4 se escribe la siguiente informacin en la consola:
+---------------------| Loading Assembly System.Xml, Version=1.0.3300.0, Culture=neutral, PublicKeyTok en=b77a5c561934e089 +---------------------Full Name: System.Xml, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5 c561934e089 Location: c:\windows\assembly\gac\system.xml\1.0.3300.0__ b77a5c5c1934e089\system .xml.dll Code Base: file:///c:/windows/assembly/gac/system.xml/ 1.0.3300.0__b77a5c561934e0 89/system.xml.dll Escaped Code Base: file:///c:/windows/assembly/gac/system.xml/ 1.0.3300.0__b77a5c 5c1934e089/system.xml.dll Loaded from GAC: True +---------------------
686
| Loading Assembly System.Xml +---------------------EXCEPTION: Cannot load assembly. +---------------------| Loading Assembly System.Xml +---------------------Full Name: System.Xml, Version=1.0.3300.0, Culture-neutral, PublicKeyToken=b77a5 c561934e089 Location: c:\Windows\assembly\gac\system.xml\1.0.3300.0__ b77a5c561934e089\system .xml.dll Code Base: file:///c:/windows/assembly/gac/system.xml/ 1.0.3300.0__b77a5c561934e0 89/system.xml.dll Escaped Code Base: file:///c:/windows/assembly/gac/system.xml/ 1.0.3300.0__b77a5c 561934e089/system.xml.dll Loaded from GAC: True
Observe con atencin lo que se est produciendo. En el segundo caso, el mtodo Main() especifica el nombre seguro para el ensamblado System.Xml, incluyendo su nombre, clave pblica, informacin de versin y detalles especficos de su referencia cultural. Como el ensamblado System.Xml est en la GAC, no se almacena en el directorio de la aplicacin, y el mtodo Load() no puede encontrar el ensamblado en el directorio que contiene el ejecutable del listado 31.4. Sin embargo, como se especific el nombre seguro para el ensamblado, el mtodo Load() tiene suficiente informacin para buscar el ensamblado en la GAC. El mtodo Load() puede encontrar el ensamblado en la GAC y la operacin de carga se completa con xito. En el segundo caso, el mtodo Main() slo especifica el nombre base del ensamblado System.Xml. Como el ensamblado System.Xml est en la GAC, no se almacena en el directorio de la aplicacin, y el mtodo Load() no puede encontrar el ensamblado en el directorio que contiene el ejecutable del listado 31.4. Adems, el mtodo Load() no tiene suficiente informacin para ubicar el ensamblado en la GAC, ya que pueden existir muchas instancias de System.Xml en la GAC con diferentes nmeros de versin o claves pblicas, por lo que la carga falla. En el tercer y ltimo caso, el mtodo Main() slo especifica el nombre base del ensamblado System.Xml e indica al cargador que encuentre un ensamblado usando slo un nombre parcial. De nuevo, como el ensamblado System.Xml est en la GAC, no se almacena en el directorio de la aplicacin, y el mtodo LoadWithPartialName() no puede encontrar el ensamblado en el directorio que contiene el ejecutable del listado 31.4. Sin embargo, el mtodo LoadWithPartialName() recibe el nombre parcialmente proporcionado e intenta hacer coincidir el nombre con un ensamblado de la GAC. Como se ha proporcionado un nombre parcial de System.Xml y hay un ensamblado con el nombre System.Xml en la GAC, la operacin de carga se completa con xito.
687
ADVERTENCIA: No se recomienda utilizar LoadWithPartialName(). Si el ensamblado parcialmente nombrado tiene varias copias en la GAC (quizs con diferentes nmeros de versin, referencias culturales o claves pblicas) la instancia realmente cargada puede no ser la versin esperada. Adems, la instancia cargada puede ser una versin diferente de la que pretenda cargar despus de que se hayan cargado versiones ms modernas en la GAC. Use Load() en lugar de LoadWithPartialName() siempre que pueda.
args)
XMLAssembly = Assembly.Load("System.Xml, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"); XMLTypes = XMLAssembly.GetExportedTypes(); foreach (Type XMLType in XMLTypes) { object NewObject; try { Console.Write(XMLType.ToString()); NewObject = XMLAssembly.CreateInstance(XMLType.ToString()); if (NewObject != null) Console.WriteLine(" - Creation successful"); else Console.WriteLine(" - CREATION ERROR"); } catch(Exception e) { Console.WriteLine(" - EXCEPTION: {0}", e.Message); }
688
} } }
El cdigo del listado 31.5 carga el ensamblado System.Xml desde la GAC y llama a un mtodo en la clase Assembly llamado GetExportedTypes() para conseguir una matriz de objetos Type que representan los tipos que se encuentran en el ensamblado y pueden ser utilizados o exportados al exterior del ensamblado. El cdigo recorre cada tipo de la matriz devuelta e invoca a otro mtodo de ensamblado llamado CreateInstance() para crear una instancia de objeto del tipo nombrado. Si la creacin se realiza con xito, el mtodo CreateInstance() devuelve una referencia de objeto vlida. Si la creacin no tiene xito, CreateInstance() devuelve una referencia de objeto nula o inicia una excepcin, dependiendo de la naturaleza del error. A continuacin tiene varias lneas de resultado del listado 31.5:
System.Xml.XPath.XPathtNavigator - EXCEPTION: Constructor on type System.Xml.XPath.XPathNavigator not found. System.Xml.IHasXmlNode - EXCEPTION: Constructor on type System.Xml.IHasXmlNode not found. System.Xml.XPath.XPathNodeIterator - EXCEPTION: Constructor on type System.Xml.XPath.XPathNodeIterator not found. System.Xml.EntityHandling - Creation successful System.Xml.IXmlLineInfo - EXCEPTION: Constructor on type System.Xml.IXmlLineInfo not found. System.Xml.XmlNameTable - EXCEPTION: Constructor on type System.Xml.XmlNameTable not found. System.Xml.NameTable - Creation successful System.Xml.ReadState - Creation successful System.Xml.ValidationType - Creation successful System.Xml.WhitespaceHandling - Creation successful
Las excepciones se producen porque no todos los tipos exportados tienen constructores, y con CreateInstance() slo se pueden crear tipos de referencia con constructores adecuados. TRUCO: Tras obtener una referencia a un objeto Type , se puede encontrar una referencia al ensamblado que contiene el tipo en la propiedad Assembly del objeto Type. Esto permite al cdigo descubrir el ensamblado que hace referencia a un tipo.
689
nativo que puede ser ejecutado por la CPU del equipo. La ventaja del diseo JIT es que permite enviar el cdigo MSIL sin tener que preocuparse por optimizarlo en funcin del procesador al que est destinado. .NET Framework probablemente ser exportado a una gran variedad de arquitecturas de CPU de una gran variedad de dispositivos, desde equipos de sobremesa hasta sistemas porttiles, e intentar escribir cdigo para cada uno de estos procesadores podra ser una tarea impresionante. Este trabajo no es necesario porque cada implementacin de .NET Framework tiene un compilador JIT que convierte las instrucciones MSIL en instrucciones nativas de la CPU de destino. Si la principal preocupacin de su aplicacin es el rendimiento, puede convertir el cdigo MSIL en cdigo especfico para la CPU de un equipo mediante un proceso conocido como generacin de imagen nativa. Durante este proceso, las instrucciones MSIL de un ensamblado se convierten en instrucciones nativas de la CPU, las cules pueden ser escritas en disco. Despus de completar esta generacin de imgenes nativas, el CLR puede usar este cdigo y puede omitir el paso JIT que normalmente se emplea en los ensamblados. .NET Framework incluye una herramienta llamada Generador de imgenes nativas, que genera una imagen nativa para un ensamblado. Esta herramienta de lnea de comandos se encuentra en un ejecutable llamado ngen.exe y recibe como entrada un nombre de ensamblado:
ngen assembly
La imagen nativa se coloca en una cach de imgenes nativas para ensamblados. Tenga en cuenta que ngen debe ejecutarse en el dispositivo destino del cdigo generado. Por ejemplo, no puede construir ensamblados como parte de su proceso de compilacin, ejecutar ngen sobre esos ensamblados y enviar esas imgenes nativas a sus clientes. El equipo en el que se compila puede perfectamente tener una CPU diferente a la de los equipos de sus clientes, y ngen genera cdigo para la CPU en la que ngen se est ejecutando. Si para sus clientes es importante tener imgenes nativas para sus ensamblados, debe ejecutar ngen en los equipos de sus clientes como parte del proceso de instalacin. Tambin es importante tener en cuenta que los ensamblados .NET originales deben estar disponibles en todo momento, aunque el cdigo nativo est disponible en la cach de imagen nativa. Las imgenes nativas son archivos Ejecutable portables (PE) Win32 estndar y carecen de los metadatos existentes en un ensamblado .NET. Si el cdigo carga su ensamblado de imagen nativa y ejecuta cdigo que obliga a .NET Framework a examinar metadatos (por ejemplo, usando reflexin para obtener informacin para el ensamblado), entonces el ensamblado .NET original debe estar disponible para que el CLR pueda consultar sus metadatos. Los metadatos no pueden ser transportados junto a la imagen nativa.
690
Resumen
En este captulo se ha examinado el concepto de ensamblado .NET desde la perspectiva de las aplicaciones de C# que pueden acceder a la informacin de los ensamblados. El acceso a la informacin de los ensamblados se realiza mediante la clase Assembly. La clase Assembly muestra la informacin con nombre para el ensamblado y permite que los ensamblados se carguen dinmicamente. Los tipos gestionados por el ensamblado pueden ser obtenidos al instante. Puede aplicar los conceptos mostrados en este captulo en la construccin de potentes aplicaciones .NET. Algunas de las herramientas que incorpora .NET Framework, como la herramienta ILDASM, usan una combinacin de mtodos de la clase Assembly y otras clases en el espacio de nombres System. Reflection para proporcionar una vista con todos los detalles de los ensamblados ya compilados. Se puede obtener una gran cantidad de informacin de los ensamblados usando los mtodos de la clase Assembly y otras clases de reflexin an cuando el cdigo fuente usado para construir el ensamblado no est disponible.
691
32 Reflexin
Una importante caracterstica de .NET Framework es su capacidad para descubrir informacin de tipos en tiempo de ejecucin. En concreto, puede usar el espacio de nombres reflection para ver la informacin de tipos que contienen los ensamblados y que se podrn enlazar con otros objetos. Incluso puede usar este espacio de nombres para generar cdigo en tiempo de ejecucin. Esta tecnologa se extiende a la tecnologa de automatizacin COM, ya conocida por muchos de los lectores. Como programador, seguramente necesite usar a menudo un objeto sin comprender del todo lo que hace ese objeto. La reflexin permite tomar un objeto y examinar sus propiedades, mtodos, eventos, campos y constructores. Como la reflexin gira en torno a System.Type, puede examinar un ensamblado y usar mtodos, como GetMethods() y GetProperties(), para devolver informacin de miembros desde el ensamblado. Con esta informacin, puede empezar usando el mtodo MethodInfo() para devolver listas de parmetros e incluso llamar a mtodos en el ensamblado con un mtodo llamado Invoke. En este captulo aprender a usar el espacio de nombres Reflection para examinar objetos en tiempo de ejecucin. Tambin aprender a enlazar posteriormente los objetos, y a usar mtodos y propiedades en estos objetos enlazados con posterioridad.
693
La clase Type
La clase Type acta como una ventana al API de reflexin, lo que permite el acceso a metadatos. La clase abstracta System.Type representa un tipo del Sistema completo de tipos (CTS). Este sistema completo de tipos es lo que permite examinar objetos en todos los lenguajes de la familia .NET. Como cada objeto usa el mismo entorno, tiempo de ejecucin y sistema de tipos, la informacin de objeto y de tipo se consigue fcilmente. Una de las mayores ventajas de las clases Type es su capacidad para crear objetos dinmicamente y usarlos en tiempo de ejecucin.
694
La figura 32.1 indica que System.String es el tipo base del sistema y que el objeto es, sin lugar a dudas, una clase. Se estar preguntando qu utilidad tiene esta informacin. Imagine que est creando una aplicacin que necesita generar instrucciones insert para introducir informacin en SQL Server. Escribir una gran cantidad de informacin requiere una gran cantidad de tiempo. Usando la reflexin y la clase Type, puede examinar el tipo subyacente de cada fragmento de informacin que quiera insertar en SQL Server y describir estos tipos con un tipo de datos SQL Server vlido. Esto simplifica mucho el proceso de generar mediante programacin las instrucciones insert que necesita.
C:\ C:\WINDOWS\System32\cmd.exe
C:\>NameType.exe Name: String Underlying System Type: System.String Is Class: True C:\>_
695
En este ejemplo, en lugar de especificar que quiere ver la informacin del tipo de Sytem.String, se crea una instancia de una variable de cadena sobre la que, a continuacin, se llama al mtodo GetType() . La informacin obtenida aqu es la misma que la que se obtuvo en el ejemplo anterior, la diferencia est en que no tiene que saber el tipo antes de tiempo. Simplemente llame al mtodo GetType() y asgnelo a un objeto Type, del que podr consultar el nombre, el tipo de sistema subyacente y similares.
El cdigo anterior presenta algunos elementos nuevos. E1 tipo process se usa para examinar procesos que se estn ejecutando. En este contexto, se emplea
696
para conseguir el nombre de su programa y luego agregar .exe al final del nombre para que pueda examinar el ensamblado. Puede igualmente escribir en el cdigo el nombre de su aplicacin, pero este mtodo garantiza que funcionar sin que importe cmo llame a su aplicacin. La figura 32.2 muestra el resultado de esta aplicacin. No es un resultado muy espectacular, ya que su programa slo contiene una clase.
c:\ C:\WINDOWS\System32\cmd.exe C:\>AssemblyTypes.exe E x a m i n i n g : AssemblyTypes.exe T y p e : AssemType Base c l a s s : System.Object C:\>_
Como puede ver en la figura 32.2, hemos examinado el proceso en curso y la propia aplicacin que lo est ejecutando, mostrando todas sus clases internas y sus tipos. Para experimentar, intente agregar algunas clases nulas a este proyecto y luego vuelva a ejecutar la aplicacin. Debera ver una lista de todas las clases que estn all contenidas y sus tipos.
697
public static int M a i n ( ) { Type t = typeof(aUsefulClass); Console.WriteLine("Type of class: " + t); Console.WriteLine("Namespace: " + t . N a m e s p a c e ) ; ConstructorInfo[] ci = t.GetConstructors(); Console.WriteLine("------------------------------------"); Console.WriteLine("Constructors a r e : " ) ; foreach(ConstructorInfo i in c i ) { Console.WriteLine(i); } PropertyInfo[] pi = t.GetProperties(); Console.WriteLine("------------------------------------"); Console.WriteLine("Properties a r e : " ) ; foreach(PropertyInfo i in p i ) { Console.WriteLine(i); } MethodInfo[] mi = t . G e t M e t h o d s ( ) ; Console.WriteLine("------------------------------------"); Console.WriteLine("Methods a r e : " ) ; foreach(MethodInfo i in mi) { Console.WriteLine("Name: " + i . N a m e ) ; ParameterInfo[] pif = i.GetParameters(); foreach(ParameterInfo p in p i f ) { Console.WriteLine("Type: " + p.ParameterType + " parameter name: " + p.Name); } } return 0; } public class aUsefulClass { public int pubInteger; private int privValue; public aUsefulClass() { } public aUsefulClass(int IntegerValueIn) { pubInteger = IntegerValueIn;
698
} public int A d d 1 0 ( i n t IntegerValueIn) { Console.WriteLine(IntegerValueIn) return IntegerValueIn + 1 0 ; } public int TestProperty { get { return _privValue; } set { _privValue = v a l u e ; } } } } }
Aqu hemos creado dos clases, Class1 y aUsefulClass. Class1 contiene el punto de entrada principal a su aplicacin (void Main), mientras que la otra clase slo existe para ser examinada. Para examinar la clase aUsefulClass, realice los siguientes pasos en el procedimiento principal: en primer lugar, declare un objeto Type y, mediante la palabra clave typeof, dirjalo hacia aUsefulClass. A continuacin, muestra la clase Type y el espacio de nombres. Despus, use GetConstructors para recuperar una lista de los constructores de la clase. A continuacin, aplique un bucle a lo largo de los constructores y mustrelos en la pantalla. Al igual que con los constructores, use GetProperties para recuperar una lista de todas las propiedades, de modo que pueda iterar a lo largo de la lista para mostrar las propiedades en la ventana de consola. GetMethods recupera todos los mtodos, adems de los mtodos que componen los descriptores de acceso get y set de sus propiedades. A continuacin, se itera a lo largo de esta informacin y la misma se muestra en pantalla. Tambin se invoca a GetParameters para recuperar una lista de los parmetros para cada mtodo, y se muestra en pantalla dicha informacin. Como puede ver en la figura 32.3, su aplicacin muestra una gran cantidad de informacin sobre el objeto de la clase. Obviamente, esta aplicacin no es especialmente til, pues ya dispone del cdigo fuente de la clase en cuestin y no necesita la reflexin para obtener detalles. Lo
699
importante aqu es que la reflexin funciona de la misma manera aunque estemos tratando con un ensamblado para el que no tengamos el cdigo fuente.
c:\ C:\WINDOWS\System32\cmd.exe C:\>ReflectionTest.exe Type of class: ReflectionTest.Class1+aUsefulClass Namespace: ReflectionTest -----------------------------------------------Constructors are: Void .ctor() Void .ctor(Int32) -----------------------------------------------Properties are: Int32 TestProperty -----------------------------------------------Methods are: Name: GetHashCode Name: Equals Type: System.Object parameter name: obj Name: ToString Name: Add10 Type: System.Int32 parameter name: IntegerValueIn Name: get_TestProperty Name: set_TestProperty Type: System.Int32 parameter name: value Name: GetType C:\>
Figura 32.3. Las clases R e f l e c t i o n y Type revelan una gran cantidad de informacin relativa al objeto de la clase
700
class CodeGenerator { Type t; AppDomain currentDomain; AssemblyName assemName; AssemblyBuilder assemBuilder; ModuleBuilder moduleBuilder; TypeBuilder typeBuilder; MethodBuilder methodBuilder; ILGenerator msilG;
public static void Main() { CodeGenerator codeGen = new CodeGenerator(); Type t = codeGen.T; if (t != null) { object o = Activator.CreateInstance(t); MethodInfo helloWorld = t.GetMethod("HelloWorld"); if (helloWorld != null) { // Ejecuta el mtodo HelloWorld helloWorld.Invoke(o, null); } else { Console.WriteLine("Could not MethodInfo"); }
retrieve
public CodeGenerator() { // Obtiene el dominio de aplicacin actual. // Esto es necesario cuando se construye cdigo. currentDomain = AppDomain.CurrentDomain; // Crea un nuevo ensamblado para nuestros mtodos assemName = new AssemblyName(); assemName.Name = "BibleAssembly"; assemBuilder = currentDomain.DefineDynamicAssembly(assemName, AssemblyBuilderAccess.Run);
701
// Crea un nuevo mdulo en este ensamblado moduleBuilder = assemBuilder.DefineDynamicModule("BibleModule"); // Crea un nuevo tipo en el mdulo typeBuilder = moduleBuilder.DefineType("BibleClass", TypeAttributes.Public); // Ahora podemos agregar el // mtodo HelloWorld a la clase recin creada. methodBuilder = typeBuilder.DefineMethod("HelloWorld", MethodAttributes.Public, null, null); // Ahora podemos generar algo de cdigo de lenguaje // intermedio de Microsoft que simplemente escriba // una lnea de texto en la consola. msilG = methodBuilder.GetILGenerator(); msilG.EmitWriteLine("Hello from C# Bible"); msilG.Emit(OpCodes.Ret); // Crea un tipo. t = typeBuilder.CreateType(); } public Type T { get { return this.t; } } } )
Como se esperaba, esta aplicacin slo escribe un mensaje en la consola, como muestra la figura 32.4. La funcin de reflexin para generar objetos y cdigo en tiempo de ejecucin es realmente impresionante y constituye la columna vertebral para la generacin de aplicaciones de lgica difusa que se adaptan y aprenden de la forma adecuada. NOTA: La lgica difusa es un tipo de lgebra que usa los valores verdadero y falso para tomar decisiones basndose en datos imprecisos. La lgica difusa suele relacionarse con los sistemas de inteligencia artificial.
Resumen
Las clases Reflection y Type van unidas cuando necesita descubrir informacin de tipos en tiempo de ejecucin. Estas clases permiten examinar obje-
702
tos, cargar objetos dinmicamente en tiempo de ejecucin e incluso generar cdigo en caso de necesidad.
c:\ C:\WINDOWS\System32\cmd.exe C:\>DynanicCode.exe Hello from C# Bible C:\>
703
33
Subprocesamiento en C#
La potencia del multiprocesamiento de .NET Framework permite escribir aplicaciones muy estables con varios subprocesos en cualquier lenguaje .NET. En este captulo aprender los entresijos de los multiprocesos. El captulo comienza con una visin general de los diferentes tipos de subprocesos y de su funcionamiento en .NET Framework, para luego continuar ensendole lo que puede hacer en sus aplicaciones gracias al multiprocesamiento. A medida que avance en el captulo, sopese cuidadosamente los peligros de agregar varios subprocesos a sus aplicaciones antes de implementarlos, porque el multiprocesamiento no es un concepto sencillo.
Subprocesamiento
Antes de empezar a escribir aplicaciones con mltiples subprocesos, debe comprender lo que sucede cuando se crean los subprocesos y cmo gestiona el sistema operativo los subprocesos. Cuando se ejecuta una aplicacin, se crea un subproceso primario y el mbito de la aplicacin se basa en ese subproceso. Una aplicacin puede crear subprocesos adicionales para realizar tareas adicionales. Un ejemplo de creacin de subproceso primario sera iniciar Microsoft Word. La ejecucin de la aplicacin comienza
705
en el subproceso principal. En la aplicacin Word, la impresin en segundo plano de un documento sera un ejemplo de un subproceso adicional creado para controlar otra tarea. Mientras est interactuando con el subproceso principal (el documento de Word), el sistema lleva a cabo su peticin de impresin. Cuando el subproceso de la aplicacin principal termina, todos los otros subprocesos creados a partir de ese proceso tambin finalizan. Considere estas dos definiciones del Kit de desarrollo de software de Microsoft Foundation Class (MFCSDK): Proceso: Una instancia que se ejecuta de una aplicacin Subproceso: Una ruta de ejecucin dentro de un proceso
C++ y MFC llevan muchos aos apoyando el desarrollo de aplicaciones multiproceso. Como el corazn del sistema operativo Windows est escrito con estas herramientas, es importante que sean compatibles con la capacidad de crear subprocesos en los que se puedan asignar y crear tareas. En los primeros tiempos de Windows 3.1, la multitarea no exista; este concepto se hizo realidad con Windows NT 3.5 y NT 4.0, y luego en Windows 95, 98, 98SE, ME, 2000 y XP. Para aprovechar las caractersticas del sistema operativo, las aplicaciones con varios subprocesos se hicieron ms importantes. Hoy en da, la capacidad de realizar ms de una tarea a la vez es un rasgo necesario para una aplicacin. Visual Basic 6.0 y versiones anteriores compilaban aplicaciones de un solo proceso, lo que significaba que, sin importar lo que pasara, la aplicacin de VB slo poda hacer una cosa a la vez. En realidad, en un sistema con un solo procesador no importa qu herramienta use para escribir su aplicacin, ya que todo sigue sucediendo en un proceso lineal. Si es un programador de C++ puede crear nuevos subprocesos y realizar tareas mientras tienen lugar otros sucesos, pero en realidad slo se comparte el mismo tiempo con el resto de procesos que se estn ejecutando en el sistema. Si slo hay un procesador, slo puede suceder una cosa cada vez. Este concepto se llama multitarea preferente o preemtiva ("pre-emtive" en ingls).
Multitarea preferente
La multitarea preferente divide el tiempo del procesador entre las tareas o subprocesos en ejecucin. Cuando una tarea se est ejecutando, usa un espacio de tiempo o slot. Cuando el espacio de tiempo de la tarea que se est ejecutando caduca, en aproximadamente 20 milisegundos dependiendo del sistema operativo, se invalida y otra tarea recibe dicho espacio de tiempo. El sistema guarda el contexto actual de la tarea invalidada y, cuando a sta se le asigna el siguiente slot de tiempo, se restaura su contexto y el proceso se reanuda. Este bucle de tarea contina repetidamente hasta que la tarea es abortada o finaliza. La multitarea preferente da al usuario la impresin de que se realiza ms de una tarea a la vez. Por qu finalizan algunas tareas antes que otras, aunque se inicie antes la ltima en terminar?
706
Multiprocesamiento simtrico
En un sistema multiprocesador pueden tener lugar realmente ms de una tarea a la vez. Como cada procesador puede asignar espacios de tiempo a las tareas que quieren ejecutarse, puede realizar ms de una tarea a la vez. Cuando necesita ejecutar un subproceso largo que requiera mucho trabajo del procesador, como ordenar 10 millones de registros por nombre, direccin, cdigo postal, apellidos y pas, si se usan varios procesadores el trabajo concluir antes que si se usa un solo procesador. Si puede delegar ese trabajo en otro procesador, la aplicacin actualmente en curso no se ver afectada en absoluto. Tener ms de un procesador permite este tipo de multiprocesamiento simtrico (SMP). La figura 33.1 muestra las opciones del procesador para SQL Server 2000. Si est ejecutando SQL Server en un equipo multiprocesador, puede definir el nmero de procesadores que deben usarse para las tareas largas y que exigen el tipo mencionado. SQL lleva esto un poco ms all, realizando consultas a lo largo de los diferentes procesadores, uniendo los datos cuando se completa el ltimo
707
subproceso y mostrando los datos al usuario. Esto se conoce como sincronizacin de subprocesos. El subproceso principal, que crea varios subprocesos, debe esperar a que todos los otros subprocesos estn completos antes de continuar con el proceso.
Observe que, cuando se usa un sistema SMP, un solo subproceso sigue ejecutndose en un solo procesador. La aplicacin VB6 se ejecuta exactamente igual que si le aade otro procesador adicional. Su aplicacin de Access 2.0 de 16 bits tampoco se ejecuta mejor porque 16 bits siguen siendo igual a un solo proceso. Deber crear nuevos procesos en los dems procesadores para aprovecharlos. Esto significa que no diseamos una GUI multiprocesador, es decir, una GUI que cree otros procesos y pueda reaccionar cuando esos procesos se completen o sean interrumpidos, mientras sigue permitiendo al usuario usar la GUI para otras tareas.
708
lo mismo en un potente servidor, el Unisys de 32 procesadores con 1 terabyte de RAM, no percibir ninguna prdida de rendimiento. Cuanta ms memoria tenga, ms espacio fsico tendrn las aplicaciones para crear nuevos subprocesos. Cuando escriba aplicaciones que creen subprocesos, asegrese de que tiene esto en cuenta. Cuantos ms subprocesos cree, ms recursos consumir su aplicacin. Esto podra llegar a causar un rendimiento menor que el de una aplicacin de un solo proceso; todo depende del SO. "Cuantos ms mejor" se refiere a los recursor, no a los subprocesos. Por tanto, tenga cuidado al crear subprocesos en la nueva versin de Tetris con mltiples subprocesos que est escribiendo en C#.
Dominios de aplicacin
Anteriormente se indic que el MFC SDK define un proceso como una instancia de una aplicacin que se ejecuta. Cada aplicacin que se est ejecutando crea un nuevo subproceso principal, que dura lo que la instancia de la aplicacin. Como cada aplicacin es un proceso, cada instancia de una aplicacin debe tener aislamiento de procesos. Dos instancias de Microsoft Word actan independientemente entre s. Cuando hace clic en Ortografa y gramtica, la InstanciaA de Word no comprueba los errores ortogrficos del documento que se est ejecutando en la InstanciaB de Word. Incluso si la InstanciaA de Word intenta pasar un puntero de memoria a la InstanciaB de Word, la InstanciaB no sabr qu hacer con l, o siquiera dnde buscarlo, ya que los punteros de memoria slo se refieren a los procesos en los que se ejecutan. En .NET Framework, los dominios de aplicacin se usan para proporcionar seguridad y aislamiento de aplicacin para el cdigo gestionado. Algunos dominios de aplicacin pueden ejecutarse en un solo proceso o subproceso, con la misma proteccin que si las aplicaciones se estuviesen ejecutando en varios procesos. El consumo de recursos se reduce con este concepto, ya que las llamadas no necesitan estar circunscritas a los lmites de los procesos si las aplicaciones necesitan compartir datos. Por el contrario, un solo dominio de aplicacin puede ejecutarse en varios subprocesos. Esto es posible gracias al modo en el que el CLR ejecuta el cdigo. Una vez que el cdigo est preparado para ser ejecutado, el compilador JIT ya le ha hecho pasar el proceso de verificacin. Este proceso de verificacin garantiza que el cdigo no va a realizar acciones no vlidas, como accesos a memoria imprevistos, que provoquen un fallo de pgina. Este concepto de cdigo de tipo seguro garantiza que el cdigo no va a violar ninguna regla despus de que el verificador lo haya aprobado al pasar de cdigo MSIL a PE. En las aplicaciones tpicas Win32 no existan mecanismos de proteccin que evitaran que un fragmento de cdigo suplantase a otro, de modo que cada aplicacin necesitaba aislamiento de proceso. En .NET, como la seguridad de tipo est garantizada, resulta seguro ejecutar varias aplicaciones de varios proveedores en el mismo dominio de aplicacin.
709
Botn Cancelar
Cualquier aplicacin que tenga un botn Cancelar en un formulario debe seguir este proceso:
710
1. Abrir y mostrar el formulario en forma modal. 2. Iniciar el proceso que tiene lugar en el nuevo proceso. 3. Esperar a que el subproceso termine. 4. Cerrar el formulario. Al seguir estos pasos, el evento click del botn Cancelar tiene lugar si el usuario hace clic en el botn mientras otro subproceso se est ejecutando. Si el usuario hace clic en el botn Cancel, en realidad hace clic mientras el proceso est ejecutndose en un subproceso que no es el que controla el evento click, y el cdigo debe entonces detener el proceso en el otro subproceso que se est ejecutando. Este es un rasgo de la GUI que convierte una buena aplicacin en una aplicacin genial.
711
Descripcin Inicia ThreadAbortException, que puede finalizar el subproceso Interrumpe un subproceso que se encuentra en estado de subproceso WaitSleepJoin Espera un subproceso Reanuda un subproceso que ha sido suspendido Inicia la ejecucin del subproceso Suspende el subproceso
712
public static int Main(String[] args) { Threads testThreading = new Threads(); Thread t1 = new Thread(new ThreadStart(testThreading.Threader1)); t1.Start(); Thread t2 = new Thread(new ThreadStart(test.Threading.Threader2)); t2.Start(); ConsoleReadLine(); return 0; } }
Cuando crea una variable de tipo thread, el procedimiento que controla el subproceso debe existir para el delegado ThreadStart. En caso contrario, se produce un error y la aplicacin no compila. La propiedad Name establece o recupera el nombre de un subproceso. Esto le permite usar un nombre con sentido en lugar de una direccin de cdigo hash para hacer referencia a los subprocesos que se estn ejecutando. Esto es til cuando se usan las utilidades de depuracin de Visual Studio .NET. En la barra de tareas de depurado hay una lista desplegable con los nombres de los subprocesos en ejecucin. Aunque no se puede "salir" del subproceso y saltar a otro subproceso con el depurador, es til para saber en qu subproceso ha ocurrido un error. Una vez declaradas, nombradas e iniciadas las variables de los subprocesos, necesita hacer algo en los subprocesos creados. Los nombres de procedimiento que se han pasado al constructor de subprocesos eran Threader1 y Threader2. Ahora puede agregar cdigo a estos mtodos y observar cmo actan. El cdigo debera tener un aspecto como el del listado 33.2.
Listado 33.2. Cmo recuperar informacin de los subprocesos en ejecucin using System; using System.Threading; public class Threads { public void Threader1() { Console.WriteLine(" *** Threader1 Information ***"); Console.WriteLine ("Name: " + Thread.CurrentThread.Name); Console.WriteLine (Thread.CurrentThread); Console.WriteLine ("State: " + Thread.CurrentThread.ThreadState);
713
Console.WriteLine ("Priority: " + Thread.CurrentThread.Priority); Console.WriteLine(" *** End Threader1 Information * * * " ) ; } public void Threader2() { Console.WriteLine(" *** Threader2 Information * * * " ) ; Console.WriteLine ("Name: " + Thread.CurrentThread.Name); Console.WriteLine (Thread.CurrentThread); Console.WriteLine ("State: " + Thread.CurrentThread.ThreadState); Console.WriteLine ("Priority: " + Thread.CurrentThread.Priority); Console.WriteLine(" *** End Threader2 Information * * * " ) ; } } public class ThreadTest { public static int Main(String[] { args)
Threads testThreading = new Threads(); Thread t1 = new Thread(new ThreadStart(testThreading.Threader1)); t1.Name = "Threader1"; t1.Start(); Thread t2 = new Thread(new ThreadStart(testThreading.Threader2)); t2.Name = "Threader2"; t2.Start(); Console.ReadLine(); return 0; } }
Cuando ejecuta la aplicacin, el resultado en su consola debera parecerse al mostrado en la figura 33.2. El resultado que muestra la figura 33.2 no es muy bonito. Si realiza otra llamada, estar trabajando con subprocesos. Sin establecer una o dos propiedades, el procedimiento Threader1 nunca terminar antes de que empiece Threader2. Cuando se ejecute el siguiente cdigo:
t1.Start();
714
comenzar la ejecucin del cdigo de Threader1. Como es un subproceso, dispone de unos 20 milisegundos de espacio de tiempo, y en ese perodo de tiempo alcanzar la segunda lnea de cdigo de la funcin, devolver el control al sistema operativo y ejecutar la siguiente lnea de cdigo:
t2.start();
El procedimiento Threader2 se ejecuta entonces durante su slot de tiempo y es luego sustituido por el subproceso t1. Este ir y venir de procesos contina hasta que los dos procedimientos puedan finalizar.
C:\Documents and Settings\Jesus\Mis documentos\Proyectos de C# *** Threader1 Information *** Name: Threader1 System.Threading.Thread State: Running Priority: Highest *** End Threader1 Information *** *** Threader2 Information *** Name: Threader2 System.Threading.Thread State: Running Priority: Normal *** End Threader2 Information ***
Cuando establezca la prioridad al mximo, t1 finalizar antes que t2. Si ejecuta la aplicacin de nuevo, el resultado deber ser como el que muestra la figura 33.3. La enumeracin ThreadPriority establece cmo se planifica un determinado subproceso en relacin a los otros subprocesos en ejecucin. ThreadPriority puede adoptar cualquiera de los siguientes valores: AboveNormal, BelowNormal, Highest, Lowest o Normal. El algoritmo que determina el orden de los subprocesos vara en funcin del sistema operativo en el que se ejecutan los subprocesos. Por defecto, cuando se crea un nuevo subproceso, se le concede la prioridad 2, que equivale a Normal en la enumeracin.
715
716
public void Threader2() { for(int intX = 0; intX < 50; intX++) { if (intX == 5) { Thread.Sleep(500); Console.WriteLine("Thread2 Sleeping");} } } } public class ThreadTest { public static int Main(String[] { args)
Threads testThreading = new Threads(); Thread t1 = new Thread(new ThreadStart(testThreading.Threader1)); t1.Priority = ThreadPriority.Highest; t1.Start(); Thread t2 = new Thread(new ThreadStart(testThreading.Threader2)); t2.Start(); Console.ReadLine(); return 0; } }
Observe que se ha establecido la propiedad Priority del subproceso t1 al mximo. Esto significa que, pase lo que pase, se ejecutar antes de que comience t2. Sin embargo, en el procedimiento Threader1 tiene el siguiente bloque if:
for(int intX = 0; intX < 50; intX++) { if(intX == 5){ Thread.Sleep(500); Console.WriteLine("Thread1 Sleeping");} }
Esto indica al subproceso t1 que se bloquee durante 500 milisegundos, abandonando su actual espacio de tiempo y permitiendo que el subproceso t2 comience a ejecutarse. Cuando los dos subprocesos se han completado, se invoca al mtodo Abort y los subprocesos son eliminados. Las llamadas al mtodo Thread.Suspend bloquean un subproceso indefinidamente, hasta que otro subproceso lo active de nuevo. Si alguna vez ha observado el contador del procesador en el Administrador de tareas llegar al ciento por
717
ciento cuando no est gastando memoria, puede entender lo que sucede cuando un subproceso se bloquea. Para que el subproceso vuelva a funcionar, debe llamar al mtodo Resume desde otro subproceso para que pueda reanudarse. El siguiente cdigo muestra los mtodos Suspend y Resume:
Thread.CurrentThread.Suspend; Console.WriteLine("Thread1 Suspended"); Thread.CurrentThread.Resume; Console.WriteLine("Thread1 Resumed");
Aqu debe tener mucha precaucin: suspender subprocesos puede acarrear resultados indeseados. Debe asegurarse de que otro subproceso reanude el subproceso. La figura 33.4 muestra lo descrito en el prrafo anterior. Observe en la figura que la ventana de la consola est en la lnea de cdigo T1 Suspended. Este ejemplo refleja una prueba, de modo que no necesita el mtodo Resume. Los resultados del Administrador de tareas indican el estado del sistema.
T h r e a d S t a t e es una combinacin bit a bit de la enumeracin FlagsAttribute. En cualquier momento, un subproceso puede estar en ms de un estado. Por ejemplo, si un subproceso es un subproceso en segundo plano y se est ejecutando en ese momento, el estado puede ser Running y Background. La tabla 33.2 describe los posibles estados en los que se puede encontrar un subproceso.
718
Tabla 33.2. Miembros de ThreadState Miembro Aborted AbortRequested Background Running Suspended SuspendRequested Unstarted WatSleepJoin Descripcin El subproceso se ha anulado. Se ha solicitado la anulacin del subproceso. El subproceso est ejecutndose como subproceso en segundo plano. El subproceso est ejecutndose. El subproceso se ha suspendido. Se ha solicitado que el subproceso se suspenda. El subproceso no se ha iniciado. El subproceso se ha bloqueado como resultado de una llamada a Wait , Sleep o Join.
por
Console.Writeline("Writing");
La segunda vez que ejecute el cdigo, obtendr dos conjuntos de resultados. El resultado Writing no aparece en la consola hasta que los dos subprocesos han terminado.
Listado 33.4. Cmo unir subprocesos
using System; using System.Threading; public class Threads { public void Threader1() { for (int intX = 0; intX < 50; { if(intX == 5) {
intX++)
719
Thread.Sleep(500); Console.WriteLine("Thread1 } } }
Sleeping");
public void Threader2() { for (int intX = 0; intX < 50; i n t X + + ) { if(intX == 5) { Thread.Sleep(500); Console.WriteLine("Thread2 Sleeping"); } } } } public class ThreadTest { public static int Main(String[] args) { Threads testThreading = new Threads(); Thread t2 = new Thread(new ThreadStart(testThreading.Threader2)); t2.Start(); Thread t1 = new Thread(new ThreadStart(testThreading.Threader1(); t1.Priority = ThreadPriority.Highest; t1.Start(); /* Invoca a Join para que espere a que todos los subprocesos terminen */ t2.Join(); Console.WriteLine("Writing");
Console.ReadLine(); return 0; } }
Como puede ver, el establecimiento de varias propiedades en los subprocesos simplifica mucho su control. Tenga en cuenta que tras suspender un subproceso, debe reactivarlo o su sistema consumir recursos innecesariamente.
720
Es muy probable que durante el bucle, un subproceso en ejecucin se detenga para permitir que otro subproceso use este mtodo. Recuerde que esto slo ocurre si permite que haya mltiples subprocesos accediendo a este bloque de cdigo. Al escribir aplicaciones con mltiples subprocesos, esto sucede muy a menudo, de modo que necesita saber cmo solucionar la situacin. El siguiente cdigo resuelve este problema:
lock(this) { int Y; int V; for (int Z = 0; Z < 20; Z++) { return Y * V;} }
La instruccin Lock es un modo de obligar a los subprocesos a unirse. Su implementacin es un poco diferente a la del mtodo Join. Con Lock evaluamos una expresin que se ha pasado al bloque Lock. Cuando un subproceso llega al bloque Lock, espera hasta que pueda conseguir un bloqueo exclusivo de la expresin que se est evaluando antes de intentar seguir con el proceso. Esto asegura que no se estropeen los datos compartidos al usar varios subprocesos. La clase Monitor permite la sincronizacin mediante los mtodos Monitor.Enter, Monitor.TryEnter y Monitor.Exit. Tras usar un bloqueo en una region de cdigo, puede usar los mtodos Monitor.Wait, Monitor.Pu1se y Monitor.PulseAll para determinar si un subproceso
721
debe continuar con un bloqueo o si algunos mtodos bloqueados anteriormente estn ya disponibles. Wait levanta el bloqueo si ste se mantiene y espera a que se le notifique. Cuando se invoca a Wait se rompe el bloqueo y retorna para volver a cerrarlo.
Sondeo y escucha
El sondeo y la escucha son otras dos instancias que representan la utilidad del multiprocesamiento. Las bibliotecas de clases como System.Net.Sockets, incluyen un rango completo de clases de multiproceso que pueden ayudarle a crear agentes de escucha TCP, agentes de escucha UDP y una gran cantidad de tareas de red que requieren multiproceso. Observe la clase TimerCallBack del espacio de nombres System.Threading. Esta clase es muy parecida a las otras que hemos estado usando hasta ahora, excepto que una de las partes del constructor es un temporizador, lo que le permite realizar sondeos en busca de acciones cada cierto intervalo. Puede conseguir el mismo resultado agregando un control temporizador al formulario, pero usando la clase TimerCallBack el temporizador y la respuesta al procedimiento son automticos. El listado 33.5 usa una devolucin de llamada temporizada para sondear un directorio en busca de archivos. Si se encuentra un archivo, es rpidamente borrado. Slo debe ejecutar este cdigo en un directorio de prueba, porque elimina archivos. El siguiente cdigo de ejemplo busca en el directorio C:\Poll. El constructor de la clase TimerCallBack espera una direccin en la que se va a ejecutar el subproceso, un tipo de datos object que representa el estado del temporizador, un tiempo de vencimiento que representa el perodo de tiempo hasta el que se van a realizar sondeos, y un perodo que es una variable en milisegundos que indica cundo se produce el intervalo de sondeo.
Listado 33.5. Cmo usar el delegado TimerCallBack
using System; using System.IO; using System.Threading; namespace cSharpTimerCallBack { class Class1 { public static void Main() { Console.WriteLine ("Checking directory updates every 2 seconds."); Console.WriteLine
722
(" (Hit Enter to terminate the sample) " ) ; Timer timer = new Timer(new TimerCallback(CheckStatus), null, 0, 2000); Console.ReadLine(); timer.Dispose(); } static void Checkstatus(Object state) { string[] str = Directory.GetFiles("C:\\Poll"); if (str.Length > 0) { for (int i = 0; i < str.Length; i++) { Console.WriteLine(str[i]); File.Delete(str[i]); } } Console.WriteLine("Directory Empty"); } } }
Tras ejecutar este programa durante un tiempo y copiar peridicamente unos cuantos archivos en el directorio C:\Poll, el resultado en la consola tendr un aspecto parecido al de la figura 33.5.
C:\Documents and Settings\Jesus\Mis documentos\Proyectos de C# Checking directory updates every 2 seconds. (Hit Enter to terminate the sample) C:\Poll\Bones.txt C:\Poll\ncc1701.xml C:\Poll\q300972_w2k_sp3_x86_en.exe Directory Empty Directory Empty C:\Poll\Bones.txt Directory Empty Directory Empty C:\Poll\q300972_w2k_sp3_x86_en.exe Directory Empty C:\Poll\ncc1701.xml Directory Empty Directory Empty Directory Empty
Resumen
En este captulo ha aprendido a implementar mltiples subprocesos en C# con el espacio de nombres System.Thread. La idea bsica tras el multiprocesamiento es simple: al crear ms de un subproceso, puede realizar ms de una tarea a la vez. Tiene que probar antes el
723
nmero de subprocesos que vaya a crear. Demasiados subprocesos pueden causar problemas de recursos. Si no se crean suficientes subprocesos, puede que la aplicacin no trabaje con todo su potencial. Con el ejemplo que ha creado aqu debera estar preparado para implementar subprocesos en sus propias aplicaciones. Simplemente evite tomar riesgos porque, como sabe, las aplicaciones con varios subprocesos pueden generar muchos problemas. Como siempre, planifique su aplicacin con antelacin y decida si el uso del multiprocesamiento es una parte importante de este proceso de planificacin.
724
Como programador de Windows, probablemente haya creado muchos componentes COM, bien como DLLs individuales o como DLLs que se ejecutan en servicios COM+. Con la llegada de .NET se preguntar si debe rescribir todo con este nuevo lenguaje. Las buenas noticias son que no tiene que rescribir ninguno de sus componentes. Microsoft ha sido suficientemente considerado como para proporcionarnos las herramientas necesarias para usar sus componentes existentes de .NET. Adems, estos componentes pueden invocarse de forma segura desde el entorno comn de ejecucin (CLR). En este captulo aprender lo sencillo que es modificar el cdigo existente y usarlo desde un cliente administrado por .NET. El cliente puede ser cualquier cosa: una aplicacin Web, otro componente .NET o incluso una aplicacin basada en servicios. No importa; la funcionalidad principal se mantiene en todos los tipos de aplicaciones. Aunque siempre tiene la opcin de rescribir su cdigo, no es necesario. Probablemente querr usar .NET para todos sus programas, especialmente el desarrollo GUI, ya que es mucho ms sencillo de usar que las versiones anteriores. Al mismo tiempo, no querr rescribir toda la lgica empresarial bsica de sus aplicaciones. Con .NET todo esto es posible; puede convertir sus aplicaciones a .NET mientras sigue usando los miles de lneas de cdigo existente que ya ha escrito en sus componentes.
727
En este captulo aprender a usar sus componentes COM existentes desde un cliente .NET, mediante las herramientas suministradas con .NET, y ver cmo todo sucede en segundo plano.
728
El proxy creado para la interoperabilidad se basa en los metadatos expuestos en la biblioteca de tipos del componente COM al que se est intentando acceder. Las bibliotecas de tipos COM pueden hacerse accesibles de una de estas dos formas: Las bibliotecas de tipos pueden encontrarse como archivos individuales. Las bibliotecas individuales de tipos suelen tener la extensin TLB. Las bibliotecas individuales de tipos ms antiguas pueden tener la extensin OLB. Si est creando una DLL ActiveX de Visual Basic, puede crear una biblioteca individual de tipos para su componente seleccionando la opcin Archivos de servidor remoto en el cuadro de dilogo Propiedades del proyecto. Las bibliotecas de tipos tambin pueden encontrarse incrustadas en un servidor COM como un recurso binario. Los servidores COM dinmicos, empaquetados como DLL, y los servidores COM estticos, empaquetados como EXE, pueden incluir la biblioteca de tipos como un recurso del propio servidor COM. Los componentes COM creados con Visual Basic tienen la biblioteca de tipos compilada dentro de la DLL.
En la siguiente seccin aprender a crear el ensamblado de interoperabilidad a partir de una DLL COM mediante los dos mtodos descritos al principio de esta seccin: usando la utilidad Tlbimp y haciendo referencia directamente a la DLL desde Visual Studio .NET.
729
/nologo /out:filename
/primary /publickey:filename
/reference:filename
/silent /strictref
/sysarray
730
Opcin /unsafe
Descripcin Genera interfaces sin comprobaciones de seguridad de .NET Framework. Esta opcin no se debe utilizar a no ser que se conozcan los riesgos que supone exponer este cdigo como no seguro Muestra informacin adicional sobre la biblioteca de tipos importada cuando se ejecuta tlbimp.exe Muestra ayuda sobre la sintaxis para tlbimp.exe
/verbose
/?
Este comando produce un ensamblado .NET con una extensin DLL que recibe el mismo nombre base que la biblioteca incrustada en el archivo de la biblioteca de tipos (que puede ser diferente del nombre de archivo de la propia biblioteca de archivos). El comando tlbimp admite el nombre de un archivo de biblioteca de tipos como dato introducido:
tlbimp server.tlb
Tambin puede admitir el nombre de un servidor COM que contiene la biblioteca de tipos incrustada:
tlbimp server.dll
Al usar la opcin /out, se puede especificar un nombre alternativo para el ensamblado .NET creado:
tlbimp server.dll /out:dotNetServer.dll
El ensamblado producido por la herramienta Tlbimp.exe es un ensamblado .NET estndar que puede examinarse con lldasm.exe. El ensamblado no contiene el cdigo del servidor COM; en su lugar, contiene referencias que ayudan al CLR a encontrar los objetos COM almacenados en el servidor, como los GUID del objeto COM. Imagine el ensamblado generado por tlbimp como un puente que conecta el cdigo .NET con el servidor COM. Como el cdigo COM sigue estando en el servidor COM, no debe olvidar instalar e inscribir en el registro todos los servidores COM que piense usar en sus aplicaciones .NET. En realidad, esto supone una gran ventaja para el programador. Como el servidor COM sigue registrado en Windows, las aplicaciones COM estndar que no son conscientes de la existencia de .NET pueden seguir usando el mismo servidor COM sin trasladar cdigo a una plataforma especfica .NET.
731
con varias funciones de clase habituales, como establecer y recuperar una propiedad, desencadenar un evento y devolver un valor de un mtodo que tenga parmetros de entrada.
Listado 34.1. Cdigo de servidor COM para Visual Basic 6.0 Option Explicit Private strMessage As String Public Event COMEvent(Message As String) Private Sub Class_Initialize() strMessage = "Default Message" End Sub Public Property Get Message() As String Message = strMessage End Property Public Property Let Message(ByVal vNewValue As String) strMessage = vNewValue End Property Public Function SquareIt(int1 As Integer, int2 As Integer) As Integer SquareIt = int1 * int2 End Function Public Sub FireCOMEvent() RaiseEvent COMEvent(StrMessage) End Sub
Este cdigo se coloca en un mdulo de clase llamado COMObject. El mdulo de clase est en un proyecto llamado VB6COMServer. Visual Basic 6.0 compila este cdigo en un servidor COM en proceso e incrusta una biblioteca de tipos en el servidor. La representacin legible de la biblioteca de tipos, escrita en Lenguaje de definicin de interfaz (IDE) de COM se muestra en el listado 34.2.
Listado 34.2. Fuente IDL para el servidor COM del listado 34.1 // Archivo .IDL generado (por el Visor de objetos OLE/COM) // // typelib filename: VB6COMServer.dll [ uuid(B4096C50-ACA4-4E1F-8D36-F36F1EE5F03B), version(1.0) ] library VB6COMServer { // TLib : // TLib : OLE Automation : {00020430-0000-0000-
732
C000-000000000046} importlib("stdole2.tlb"); // Declaracin anticipada de todos los tipos definidos en // esta biblioteca de clases interface _COMObject; dispinterface __COMObject; [ odl, uuid(5960D780-FEA2-4383-B2CB-9F78E4677142), version(1.0), hidden, dual, nonextensible, oleautomation ] interface _COMObject : IDispatch { [id(0x68030000), propget] HRESULT Message([out, retval] BSTR* ); [id(0x68030000), propput] HRESULT Message([in] BSTR ); [id(0x60030002)] HRESULT SquareIt( [in, out] short* int1, [in, out] short* int2, [out, retval] short* ); [id(0x60030003)] HRESULT FireCOMEvent(); }; [ uuid(50730C97-09EB-495C-9873-BEC6399AA63A), version(1.0) ] coclass COMObject { [default] interface _COMObject; [default, source] dispinterface __COMObject; }; [ uuid(A4D4C3D8-DFFF-45DB-9A14-791E4F82EF35), version(1.0), hidden, nonextensible ] dispinterface __COMObject { properties: methods: [id(0x00000001)] void COMEvent([in, out] BSTR* Message); }; };
733
Para crear el ensamblado de interoperabilidad que permite a la aplicacin C# usar la DLL no administrada, debe ejecutar la utilidad Tlbimp descrita en la anterior seccin. En la figura 34.2 puede ver que se usa el parmetro /out para dar al ensamblado de interoperabilidad el nombre compinterop.dll. El nombre del ensamblado resultante puede ser el que prefiera (incluso puede tener el mismo nombre que el componente COM original).
c:\ Smbolo del sistema Visual Studio .NET C:\>tlbimp c:\vb6comserver.dll /out:cominterop.dll Microsoft (R) .NET Framework Type Library to Assembly Converter 1.0.370 Copyright (C) Microsoft Corporation 1998-2001. All rights reserved. Type library imported to C:\cominterop.dll C:\>
El VB6COMServer.dll que se cre usando VB6 ahora puede ser consumido desde cualquier cliente .NET (siempre que la aplicacin haga referencia al ensamblado cominterop.dll y el componente VB6 est registrado en el equipo que est intentando consumir el cdigo). C o m o el resultado de Tlbimp es ahora un ensamblado .NET, puede usar la utilidad ILDASM para ver los detalles sobre el metadato que se cre a partir de la DLL ActiveX que usa realmente el CLR. La figura 34.3 muestra la utilidad ILDSM cuando se ejecuta el nuevo cominterop. dll recin creado. El ensamblado generado al importar la biblioteca de tipos cuyo cdigo fuente se muestra en el listado 34.3 incluye un espacio de nombres llamado cominterop, que es el nombre del ensamblado que se pas al parmetro /out desde la utilidad Tlbimp. Este espacio de nombres debe ser tratado exactamente igual que un espacio de nombres definido por el cdigo o por .NET Framework: el cdigo debe hacer referencia al espacio de nombres cuando use alguna de las clases del espacio de nombres. La figura 34.3 muestra las clases insertadas en el ensamblado generado por tlbimp. La clase que usamos en el cdigo C# para trabajar con el objeto COM tiene el mismo nombre que el objeto COM en la instruccin coclass de la fuente IDL. En el listado 34.3, el objeto COM recibe un nombre coclass de COMObject. El ensamblado generado por tlbimp incluye una clase .NET con el mismo nombre, y es esta clase la que se usa en el cdigo para trabajar con el objeto Visual Basic COM.
734
735
Una vez que la aplicacin hace referencia al ensamblado, puede usarlo exactamente igual que cualquier otro ensamblado .NET. Como Visual Studio .NET dispone de funciones tan tiles como completar o listar miembros automticamente una vez que se agrega la referencia, sus mtodos, eventos y propiedades estn a su disposicin mediante el IDE. La figura 34.5 muestra el listado automtico de miembros en funcionamiento despus de que la instancia del objeto Cominterop haya sido creada y de que se haga referencia al ensamblado con la instruccin using.
private void button1_Click(object sender, System.EventArgs e) { COMObject ObjectInstance; short Num1; short Num2; short Sum; ObjectInstance = new COMObjectClass(); Num1 = 123; Num2 = 456; Sum = ObjectInstance
} } }
736
Para comprobar todos los mtodos, propiedades y eventos que ha escrito en la DLL ActiveX, copie el listado 34.3 en la aplicacin WindowsForms.
Listado 34.3. Cdigo de cliente COM escrito en C# /// <summary> /// El punto de entrada principal para la aplicacin. /// </summary> [STAThread] static void Main() { Application.Run(new Form1()); } // Cree un controlador para el evento private __COMObject _COMEventEventHandler COMEventHandler Instance; private void button1_Click(object sender, System.EventArgs e) { // cree una nueva instancia de la clase COMObject COMObject ObjectInstance; short Num1; short Num2; short Sum; ObjectInstance = new COMObjectClass(); Num1 = 5; Num2 = 6; // Llame al mtodo SquareIt Sum = ObjectInstance.SquareIt(ref Num1, ref Num2); listBox1.Items.Add(Sum.ToString()); ListBox1.Items.Add(ObjectInstance.Message); // Establezca un valor de mensaje diferente del que se // ofrece por defecto ObjectInstance.Message = "C# Rocks"; COMEventHandlerInstance = new __COMObject_COMEventEventHandler(COMEventHandler); ObjectInstance.COMEvent += COMEventHandlerInstance; ObjectInstance.FireCOMEvent(); } void COMEventHandler(ref string Message) { listBox1.Items.Add(Message); } } }
737
Form1
Como cualquier otro objeto de .NET, el operador new se usa para crear una nueva instancia de la clase COMObject, como muestra el siguiente fragmento de cdigo:
ObjectInstance = new COMObject();
Tras instanciar el nombre de la variable ObjectInstance, use el objeto igual que cualquier otro objeto .NET; no hace falta hacer nada especial. El RCW se encarga de toda la interoperabilidad, conversiones de tipos y organizacin de objetos para los tipos, de modo que no se dar cuenta de ninguno de los preparativos internos COM que se estn produciendo. Si ha usado la interoperabilidad COM de VB.NET, percibir algunas diferencias respecto al modo en que se pasan los parmetros a los mtodos en C#. Si observa el cdigo de C# del mtodo SquareIt, ver que se ha agregado la palabra clave ref:
Num1 = 5; Num2 = 6; // Llame al mtodo SquareIt Sum = ObjectInstance.SquareIt(ref
Num1,
ref
Num2);
Los servidores COM de Visual Basic pueden pasar valores por valor o por referencia. El cdigo C# necesita usar las palabras clave adecuadas cuando pasa parmetros a las llamadas de mtodos COM. Puede usar ILDASM para que le ayude a determinar si un parmetro debe pasarse por valor o por referencia. Abra el ensamblado generado por Tlbimp usando la herramienta ILDASM y mire la definicin del mtodo al que quiere llamar. En este caso, debe llamar al mtodo SquareIt() que se muestra en el ensamblado con la siguiente firma:
SquareIt : int16(int16&,int16&)
Tras los dos puntos situamos el tipo de valor devuelto por el mtodo. La firma del mtodo SquareIt() muestra un tipo devuelto int16, que, en la jerga del lenguaje intermedio, indica un entero de 16 bits. Tras el tipo de los parmetros se
738
coloca un signo & que indica que el parmetro debe pasarse por referencia. Los parmetros que deben ser pasados por referencia deben ir acompaados de la palabra clave ref en el cliente C#. Los parmetros que deben pasarse por valor no aparecen con el smbolo & en el ensamblado. En este caso, el cliente C# no necesita usar la palabra clave ref en los parmetros.
739
2. Tras seleccionar el componente COM que debe usar, puede utilizar el mismo cdigo que us para escribir la aplicacin de formulario de Windows, cambiando solamente el ensamblado al que se hace referencia, que en este caso ser VB6ComServcr y no Cominterop.
using VB6COMServer; Object Instance = new COMObject.Class();
Como puede ver, hacer referencia a un componente COM directamente desde el IDE es incluso ms sencillo usando la utilidad Tlbimp, aunque pierda parte de versatilidad respecto a lo que puede hacer realmente con el componente.
HRESULT
MSEE_E_APPDOMAINUNLOADED COR_E_APPLICATION COR_E_ARGUMENT o E_INVALIDARG COR_E_ARGUMENTOUTOFRANGE COR_E_ARITHMETIC o ERROR_ARITHMETIC_OVERFLOW COR_E_ARRAYTYPEMISMATCH COR_E_BADIMAGEFORMAT o ERROR_BAD_FORMAT COR_E_COMEMULATE_ERROR COR_E_CONTEXTMARSHAL COR_E_CORE NTE_FAIL COR_E_DIRECTORYNOTFOUND o ERROR_PATH_NOT_FOUND COR_E_DIVIDEBYZERO COR_E_DUPLICATEWAITOBJECT COR_E_ENDOFSTREAM COR_E_TYPELOAD COR_E_EXCEPTION
Excepcin .NET
AppDomainUnloadedException ApplicationException ArgumentException ArgumentOutOfRangeException ArithmeticException ArrayTypeMismatchException BadlmageFormatException COMEmulateException ContextMarshalException CoreException CryptographicException DirectoryNotFoundException DivideByZeroException DuplicateWaitObjectException EndOfStreamException EntryPointNotFoundException Exception
740
HRESULT
COR_E_EXECUTIONENGINE COR_E_FIELDACCESS COR_E_FILENOTFOUND o ERROR_FILE_NOT_FOUND COR_E_FORMAT COR_E_INDEXOUTOFRANGE COR_E_INVALIDCAST o E_NOINTERFACE COR_E_INVALIDCOMOBJECT COR_E_INVALIDFILTERCRITERIA COR_E_INVALIDOLEVARIANTTYPE COR_E_INVALIDOPERATION COR_E_IO COR_E_MEMBERACCESS COR_E_METHODACCESS COR_E_MISSINGFIELD COR_E_MISSINGMANIFESTRESOURCE COR_E_MISSINGMEMBER COR_E_MISSINGMETHOD COR_E_MULTICASTNOTSUPPORTED COR_E_NOTFINITENUMBER E_NOTIMPL COR_E_NOTSUPPORTED COR_E_NULLREFERENCE o E_POINTER COR_E_OUTOFMEMORY o E_OUTOFMEMORY COR_E_OVERFLOW COR_E_PATHTOOLONG o ERROR_FILENAME_EXCED_RANGE COR_E_RANK COR_E_REFLECTIONTYPELOAD COR_E_REMOTING COR_E_SAFEARRAYTYPEMISMATCH COR_E_SECURITY CORE_SERIALIZATION COR_E_STACKOVERFLOW o ERROR_STACK_OVERFLOW COR_E_SYNCHRONIZATIONLOCK COR_E_SYSTEM COR_E_TARGET COR_E_TARGETINVOCATION COR_E_TARGETPARAMCOUNT COR_E_THREADABORTED
Excepcin .NET
ExecutionEngineException FieldAccessException FileNotFoundException FormatException IndexOutOfRangeException InvalidCastException InvalidComObjectException InvalidFilterCriteriaException InvalidOleVariantTypeException InvalidOperationException lOException AccessException MethodAccessException MissingFieIdException MissingManifestResourceException MissingMemberException MissingMethodException MulticastNotSupportedException NotFiniteNumberException NotImplementedException NotSupportedException NullReferenceException OutOfMemoryException OverflowException PathTooLongException RankException ReflectionTypeLoadException RemotingException SafeArrayTypeMismatchException SecurityException SerializationException StackOverflowException SynchronizationLockException SystemException TargetException TargetlnvocationException TargetParameterCountException ThreadAbortException
741
HRESULT
COR_E_THREADINTERRUPTED COR_E_THREADSTATE COR_E_THREADSTOP COR_E_TYPELOAD COR_E_TYPEINITIALIZATION COR E VERIFICATION COR_E_WEAKREFERENCE COR_E_VTABLECALLSNOTSUPPORTED Cualquier otro HRESULT
Excepcin .NET
ThreadInterruptedException ThreadStateException ThreadStopException TypeLoadException TypelnitializationException VerificationException WeakReferenceException VTableCallsNotSupportedException COMException
Si su aplicacin debe obtener informacin de error extendida y el objeto COM admite la interfaz IErrorInfo, puede usar el objeto IErrorInfo para conseguir informacin sobre la excepcin. La tabla 34.3 describe la informacin de error adicional.
Tabla 34.3. Informacin de error extendida de interoperabilidad COM Campo de la excepcin ErrorCode HelpLink Informacin de la fuente COM El HRESULT devuelto por la llamada de mtodo Si IErrorInfo->HelpContext no es cero, la cadena se forma concatenando IErrorInfo - > G e t H e l p F i l e con " # " y con IErrorInfo>GetHelpContext. En caso contrario, la cadena devuelta procede de IErrorInfo-> GetHelpFile. Siempre es null Cadena devuelta de IErrorInfo->GetDescription. Cadena devuelta de IErrorInfo->GetSource El seguimiento de la pila de esta excepcin .NET El nombre del mtodo que hizo que se devolviese HRESULT a .NET
Obviamente debe incluir control de errores en sus aplicaciones, aunque est usando interoperabilidad COM. No hay diferencia fundamental entre el modo de codificar componentes COM y el modo de codificar ensamblados .NET, de modo que el control de excepciones estructurado de .NET debe usarse siempre que escriba cdigo susceptible de causar una excepcin.
742
Cuando usa la invocacin de plataforma, puede necesitar cambiar el comportamiento por defecto de la interoperabilidad entre el cdigo gestionado y el no gestionado. Esto puede conseguirse modificando los campos de la clase DLLImportAttribute. La tabla 34.4 describe los campos de la clase DLLImportAttribute que pueden ser personalizados.
Tabla 34.4. Campos DLLImportAttribute Campo de objeto EntryPoint CharSet Descripcin Especifica el punto de entrada de la DLL a la que se va a llamar. Controla el modo en que los argumentos de cadena deben ser organizados para la funcin. El valor por defecto es CharSet.Ansi. Evita que un punto de entrada se modifique para que corresponda con el conjunto de caracteres. El valor por defecto vara dependiendo del lenguaje de programacin. Indica el valor de convencin de llamada utilizado para pasar los argumentos del mtodo. El valor por
ExactSpelling
CallingConvention
743
Campo de objeto
Descripcin defecto es WinAPI, que se corresponde a to __stdcall para las plataformas Intel basadas en 32 bits.
PreserveSig
Indica si la firma administrada del mtodo se debe transformar en una firma no administrada que devuelve HRESULT y que puede tener un argumento adicional [out, retval] para el valor devuelto. El valor por defecto es True (la firma no se puede modificar).
SetLastError
Permite al invocador usar la funcin API Marshal. GetLastWin32Error para determinar si se produjo un error mientras se ejecutaba el mtodo. En Visual Basic, el valor por defecto es True; en C# y C++, el valor por defecto es False.
La operacin de llamar a funciones DLL desde C# es similar a hacerlo desde Visual Basic 6. Sin embargo, con el atributo DLLImport slo est pasando el nombre de la DLL y el mtodo que debe llamar. NOTA: Se recomienda que las llamadas de funcin DLL estn agrupadas en clases separadas. Esto simplifica la codificacin, aisla las llamadas de funciones externas y reduce la carga.
Resumen
Este captulo describe cmo usar objetos COM en cdigo .NET y cmo usar la utilidad Tlbimp para generar ensamblados .NET. Tambin se estudia brevemente como interpretar los ensamblados generados. Adems, se ha explicado cmo escribir cdigo cliente COM en C#, incluyendo llamadas a mtodos COM y a trabajar con propiedades COM. Como puede ver, .NET Framework permite integrar fcilmente el cdigo COM existente en sus aplicaciones .NET. Esta sencilla integracin le da la oportunidad de mover poco a poco partes de una aplicacin a .NET, sin tener que rescribir toda la lgica de componentes COM de C#.
744
747
conjunto de plataformas. Los componentes escritos en C# que aprovechan los servicios COM+ slo pueden ser usados en plataformas que admiten COM+. El cdigo de la clase COM+ compilado en .NET Framework inicia una excepcin de clase PlatformNotSupported si el cdigo intenta acceder a una caracterstica que no existe en la plataforma en tiempo de ejecucin.
748
Clase ApplicationActivationAttribute
Descripcin Especifica si los componentes del ensamblado se ejecutan en el proceso del creador o en un proceso del sistema. Especifica el identificador de la aplicacin (como GUID) para este ensamblado. No se puede heredar esta clase. Especifica el nombre de la aplicacin COM+ que se utilizar para la instalacin de los componentes del ensamblado. No se puede heredar esta clase. Habilita el uso de una cola para el ensamblado marcado y permite a la aplicacin leer llamadas a mtodos desde colas de Message Queue Server. No se puede heredar esta clase. Marca el mtodo con atributos como un objeto AutoComplete . No se puede heredar esta clase. Ajusta la clase ByotServerEx y las interfaces DTC ICreateWithTransactionEx y ICreateWithTipTransactionEx de COM+. No se puede heredar esta clase. Habilita la comprobacin de seguridad en las llamadas a un componente. No se puede heredar esta clase. Permite pasar propiedades de contexto desde el Integrador de transacciones COM (COMTI) al contexto de COM+. Habilita la posibilidad de construccin de objetos COM+. No se puede heredar esta clase. Obtiene informacin acerca del contexto de objetos de COM+. No se puede heredar esta clase. Especifica una descripcin para un ensamblado (aplicacin), componente, mtodo o interfaz. No se puede heredar esta clase.
ApplicationIDAttribute
ApplicationNameAttribute
ApplicationQueuingAttribute
AutoCompleteAttribute
BYOT
ComponentAccessControlAttribute
COMTIIntrinsicsAttribute
ConstructionEnabledAttribute
ContextUtil
DescriptionAttribute
749
Clase EventClassAttribute
Descripcin Marca la clase con atributos como una clase de eventos. No se puede heredar esta clase. Habilita el seguimiento de eventos para un componente. No se puede heredar esta clase. Establece la clase de excepcin de cola para la clase en cola. No se puede heredar esta clase. Permite el acceso a valores intrnsecos de ASP desde ContextUtil. GetNamedProperty . No se puede heredar esta clase. Habilita la capacidad de utilizar una cola para la interfaz marcada. No se puede heredar esta clase. Habilita o deshabilita la activacin justo a tiempo (JIT). No se puede heredar esta clase. Determina si el componente participa en el equilibrio de carga, en caso de que el servicio de equilibrio de carga de componentes est instalado y habilitado en el servidor. Obliga a crear el objeto con atributos en el contexto del creador, si es posible. No se puede heredar esta clase. Habilita y configura el agrupamiento de objetos para un componente. No se puede heredar esta clase. Identifica un componente como componente privado que slo puede ser visto y activado por otros componentes de la misma aplicacin. No se puede heredar esta clase. Recupera informacin de error extendida sobre mtodos relativos a mltiples objetos COM+. Esto tambin incluye mtodos que instalan, importan y exportan componentes y aplica-
EventTrackingEnabledAttribute
ExceptionClassAttribute
IISIntrinsicsAttribute
InterfaceQueuingAttribute
JustlnTimeActivationAttribute
LoadBalancingSupportedAttribute
MustRunlnClientContextAttribute
ObjectPoolingAttribute
PrivateComponentAttribute
RegistrationErrorInfo
750
Clase
RegistrationException RegistrationHelper
Excepcin que se produce cuando se detecta un error de registro. Instala y configura ensamblados en el catlogo de COM+. No se puede heredar esta clase. Almacena objetos en la transaccin actual. No se puede heredar esta clase. Garantiza que la infraestructura realice las llamadas por medio de una interfaz para un mtodo o para cada mtodo de una clase cuando se utiliza el servicio de seguridad. Las clases necesitan utilizar interfaces para poder usar los servicios de seguridad. No se puede heredar esta clase. Describe la cadena de llamadores que conducen hasta la llamada al mtodo actual. Suministra una coleccin ordenada de identidades en la cadena de llamadas actual. Contiene informacin relativa a una identidad incluida en una cadena de llamadas de COM+. Configura una funcin para una aplicacin o un componente. No se puede heredar esta clase. Representa la clase base de todas las clases que utilizan servicios de COM+. Excepcin que se produce cuando se detecta un error en un componente que utiliza servicios. Obtiene acceso a una propiedad compartida. No se puede heredar esta clase. Representa una coleccin de propiedades compartidas. No se puede heredar esta clase.
ResourcePool SecureMethodAttribute
SecurityCallContext
SecurityCallers
Securityldentity
SecurityRoleAttribute
ServicedComponent ServicedComponentException
SharedProperty SharedPropertyGroup
751
Clase SharedPropertyGroupManager
Descripcin Controla el acceso a grupos de propiedades compartidas. No se puede heredar esta clase. Establece el valor de sincronizacin del componente. No se puede heredar esta clase. Especifica el tipo de transaccin que est disponible para el objeto con atributos. Los valores permitidos son miembros de la enumeracin TransactionOption.
SynchronizationAttribute
TransactionAttribute
Si va a escribir clases que se ejecutan en servicios COM+, estar escribiendo lo que se conoce como componentes que usan servicios. Los componentes que usan servicios aprovechan las caractersticas del espacio de nombres System.EnterpriseServices y le permiten usar las caractersticas empresariales de COM+.
La clase ServicedComponent
Cualquier clase diseada para aprovechar los servicios de COM+ debe derivarse directamente de ServicedComponent o de una clase que tenga ServicedComponent en alguna parte de su rbol de herencia. Todos los servicios COM+ que puede usar estn disponibles asignando atributos a las clases que se derivan de la clase ServicedComponent. La clase ServicedComponent no admite ninguna propiedad; sin embargo, admite una serie de mtodos pblicos que pueden ser invocados por clientes de clase. La mayor parte de estos mtodos, incluyendo A c t i v a t e ( ) , Deactivate() y CanBePooled(), describen los mtodos definidos por interfaces COM+, como IObjectControl. Estos mtodos son virtuales y pueden ser reemplazados por clases derivadas para proporcionar funcionalidades especficas. El listado 35.1 muestra una sencilla clase COM+ escrita en C#. Este objeto participa en el agrupamiento de objetos COM+.
Listado 35.1. Componente COM+ agrupable en C#
using System.EnterpriseServices; [ObjectPooling(5, 10)] public class PooledClass : ServicedComponent
752
{ public { } PooledClass()
La clase del listado 35.1 usa un atributo de .NET Framework llamado ObjectPooling para indicar que la clase PooledClass debe poder agruparse en COM+. El atributo ObjectPooling admite varios constructores. El listado 35.1 usa el constructor que acepta dos nmeros enteros que representan el tamao mximo y mnimo de la agrupacin. El cdigo usa los valores 5 y 10, lo que indica a COM+ que debe admitir un mnimo de cinco y un mximo de diez objetos de esta clase en la agrupacin de objetos COM+. Los componentes COM+ escritos en C# que quieran formar parte de una agrupacin de objetos C0M+ deben reemplazar el mtodo virtual CanBePooled() de la clase base ServicedComponent y deben devolver True. Si se devuelve un valor False significa que el componente no debe formar parte de la agrupacin de objetos. Los componentes C0M+ que pueden ser agrupados tambin pueden reemplazar a los mtodos ServicedComponent virtuales llamados Activate() y Deactivate() . El mtodo Activate() es invocado cuando el objeto se elimina de la agrupacin de objetos y se asigna a un cliente, y el mtodo Deactivate() es invocado cuando el objeto es liberado por un cliente y devuelto a la aplicacin. Debe seguir las directrices impuestas por el desarrollo de COM+ estndar y colocar todo el cdigo relevante de destruccin y construccin del estado de los objetos en los mtodos Activate() y Deactivate(). El constructor y destructor de su clase son invocados, pero slo son invocados una vez. El constructor es invocado slo cuando COM+ crea instancias de su objeto para colocarlas en la agrupacin de objetos COM+ y el destructor slo es llamado cuando COM+ destruye el objeto tras eliminarlo de la agrupacin. El mtodo Activate() se diferencia del constructor en que se invoca cada vez que la
753
instancia se asigna a un cliente COM+. El mtodo Deactivate() se diferencia del destructor en que se invoca cada vez que la instancia es liberada de un cliente COM+ y devuelta a la agrupacin de objetos COM+. Si tiene cdigo que debe realizar alguna inicializacin cada vez que se le asigna a un nuevo cliente el uso del objeto, coloque el cdigo en Activate() en lugar de en su constructor de clase. Del mismo modo, si tiene algn cdigo que deba realizar alguna finalizacin cada vez que un nuevo cliente libere el objeto, coloque el cdigo en Deactivate() en lugar de en su destructor de clase.
[assembly:AssemblyKeyFile("keyfile.snk")] [assembly:AssemblyVersion("1.0.*")] [ObjectPooling(5, 10)] public class PooledClass : { public PooledClass() { } ~PooledClass()
ServicedComponent
754
{ } public override bool CanBePooled() { return true; } public override void Activate() { } public override void Deactivate() { } }
Puede exponer esta clase como una clase COM+ con slo unas cuantas herramientas de lnea de comandos. En primer lugar, genere un nuevo par de claves para el nombre seguro del ensamblado con la herramienta de lnea de comando estndar sn:
sn -k keyfile.snk
Tras generar el ensamblado, puede usar la herramienta regsvcs para registrar el ensamblado con COM+:
regsvcs /appname:Listing28-2App Listing35-2.dll
TRUCO: La infraestructura de interoperabilidad .NET/COM+ admite aplicaciones COM+ basadas en la cach de ensamblados global. Si varios clientes usan el cdigo, quizs quiera instalar su ensamblado en la cach de ensamblado global antes de registrarla con COM+. El argumento /appname para la herramienta regsvs especifica el nombre de la aplicacin COM+ creada para almacenar las clases pblicas encontradas en el ensamblado. Si cuando se ejecuta regsvcs ya existe una aplicacin COM+ con su nombre, las clases se agregan a la aplicacin existente. La figura 35.1 muestra el explorador COM+ ejecutndose con el ensamblado generado a partir del cdigo del listado 35.2 registrado con COM+. PooledClass es detectado automticamente por el proceso de registro y agregada a la aplicacin COM+. Inicie el explorador COM+ realizando los siguientes pasos: 1. Haga clic en el botn Inicio del Explorador de Windows. Aparecer el men Inicio.
755
2. Escoja Programas>Herramientas administrativas . Aparecern los iconos de las aplicaciones en el grupo de programas Herramientas administrativas. 3. Seleccione Servicios de componentes. Aparecer el Explorador COM+. La figura 35.2 muestra la hoja de propiedades de la clase PooledClass. Observe que la informacin de agrupacin de objetos especificada en los atributos del listado 35.2 es detectada automticamente por el proceso de registro y agregada a la aplicacin COM+.
756
aparece en el cdigo. Un valor predeterminado configurado hace referencia al valor asignado al atributo, si se asigna, pero omite su valor.
Figura 35.2. Pgina de la propiedad de clase COM+ con informacin de agrupacin de objetos
ApplicationAccessControl
El atributo ApplicationAccessControl especifica si se puede configurar la seguridad de un ensamblado. Este atributo recibe un valor booleano que debe ser T r u e si se permite la configuracin de seguridad, y F a l s e en caso contrario. El valor predeterminado no configurado es False, y el valor predeterminado configurado es True.
ApplicationActivation
El atributo ApplicationActivation es un atributo de nivel de ensamblado que especifica si se debe agregar la clase a una biblioteca o a una aplicacin de servidor COM+. El atributo recibe como parmetro un tipo de enumeracin llamado ActivationOption que admite los siguientes valores: Library, que especifica una aplicacin de biblioteca COM+ Server, que especifica una aplicacin de servidor COM+
757
ApplicationID
El atributo ApplicationID puede usarse para especificar el GUID que se va a asignar a la aplicacin COM+ creada para que contenga la clase COM+. El GUID se especifica usando su representacin de cadena, que se enva al constructor del atributo. El atributo ApplicationID debe aplicarse en el nivel de ensamblado, como en el siguiente fragmento de cdigo:
[assembly:ApplicationID("{E3868E19-486E-9F13-FC8443113731}"}] public class MyClass { }
El atributo recibe una cadena como parmetro que describe el GUID de la aplicacin.
ApplicationName
El atributo ApplicationName se utiliza para especificar el nombre que se va a asignar a la aplicacin COM+ creada para que contenga la clase COM+. Se le debe proporcionar el nombre al constructor del atributo. Si se especifica este atributo en el cdigo, no ser necesario usar el argumento /appname de la herramienta de lnea de comandos regsvcs. El atributo ApplicationName debe aplicarse en el nivel de ensamblado, como en el siguiente fragmento de cdigo:
[assembly:ApplicationName("MyName")] public class MyClass { }
El atributo recibe como parmetro una cadena que describe el nombre de la aplicacin, y su valor predeterminado es el nombre del ensamblado para un valor predeterminado no configurado.
ApplicationQueuing
El atributo ApplicationQueuing se usa para especificar que la clase debe ser configurada como un componente en cola. El atributo no admite parmetros. El atributo ApplicationQueuing debe aplicarse en el nivel de ensamblado, como en el siguiente fragmento de cdigo:
[assembly:ApplicationQueuing] public class MyClass { }
758
AutoComplete
El atributo AutoComplete puede aplicarse a mtodos en una clase COM+. Si la llamada del mtodo se hace en el mbito de una transaccin y la llamada del mtodo se completa con normalidad, las llamadas a mtodos marcados como mtodos AutoComplete van inmediatamente seguidas por una llamada de .NET Framework a SetComplete() . No es necesario realizar una llamada explcita a SetComplete() para los mtodos AutoComplete . Si un mtodo AutoComplete() inicia una excepcin, se invoca a SetAbort() y la transaccin es eliminada. El atributo AutoComplete debe aplicarse en el nivel de mtodo, como en el siguiente fragmento de cdigo:
public class MyClass { [AutoComplete] public MyMethod() { } }
El atributo AutoComplete no acepta parmetros. El valor por defecto para el valor predeterminado no configurado es False, y para el valor predeterminado configurado es True.
ComponentAccessControl
El atributo ComponentAccessControl activa o desactiva las comprobaciones de seguridad en las llamadas a instancias de clase. El atributo recibe como parmetro un valor booleano, que debe ser True si la comprobacin del nivel de seguridad de la llamada debe estar activa, y False en caso contrario. El atributo ComponentAccessControl debe aplicarse en el nivel de clase, como en el siguiente fragmento de cdigo:
[ComponentAccessControl] public class MyClass { }
El atributo ComponentAccessControl no acepta parmetros. El valor por defecto para el valor predeterminado no configurado es False, y para el valor predeterminado configurado es True.
ConstructionEnabled
El atributo ConstructionEnabled permite la construccin de objetos COM+. El mecanismo de construccin de objetos COM+ permite que se pueda
759
pasar una cadena como cadena de constructor a las instancias de objetos con instancias. El atributo no especifica la cadena; en cambio, simplemente permite que se admita la construccin de objetos COM+. El atributo admite un valor booleano como parmetro, que debe ser True si la construccin de objetos COM+ debe estar activada para la clase, y False en caso contrario. Las clases de C# que admiten la construccin de objetos deben implementar la interfaz IObjectConstruct. El mtodo Construct() de la interfaz es invocado por COM+ para pasar la cadena del constructor al objeto. El atributo ConstructionEnabled debe aplicarse en el nivel de clase, como en el siguiente fragmento de cdigo:
[ConstructionEnabled] public class MyClass { }
El atributo ConstructionEnabled no admite parmetros. El valor por defecto del valor predeterminado no configurado es False, y el del valor predeterminado configurado es True.
JustInTimeActivation
El atributo JustInTimeActivation habilita o deshabilita la activacin justo a tiempo (JIT) de una clase. El atributo admite un valor booleano como parmetro, que debe ser True si se va a permitir la activacin JIT en la clase, y False en caso contrario. La actuacin JIT siempre debe estar habilitada en los objetos que toman parte en transacciones. El atributo JustInTimeActivation debe aplicarse en el nivel de clase, como en el siguiente fragmento de cdigo:
[JustInTimeActivation] public class MyClass { }
El atributo JustInTimeActivation no admite parmetros. El valor por defecto para el valor predeterminado no configurado es False, y para el valor predeterminado configurado es True.
LoadBalancingSupported
El atributo LoadBalancingSupported habilita o deshabilita la compatibilidad con el equilibrio de cargas de una clase. El atributo admite un valor booleano como parmetro que debe ser True si la compatibilidad con el equilibrio de cargas va a estar habilitada, y False en caso contrario.
760
El atributo LoadBalancingSupported debe aplicarse en el nivel de clase, como en el siguiente fragmento de cdigo:
[LoadBalancingSupported] public class MyClass { }
El atributo LoadBalancingSupported no admite parmetros. El valor por defecto para el valor predeterminado no configurado es False, y para el valor predeterminado configurado es True.
SecurityRole
El atributo SecurityRole especifica una funcin de seguridad. Este atributo puede aplicarse a una clase, a un mtodo o a un ensamblado completo. El constructor recibe como argumento una cadena que debe especificar el nombre de la funcin a la que deben pertenecer los miembros, como muestra el siguiente fragmento de cdigo:
[assembly:SecurityRole("MySecurityRole")] public class MyClass { }
761
tribuida de otra compaa, el proveedor de alimentos. Si el primer mtodo contiene cdigo que introduce la peticin de billetes en la base de datos de la lnea area, y un segundo mtodo contiene cdigo que intenta introducir datos en la base de datos del proveedor de alimentos, que ocurrir si no se encuentra la base de datos del proveedor de alimentos? La informacin original que contiene los detalles del vuelo se introducir en la base de datos de la compaa area, pero cuando los pasajeros aparezcan, no tendrn comida porque la segunda parte de la transaccin no se pudo realizar. Evidentemente, esto no es una situacin deseable para una aplicacin empresarial. Si el proveedor de alimentos no est disponible, el billete no se introducir en la base de datos de la compaa area. Este problema puede solucionarse fcilmente envolviendo las dos llamadas de mtodo en una transaccin. NOTA: En el ejemplo ExAir, a la compaa no le interesa dejar de reservar un billete slo porque el vnculo a la base de datos de su proveedor de alimentos no funcione. En esta situacin, la solucin podra ser usar algo como los componentes en cola o Microsoft Message Queue. Con los servicios Message Queue, que se incluyen en el espacio de nombres System.Messaging, puede garantizar la peticin eventual de envo de comida envindola a una cola de mensajes en lugar de intentar escribir inmediatamente los datos en la base de datos remota. Con este tipo de arquitectura, la aplicacin siempre puede aceptar las peticiones de billetes y el proveedor slo tendr que extraer mensajes de la cola de mensajes cuando pueda procesar los pedidos de alimentos.
Propiedades ACID
Para que funcionen las transacciones, deben atenerse a las propiedades ACID. ACID es el acrnimo de atomicidad, coherencia, aislamiento y permanencia. La tabla 35.2 describe las definiciones de las propiedades ACID.
Tabla 35.2. Propiedades ACID Propiedad Atomicidad Coherencia Aislamiento Descripcin Todo el trabajo es atmico o sucede en una sola unidad de trabajo. Todo dato que se usa en la transaccin es abandonado en un estado consistente. Cada transaccin est aislada de las otras transacciones, lo que permite que las transacciones pue-
762
Propiedad
Descripcin dan sobrescribir los datos de otros procesos, lo que mantiene la uniformidad.
Permanencia
Cuando se completa la transaccin, todos los datos deben estar en un almacn permanente, como una base de datos. Si la transaccin falla, todos los datos usados en ella deben ser eliminados. La permanencia garantiza que los datos sobrevivan a acontecimientos imprevistos, como cortes de luz o huracanes.
763
El atributo recibe un solo parmetro que nombra a un valor de la enumeracin TransactionOption, y describe el nivel de transaccin que admite el diseo de la clase. El atributo tambin puede usarse sin parmetros, como en el siguiente fragmento de cdigo:
[Transaction] public class MyClass { }
Si se especifica el atributo Transaction sin especificar un parmetro, el nivel de compatibilidad con el parmetro de la clase pasa a ser Required. El listado 35.3 muestra el cdigo completo para el contexto de ExAir. Existen dos mtodos y cada uno accede a recursos diferentes. Como estn contenidos en una transaccin, queda garantizado que el procesamiento del pedido tiene lugar como una sola unidad de trabajo; el proceso est aislado de los otros posibles pedidos; los datos del pedido se dejan en un estado coherente; y una vez que el pedido est consignado, permanecen en un almacn permanente.
Listado 35.3. Ejemplo de base de datos transaccional
namespace { TransactionSupport
using System; using System.Data.SqlClient; using System.EnterpriseServices; [Transaction(TransactionOption.Required)] public class ExAirMain : ServicedComponent { public void Process() { /* Llama a mtodos para agregar informacin de Food y de Ticket */ AddFood process1 = new AddFood(); AddAirline process2 = new AddAirline(); process1.Add(); process2.Add(); } } [Transaction(TransactionOption.Supported)] [AutoComplete] public class AddFood : ServicedComponent { public void Add() {
764
SQLConnection cnn = new SQLConnection("FoodSupplierConnection"); SQLCommand cmd = new SQLCommand(); cnn.Open(); cmd.ActiveConnection = cnn; cmd.CommandText = ""; // Introduce una instruccin en DB cmd.ExecuteNonQuery(); cnn.Close(); } } [Transaction(TransactionOption.Supported)] [AutoComplete] public class AddAirline : ServicedComponent { public void Add() { SQLConnection cnn = new SQLConnection("AirlineConnection"); SQLCommand cmd = new SQLCommand(); cnn.Open(); cmd.ActiveConnection = cnn; cmd.CommandText = ""; // Introduce una instruccin en DB cmd.ExecuteNonQuery(); cnn.Close(); } } }
La clase ContextUtil contiene varias propiedades y mtodos que conceden a los invocadores el acceso a informacin de estado del contexto COM+. Todos los mtodos y propiedades de la clase son estticos, lo que significa que se puede acceder a los miembros directamente desde la clase ContextUtil sin crear un objeto de la clase. La tabla 35.3 describe las propiedades de la clase ContextUtil, y la tabla 35.4 describe los mtodos de la clase ContextUtil. El cdigo del listado 35.4 implementa un componente COM+ transaccional que implementa un mtodo pblico llamado DoWork() . El mtodo DoWork()
765
comprueba la propiedad IsCallerInRole() para determinar la funcin COM+ del invocador. Si la funcin del invocador es ClientRole, entonces la transaccin del objeto se realiza con una llamada a SetComplete(). Si la funcin del invocador es distinta de la de ClientRole, entonces la transaccin del objeto es cancelada con una llamada a SetAbort().
Tabla 35.3. Propiedades de la clase ContextUtil Propiedad ActivityId ApplicationId ApplicationInstanceId ContextId DeactivateOnReturn IsInTransaction IsSecurityEnabled MyTransactionVote PartitionId Transaction TransactionId Descripcin Obtiene un identificador GUID que representa la actividad que contiene el componente Obtiene un identificador GUID para la aplicacin actual Obtiene un identificador GUID para la instancia de aplicacin actual Obtiene un identificador GUID para el contexto actual Obtiene o establece el bit hecho en el contexto de COM+ Obtiene un valor que indica si el contexto actual es transaccional Obtiene un valor que indica si la seguridad basada en funciones est activa en el contexto actual Obtiene o establece el bit consistente en el contexto de COM+ Obtiene un identificador GUID para la particin actual Obtiene un objeto que describe la transaccin de DTC actual de COM+ Obtiene el identificador GUID de la transaccin de DTC actual de COM+ Tabla 35.4. Propiedades de la clase ContextUtil Propiedad DisableCommit EnableCommit Descripcin Asigna a los bits consistente y hecho el valor False en el contexto de COM+ Asigna al bit consistente el valor True, y al bit hecho el valor False, en el contexto de COM+
766
Descripcin Devuelve una propiedad con nombre desde el contexto COM+ Determina si el llamador se incluye en la funcin especificada Asigna al bit consistente el valor False, y al bit hecho el valor True , en el contexto de COM+ Asigna a los bits consistente y hecho el valor True en el contexto de COM+
Listado 35.4. Cmo acceder al contexto COM+ mediante la clase ContextUtil using System.Reflection; using System.EnterpriseServices; [assembly:AssemblyKeyFile("keyfile.snk")] [assembly:AssemblyVersion("1.0.*")] [ObjectPooling(5, 10)] [Transaction(TransactionOption.Required)] [SecurityRole("ClientRole")] public class { public { } ~PooledClass() { } public override bool CanBePooled() { return true; } public override void Activate() { } public override void Deactivate() { } public void DoWork() { bool IsInRole; PooledClass : ServicedComponent
PooledClass()
767
Resumen
La exposicin de una clase C# como si fuera una aplicacin COM+ no supone ningn esfuerzo y resulta ms sencillo que implementar la misma funcionalidad usando versiones anteriores de Visual Studio 6.0. Las aplicaciones de COM+ escritas en Visual C++ 6.0 necesitaban mucho ms cdigo para realizar las mismas tareas, y algunas caractersticas de COM+ (como la agrupacin de objetos) ni siquiera estaban disponibles en Visual Basic 6.0. El desarrollo de componentes COM+ mediante C# implica cuatro sencillos conceptos: Derivar la clase de ServicedComponent Agregar atributos para describir las configuraciones de la aplicacin Usar la herramienta regsvcs para construir una aplicacin COM+ para las clases pblicas
Invocar a mtodos y propiedades en la clase ContextUtil para acceder al contexto de COM+ en tiempo de ejecucin. Microsoft no ofreci pistas sobre el modelo de programacin COM+ hasta 1997. Entonces, describieron un modelo basado en programacin con atributos, en el que los componentes COM deban ser descritos con atributos y en tiempo de ejecucin se trataran detalles como los generadores de clases y el clculo de referencias al estilo IUnknown. Ahora es evidente que el modelo .NET de desarrollo de componentes COM+ es la culminacin de esa visin original.
768
771
diferentes. Antes de la llegada de .NET Framework, se podan pasar objetos a travs de lmites de procesos mediante COM o DCOM. DCOM funcionaba bien, pero tena limitaciones, como los tipos de datos que podan pasarse y el contexto de seguridad pasado entre el llamador cliente y la activacin del servidor. Adems, estaba basado en COM, lo que significaba que aunque poda comunicarse a travs de lmites de equipo, todos los equipos deban estar ejecutando un sistema operativo de Microsoft. Esto no era un gran problema, pero limitaba nuestras opciones respecto a lo que podamos hacer con la infraestructura existente. En .NET, el entorno remoto se ocupa de estos aspectos y mejora lo que DCOM ofreca como un mtodo viable para establecer una comunicacin remota entre objetos. El entorno remoto permite implementar un servidor o una aplicacin de servidor y una aplicacin cliente. En el servidor o el cliente, la aplicacin puede ser cualquiera de las plantillas de aplicacin .NET disponibles, incluyendo aplicaciones de consola, aplicaciones de servicios Windows, aplicaciones ASP.NET, aplicaciones WindowsForms y aplicaciones IIS. En el servidor, se configura mediante programacin (o se emplea un archivo de configuracin para especificarlo) el tipo de activacin que permitirn los clientes. Los clientes pueden usar varios tipos de mtodos de activacin, incluyendo Singleton y SingleCall, como se explicar en la seccin "Cmo activar el objeto remoto" ms tarde en este mismo captulo. Es en este punto donde se especifica el canal y el puerto a travs de los cules se comunica el objeto, y el formato que tendrn los datos cuando se pasen entre el servidor y el cliente. Despus aprender a implementar canales y puertos. El formato de los datos es importante, segn el diseo del sistema: puede usar datos binarios, SOAP o un formato personalizado, para recodificar los datos. Tras especificar el canal, el puerto y el formato, segn el tipo de servidor remoto que est exponiendo, debe determinar cmo exponer los metadatos a los clientes. Puede hacerlo de varias maneras, como permitiendo al invocador descargar el ensamblado o haciendo que la fuente est disponible para el invocador. En cualquiera de los dos casos, el cliente debe saber qu objeto est creando, de modo que los metadatos deben estar disponibles en varias formas para el invocador. Cuando el servidor est configurado y creado adecuadamente, puede escribir el cliente. En el cliente todo lo que debe hacer es crear una instancia del objeto en el canal y puerto especificados que esperen peticiones del servidor. Puede lograr esto mediante programacin o mediante un archivo de configuracin. En este punto, las llamadas de mtodo no son diferentes de cualquier otro objeto de una aplicacin .NET que se pueda usar. Tras crear objetos, llame a los mtodos, establezca y recupere propiedades y desencadene eventos igual que hara con un objeto que no est usando el entorno remoto. Estos pueden parecer muchos pasos, pero en realidad es muy sencillo una vez que lo ha hecho una vez. Puede resumir todo el proceso en las siguientes tareas, esquematizadas en la figura 36.1.
772
1. Especifique los canales y puertos que recodifican los objetos entre el servidor y el cliente. 2. Use formateadores (explicados ms adelante en este captulo) para especificar el formato en el que los datos son serializados y deserializados entre el servidor y el cliente. 3. Determine cmo se activan los objetos del servidor y cunto dura la actuacin.
Dominio de aplicacin de cliente Objeto Formateador Servidor Proxy Canal
Dominio de aplicacin de cliente Objeto Canal Formateador Figura 36.1. Vista del entorno remoto .NET
En las siguientes secciones aprender a crear la aplicacin anfitriona en un contexto remoto, incluyendo los detalles especficos de los formateadores, canales y puertos, y cmo se puede activar el servidor. Despus de construir el servidor, aprender a consumir el objeto remoto de una aplicacin cliente.
773
mi unidad C llamado cSharpRemoting y le agregu tres subcarpetas llamadas Servidor, ServidorObject y Client. Ya se imaginara lo que se pretende. La aplicacin de biblioteca de clases ServidorObject debe ser creada en el directorio cSharpRemoting\ServidorObject. Esto hace que le sea ms sencillo ejecutar las aplicaciones de consola que crear posteriormente. 2. Tras crear la aplicacin de biblioteca de clases ServidorObject, agregue un mtodo pblico que reciba un parmetro, llamado customerID, y devuelva el nombre del cliente de la base de datos Northwind en el SQL Server basado en el customerID que se ha pasado. La clase completa para la aplicacin ServidorObject debera tener un aspecto parecido al listado 36.1.
Listado 36.1. Cmo crear la aplicacin ServidorObject
using using using System; System.Data; System.Data.SqlClient;
namespace ServidorOb]ect { public class Class1: MarshalByRefObject { public string thisCustomer; public Class1() { Console.WriteLine("ServidorObject }
has
been
activated");
public string ReturnName(string customerID) { // Crea una conexin, enva el objeto al SQL string cnStr = "Initial Catalog=Northwind; Data" + " Source=localservidor; Integrated Security=SSPI;"; SqlConnection cn = new SqlConnection(cnStr); string strSQL = ("Select CompanyName from C u s t o m e r s " + " where CustomerID = '" + customerID + " ' " ) ; cn.CreateCommand(); = strSQL;
cmd.ExecuteReader
774
(CommandBehavior.CloseConnection); while (rdr.Read()) { thisCustomer = rdr.GetString(); } Console.WriteLine(thisCustomer + " was returned to the client"); return } } thisCustomer;
El cdigo anterior realiza una simple peticin a SQL Server para tomar el campo CompanyName de la base de datos Customers segn el parmetro customerID que se pasa al mtodo. Como puede ver, este cdigo no es diferente de cualquier otra biblioteca de clases que haya creado en C#. El siguiente paso es crear la aplicacin servidor que atiende a las peticiones de esta clase de biblioteca por parte del cliente.
ActivatedServiceTypeEntry
775
Clase ObjectHandle
Descripcin Ajusta referencias de objetos calculadas por valor, de este modo, se pueden devolver a travs de un direccionamiento indirecto Almacena toda la informacin relevante necesaria para generar un proxy y establecer comunicacin con un objeto remoto Proporciona varios mtodos estticos para configurar la infraestructura remota Excepcin que se inicia cuando se produce algn tipo de error durante la interaccin remota Proporciona varios mtodos para utilizar y publicar servidores proxy y objetos remotos. No se puede heredar esta clase Excepcin que se inicia cuando no se puede obtener acceso al servidor o al cliente en el perodo de tiempo previamente especificado Excepcin que se inicia para comunicar errores al cliente cuando ste se conecta a aplicaciones distintas de .NET Framework que no pueden iniciar excepciones Proporciona varios mtodos para utilizar y publicar objetos remotos en formato SOAP Implementa una clase base que contiene la informacin de configuracin utilizada para activar una instancia de un tipo remoto Contiene valores de un tipo de objeto registrado en el cliente como objeto de tipo conocido (llamada nica o singleton) Contiene valores de un tipo de objeto registrado en el servicio como objeto de tipo conocido (llamada nica o singleton)
ObjRef
RemotingConfiguration RemotingException
RemotingServices
RemotingTimeoutException
ServerException
SoapServices TypeEntry
WelIKnownClientTypeEntry
WelIKnownServiceTypeEntry
Aunque no use todas estas clases cuando escriba aplicaciones remotas, varias de estas clases son extremadamente importantes para implementar una infraestructura remota, como son la clase ObjRef, la clase RemotingConfiguration, la clase RemotingServices y la enumeracin WellKnownObjectMode. Aprender ms de cada una de ellas ms tarde en esta misma seccin mientras escribe el cdigo de su aplicacin anfitriona.
776
Para empezar a escribir la aplicacin anfitriona, debe comprender lo que la infraestructura remota necesita para funcionar. Para recordar los pasos necesarios para crear la aplicacin anfitriona, revise los siguientes pasos reseados con anterioridad: 1. Especifique los canales y puertos que recodifican los objetos entre el servidor y el cliente. 2. Use formateadores para especificar el formato en el que los datos son serializados y deserializados entre el servidor y el cliente. 3. Determine cmo se activan los objetos del servidor y cunto dura la activacin. Las siguientes secciones estudian cada uno de estos pasos.
777
Descripcin Crea una cadena de receptores de canal para el canal especificado Enva las llamadas remotas de entrada Devuelve un canal registrado con el nombre especificado Devuelve una IDictionary de propiedades para un proxy determinado Devuelve una matriz de todas las direcciones URL que pueden utilizarse para alcanzar el objeto especificado Registra un canal con los servicios de canal De forma asincrnica, enva el mensaje de entrada a las cadenas del servidor, en funcin de la direccin URI incrustada en el mensaje Anula el registro de un canal determinado de la lista de canales registrados
RegisterChannel SyncDispatchMessage
UnregisterChannel
En esta tabla no aparece una propiedad de la clase ChannelServices llamada RegisteredChannels, que obtiene o asigna los canales registrados de la instancia del objeto actual. El siguiente fragmento de cdigo crea y registra un canal TCP y otro HTTP en los puertos especficos usando el mtodo RegisterChannel de la clase ChannelServices :
TcpChannel chan1 = new TcpChannel(8085); ChannelServices.RegisterChannel(chan1); HttpChannel chan2 = new HttpChannel(8086); ChannelServices.RegisterChannel(chan2);
Cuando crea un canal, tambin especifica un tipo de formateador para el canal. Las siguientes secciones describen los tipos de formateadores disponibles.
778
TcpCIientChannel
TcpServerChannel
779
Tabla 36.4. Espacio de nombres System.Runtime.Remoting.Channels.Http Clase HttpChannel Descripcin Proporciona una implementacin de un canal emisor-receptor que utiliza el protocolo HTTP para transmitir mensajes. Esta clase es una combinacin de las clases HttpClientChannel y HttpServerChannel, lo que permite la comunicacin en ambos sentidos a travs de HTTP. Proporciona una implementacin de un canal de cliente que utiliza el protocolo HTTP para transmitir mensajes. Implementa un controlador ASP.NET que enva solicitudes al canal HTTP remoto. Inicializa nuevas instancias de la clase HttpRemotingHandler. Proporciona una implementacin de un canal de servidor que utiliza el protocolo HTTP para transmitir mensajes.
HttpCIientChannel
namespace Client { /// <summary> /// Descripcin resumida de Class1. /// </summary> class RemotingClient { [STAThread] static void Main(string[] args) { TcpChannel chan1 = new TcpChannel(8085); ChannelServices.RegisterChannel(chan1); HttpChannel chan2 = new HttpChannel(8086); ChannelServices.RegisterChannel(chan2);
780
} } }
NOTA: No necesita usar los canales HTTP y TCP en la aplicacin anfitriona. Si est permitiendo a los clientes que llamen en los dos tipos de canales, puede registrar los dos tipos de canales; sin embargo, por lo general usar un formateador, TCP o HTTP, basado en el tipo de los clientes que estn accediendo al objeto remoto.
Tras agregar una referencia al ensamblado remoto creado anteriormente, puede agregar el cdigo que registra el objeto mediante el entorno remoto. Hay dos maneras de hacerlo: Use el mtodo RegisterWellKnownServiceType() de la clase RemotingConfiguration para pasar el tipo de objeto que est creando, el URI del objeto y el modo de activacin del objeto. Use el mtodo Configure() de la clase RemotingConfiguration para pasar un archivo de configuracin con los detalles de activacin del objeto.
Cada mtodo de activacin funciona igual, pero almacenar los detalles de activacin en un archivo de configuracin otorga ms flexibilidad si cualquiera de los detalles de activacin cambia, por ejemplo, el nmero de puerto del canal que est usando. Ya sopesaremos las ventajas y desventajas de ambos tipos de activa-
781
cin, pero antes examine los mtodos y propiedades de la clase RemotingConfiguration disponibles, descritos en la tabla 36.5 y en la tabla 36.6 respectivamente. Adems de los mtodos de activacin descritos anteriormente, puede usar muchos mtodos y propiedades muy prcticos de esta clase para descubrir informacin en tiempo de ejecucin sobre los objetos que est ejecutando.
Tabla 36.5. Mtodos de la clase RemotingConfiguration Mtodo Configure Descripcin Lee el archivo de configuracin y configura la infraestructura remota. Recupera una matriz de tipos de objetos registrados en el cliente como tipos que se activarn de forma remota. Recupera una matriz de tipos de objetos registrados en el servicio que se pueden activar cuando lo solicita un cliente. Recupera una matriz de tipos de objetos registrados en el cliente como tipos conocidos. Recupera una matriz de tipos de objetos registrados en el servicio como tipos conocidos. Obtiene el tipo de la instancia actual. Devuelve un valor booleano que indica si el tipo especificado est autorizado para ser cliente activado. Sobrecargado. Comprueba si el tipo de objeto especificado se registra como tipo de cliente activado de forma remota. Sobrecargado. Comprueba si el tipo de objeto especificado est registrado como tipo de cliente conocido. Sobrecargado. Registra un tipo de objeto en el cliente como un tipo
GetRegisteredActivatedCIientTypes
GetRegisteredActivatedServiceTypes
GetRegisteredWellKnownCIientTypes
GetRegisteredWellKnownServiceTypes
IsRemotelyActivatedClientType
IsWellKnownClientType
RegisterActivatedClientType
782
Mtodo
RegisterActivatedServiceType
Sobrecargado. Registra un tipo de objeto en el servicio como un tipo que se puede activar a peticin del cliente. Sobrecargado. Registra un tipo de objeto registrado en el cliente como objeto de tipo conocido (llamada nica o singleton). Sobrecargado. Registra un tipo de objeto en el servicio como objeto de tipo conocido (llamada nica o singleton).
RegisterWellKnownClientType
RegisterWellKnownServiceType
Tabla 36.6. Propiedades de la clase RemotingConfiguration Propiedad ApplicationId ApplicationName Processld Descripcin Obtiene el ID de la aplicacin que se ejecuta actualmente Obtiene o establece el nombre de una aplicacin remota Obtiene el ID del proceso que se ejecuta actualmente
783
using System.Runtime.Remoting.Channels.Http; using System.Runtime.Remoting.Channels.Tcp; using ServidorObject; namespace Servidor { /// <summary> /// Descripcin resumida de Class1. /// </summary> class { /// <summary> /// El punto de entrada principal a la aplicacin. /// </summary> [STAThread] static void Main(string[] { TcpChannel chan1 = new TcpChannel(8085); ChannelServices.RegisterChannel(chan1); RemotingConfiguration.RegisterWellKnownServiceType( typeof(ServidorObject.Class1), "ReturnName", WellKnownObjectMode.SingleCall); Console.WriteLine("Press Console.ReadLine(); } } } any key to exit"); args) Class1
Como la aplicacin anfitriona es una aplicacin de consola, agregue la instruccin Console.ReadLine al final para que la ventana de la consola permanezca abierta mientras los objetos estn usando el objeto remoto. La duracin del canal es la cantidad de tiempo que la ventana permanece abierta. Tras cerrar la ventana de consola, el canal se destruye y termina la concesin de ese canal en particular en el entorno remoto. La enumeracin WellKnownObjectMode contiene dos miembros que definen cmo se crean los objetos. Si WellKnownObjectMode es SingleCall, cada peticin de un cliente es atendida por una nueva instancia de objeto. Esto puede representarse mediante el siguiente pseudo-cdigo:
Crear objeto X Llamar al mtodo del objeto X Devolver datos al llamador
784
Si WellKnownObjectMode es Singleton, cada peticin de un cliente es atendida por la misma instancia de objeto. Esto puede representarse mediante el siguiente pseudo-cdigo:
Crear objeto X Llamar al mtodo del objeto X Devolver datos al llamador Llamar al mtodo del objeto X Devolver datos al llamador . . . contina hasta que el canal
es
destruido
Este bucle contina hasta que el canal con el que se registra este objeto en el entorno remoto es destruido. Dependiendo del tipo de aplicacin que est escribiendo, se determina el modo de activacin que debe usar, segn los siguientes factores: Coste: Si crear el objeto remoto consume recursos y tiempo, usar el modo SingleCall puede no ser el modo ms efectivo de crear su objeto, ya que el objeto es destruido despus de que cada cliente lo use. Informacin de estado: Si est almacenando informacin de estado, como propiedades, en el objeto remoto, use objetos Singleton, que pueden mantener datos de estado.
785
<formatter> (Instance) <clientProviders> (Instance) <provider> (Instance) <formatter> (Instance) <client> <wellknown> (Client Instance) <activated> (Client Instance) <service> <wellknown> (Service Instance) <activated> (Service Instance) <soapInterop> <interopXmlType> <interopXmlElement> <preLoad> <channels> (Template) <channel> (Template) <serverProviders> (Instance) <provider> (Instance) <formatter> (Instance) <clientProviders> (Instance) <provider> (Instance) <formatter> (Instance) <channelSinkProviders> <serverProviders> (Template) <provider> (Template) <formatter> (Template) <clientProviders> (Template) <provider> (Template) <formatter> (Template) <debug>
Aunque hay muchas opciones en el archivo de configuracin, slo necesita usar las necesarias para su aplicacin. Por ejemplo, el fragmento de cdigo del listado 36.5 representa un archivo de configuracin para un objeto activado con HTTP que es un objeto de modo SingleCall .
Listado 36.5. Ejemplo de archivo de configuracin
<configuration> <system.runtime.remoting> <application> <client url="http://localservidor/ServidorObject"> <wellknown type="ServidorObject.Class1, ReturnName" url="http://localservidor/ServidorObject/ Class1.soap"/> </client> <channels>
786
Tras crear el archivo de configuracin, la operacin de crear el objeto anfitrin es mucho ms simple que registrar un objeto con el mtodo RegisterWellKnownServiceType() de la clase RemotingConfiguration. El cdigo del listado 36.6 muestra cmo registrar el objeto mediante el mtodo Configure() .
Listado 36.6. Cmo usar un archivo de configuracin remota en la clase anfitriona namespace Servidor { /// <summary> /// Descripcin resumida de Class1. /// </summary> class Class1 { /// <summary> /// El punto de entrada principal a la aplicacin. /// </summary> [STAThread] static void Main(string[] args) { RemotingConfiguration.Configure("Servidor.Exe.Config"); Console.WriteLine("Press Console.ReadLine(); } } } any key to exit");
Como puede ver, el cdigo se ha visto reducido de quince lneas a una. NOTA: El nombre del archivo de configuracin debe ser el nombre del ejecutable, incluida la extensin exe, y seguido de la extensin config adicional. En el caso de la aplicacin anfitriona, el archivo de configuracin recibir el nombre servidor.exe.config y estar en el directorio Bin donde est el archivo ejecutable de la aplicacin anfitriona. La tabla 36.7 recoge todos los elementos disponibles y sus usos para el esquema de archivos de configuracin remota.
787
Tabla 36.7. Esquema para el archivo de configuracin de valores remotos Elemento <system.runtime.remoting> <application> Descripcin Contiene informacin sobre objetos y canales remotos Contiene informacin sobre los objetos remotos que la aplicacin consume y expone Contiene informacin sobre el perodo de duracin de todos los objetos activados en el cliente que atiende esta aplicacin Contiene los canales que la aplicacin utiliza para comunicar con objetos remotos Configura el canal que la aplicacin utiliza para comunicar con objetos remotos Contiene los proveedores de receptores de canal que van a formar parte de la cadena de llamadas de receptores de canal predeterminada del servidor correspondiente a esta plantilla de canal cuando se hace referencia a la plantilla en otro lugar del archivo de configuracin Contiene el proveedor de receptores de canal correspondiente a un receptor de canal que se ha de insertar en la cadena de receptores de canal Contiene el proveedor de receptores de canal para un receptor de formateador que se ha de insertar en la cadena de receptores de canal Contiene los proveedores de receptores de canal que van a formar parte de la cadena de llamadas de receptores de canal predeterminada del cliente correspondiente a esta plantilla de canal cuando se hace referencia a la plantilla en otro lugar del archivo de configuracin
<lifetime>
<channels> (Instancia)
<channel> (Instancia)
<serverProviders> (Instancia)
<provider> (Instancia)
<formatter> (Instancia)
<clientProviders> (Instancia)
788
Descripcin Contiene los objetos que la aplicacin consume Contiene informacin sobre los objetos (conocidos) activados en el servidor y que la aplicacin desea consumir Contiene los objetos activados en el cliente que consume una aplicacin de cliente Contiene los objetos que la aplicacin expone a otros dominios de aplicacin o contextos Contiene informacin sobre los objetos (conocidos) activados en el servidor y que la aplicacin desea publicar Contiene informacin sobre los objetos activados en el cliente y que la aplicacin expone a los clientes Contiene las asignaciones de tipos utilizadas con SOAP Crea una asignacin bidireccional entre un tipo de Common Language Runtime y un tipo XML y espacio de nombres XML Crea una asignacin bidireccional entre un tipo de Common Language Runtime y un elemento XML y espacio de nombres XML Especifica el tipo para cargar las asignaciones de las clases que extienden SoapAttribute Contiene las plantillas de canal que la aplicacin utiliza para comunicar con objetos remotos Contiene la plantilla de canal que la aplicacin puede especificar y configurar para comunicar o escuchar las solicitudes de objetos remotos Contiene plantillas para proveedores de receptores de canal de cliente y servi-
<service>
<soaplnterop> <interopXmlType>
<interopXmlElement>
<preLoad>
<channels> (Plantilla)
<channel> (Plantilla)
<channelSinkProviders>
789
Elemento
Descripcin dor. Se puede hacer referencia a todos los proveedores de receptores de canal especificados debajo de este elemento en cualquier lugar donde est registrado un proveedor de receptores de canal
<serverProviders> (Plantilla)
Contiene plantillas de receptores de canal que se pueden insertar en una cadena de llamadas de canales de servidor Contiene la plantilla de proveedores de receptores de canal correspondiente a un receptor de canal que se ha de insertar en la cadena de receptores de canal del servidor o cliente Contiene el proveedor de receptores de canal para un receptor de formateador que se ha de insertar en la cadena de receptores de canal del cliente o servidor Contiene plantillas de receptores de canal que se pueden insertar en una cadena de llamadas de canales de cliente Especifica si se van a cargar tipos en el archivo de configuracin cuando se inicia la aplicacin
<provider> (Plantilla)
<formatter> (Plantilla)
<clientProviders> (Plantilla)
<debug>
Hasta ahora, este captulo ha explicado los entresijos de la creacin de la aplicacin anfitriona que registra el objeto remoto con el entorno remoto. Ahora debe escribir la aplicacin cliente que realiza las peticiones al objeto remoto, que es el tema de la siguiente seccin.
790
Object y pasa un parmetro customerID que usa el mtodo ReturnName() para buscar el nombre de la compaa del cliente en la base de datos Northwind. Para empezar, cree una nueva aplicacin de consola llamada Client en el directorio C:\cSharpRemoting\Client. Puede llamar al objeto remoto con cualquier tipo de aplicacin, pero para hacerlo ms sencillo, crearemos una aplicacin de consola. Se puede llamar al objeto remoto desde el cliente de una de estas tres formas: Llamando al mtodo GetObject() de la clase Activator, con lo que es activado en el servidor. Llamando al mtodo CreateInstance() de la clase Activator, con lo que es activado en el cliente. Usando la palabra clave new, con lo que puede ser activado en el servidor o en el cliente.
La diferencia entre activacin en el cliente y en el servidor se produce cuando se crea realmente el objeto. Cada tipo de activacin puede conseguirse mediante programacin o mediante un archivo de configuracin (usando el mismo formato descrito en la tabla 36.7), pero para la activacin en cliente, se hace un viaje de ida y vuelta al servidor para crear el objeto cuando se invoca al mtodo CreateInstance() . Por el contrario, cuando un objeto es activado en el servidor, el objeto servidor no se crea hasta que se hace una llamada al mtodo desde el cliente. Los objetos activados en el servidor crean un proxy que el cliente puede usar para descubrir las propiedades y mtodos disponibles en el objeto servidor. La principal desventaja de la activacin en el servidor es que slo se permiten constructores predeterminados, de modo que si necesita pasar varios parmetros a un constructor de mtodo, deber usar una activacin en el lado del cliente mediante el mtodo CreateInstance() de la clase Activator. Todos los mtodos de la clase Activator aparecen en la tabla 36.8.
Tabla 36.8. Mtodos de la clase Activator Mtodo CreateComlnstanceFrom Descripcin Crea una instancia del objeto COM cuyo nombre se especifica, utilizando el archivo de ensamblado con nombre y el constructor que mejor coincida con los parmetros especificados Sobrecargado. Crea una instancia del tipo especificado utilizando el constructor que mejor coincida con los parmetros especificados Sobrecargado. Crea una instancia del tipo cuyo nombre se especifica, utilizando el archivo de
Createlnstance
CreateInstanceFrom
791
Mtodo
Descripcin ensamblado con nombre y el constructor que mejor coincida con los parmetros especificados
GetObject
Sobrecargado. Crea un proxy para un objeto remoto en ejecucin, un objeto conocido activado en el servidor o un servicio Web XML
Tras decidir el tipo de aplicacin necesaria para la aplicacin, puede escribir el cdigo cliente. El listado 36.7 muestra el cdigo completo de la aplicacin cliente. Como suceda en el cdigo anfitrin, debe registrar un canal en primer lugar. Cuando registra un canal desde el cliente, no especifica el nmero de canal. La llamada al final del URI indica al cliente la direccin en la que se encuentra el canal correcto, porque est incluido en la llamada de mtodo GetObject(): especifique el objeto que est intentando crear y la localizacin del objeto. Tras crear la clase, puede llamar a mtodos y establecer propiedades del mismo modo que en cualquier otra clase
Listado 36.7. Aplicacin de cliente remoto
using using using using using System; System.Runtime.Remoting; System.Runtime.Remoting.Channels; System.Runtime.Remoting.Channels.Tcp; ServidorObject;
namespace Client { /// <summary> /// Descripcin resumida de Class1. /// </summary> class RemotingClient { [STAThread] static void Main(string[] args) { ChannelServices.RegisterChannel(new TcpChannel());
792
} } }
Tras escribir el cliente, puede ejecutar la aplicacin Servidor.exe y, a continuacin, ejecutar la aplicacin Cliente.exe. Debera obtener unos resultados similares a los de la figura 36.2. La aplicacin anfitriona permanece siempre abierta, o hasta que la cierre, y cada llamada del cliente a la aplicacin anfitriona devuelve el nombre de la compaa para el customerID ALFKI, que es la identificacin de comprador pasada a la aplicacin cliente.
C:\cSharpRemoting\Client\Client\bin\Debug\Client.exe Alfreds Futterkiste
C:\cSharpRemoting\Host\Host\bin\Debug\Host.exe Press any key to exit HostObject has been activated Alfreds Futterkiste
Si deja el cdigo de la aplicacin anfitriona original en modo SingleCall, cada vez que ejecute la aplicacin cliente se destruir el objeto servidor y se volver a crear. Al cambiar WellKnownObjectMode a Singleton, observar la diferencia entre los modos SingleCall y Singleton. El siguiente fragmento de cdigo muestra la aplicacin anfitriona que crea el objeto en modo Singleton:
RemotingConfiguration.RegisterWellKnownServiceType( typeof(ServidorObject.Class1), "ReturnName", WellKnownObjectMode.SingleCall);
La figura 36.3 muestra la diferencia entre los resultados de la aplicacin anfitriona tras ejecutar la aplicacin cliente varias veces. Como puede ver, el modo Singleton no destruye el objeto cuando el mtodo sale del contexto, mientras que el modo SingleCall necesita volver a crear el objeto cada vez que se llama al mtodo ReturnName() .
793
C\cSharpRemoting\Host\Host\bin\Debug\Host.exe Press any key to exit - this is SingleCall HostObject has been activated HostObject has been activated Alfreds Futterkiste HostObject has been activated Alfreds Futterkiste HostObject has been activated Alfreds Futterkiste
C:\cSharpRemoting\Host\Host\bin\Debug\Host.exe Press any key to exit - this is Singleton HostObject has been activated Alfreds Futterkiste Alfreds Futterkiste Alfreds Futterkiste
Resumen
Este captulo analiza detalladamente el entorno remoto de .NET. Al usar el entorno remoto, puede activar objetos entre lmites de procesos, dominios de aplicacin y lmites de equipos. Si va a implementar un entorno remoto, hay algunos temas ms avanzados en el SDK que quizs le convenga leer antes de empezar: Crear formateadores de usuario: Puede crear formateadores de usuario si los formateadores TCP y HTTP no satisfacen sus necesidades de uso de datos. Busque Receptores y Cadenas de receptores en el Framework SDK. Acceso remoto asncrono: El acceso remoto es otra tecnologa .NET con capacidades asncronas integradas. Busque RPC asncrono en el Framework SDK para aprender a usar delegados y eventos con procedimientos remotos.
Hay muchas buenas razones para estudiar los accesos remotos, pero antes de empezar asegrese de estudiar las capacidades de los servicios Web XML y ASP.NET para conseguir comunicacin entre procesos. Puede ahorrarse mucho tiempo y esfuerzo creando las aplicaciones anfitrionas y modificando el modo en que sus clientes instancian objetos.
794
37 c# y seguridad .NET
Una de las cosas ms importantes que debe recordar cuando se traslade a C# y .NET Framework es la seguridad. Debe asegurarse de que cuando cree aplicaciones con n-niveles, la seguridad sea de la mxima prioridad, porque las probabilidades de que se produzca una brecha en una aplicacin distribuida son mucho mayores que en una aplicacin independiente. Es por esto que .NET Framework se cre pensando en la seguridad, lo que se refleja en cada aspecto del entorno. .NET Framework es capaz de ejecutarse de manera remota, realizar descargas dinmicas de nuevos componentes e incluso ejecutarse de forma dinmica. Con este tipo de entorno, si un programador debe crear el modelo de seguridad probablemente tarde ms en codificarlo que en crear el propio programa. Cuando crea aplicaciones, el modelo de seguridad suele basarse en el nivel de usuario o en el nivel de grupo. La aplicacin realizar ciertas acciones o no. .NET Framework proporciona a los programadores medios para la seguridad basada en funciones, que trabaja de una manera muy parecida a la seguridad de nivel de usuario y de nivel de grupo. La seguridad basada en funciones se puede resumir en principios e identidades, aunque tambin proporciona seguridad de nivel de cdigo, al que se hace referencia generalmente como seguridad de acceso a cdigo o seguridad basada en pruebas. Cuando un usuario inicia una aplicacin que usa seguridad de acceso a cdigo, puede tener acceso a un recurso (por ejemplo, una unidad de red), pero si el
797
cdigo contenido en la aplicacin no es fiable, el programa no puede acceder a la unidad de red. Este tipo de seguridad se basa en cdigo mvil. Quizs no quiera usar una aplicacin mvil y dejar a esa aplicacin que acceda a todos los recursos a los que se ha encomendado. La seguridad basada en funciones evita que los programadores malintencionados escriban aplicaciones que puedan ejecutarse como si las estuviramos ejecutando nosotros y realicen todo tipo de acciones en nuestro equipo local o a travs de nuestra red corporativa. La seguridad de .NET Framework se coloca sobre la seguridad ya presente en su sistema operativo (OS). Este segundo nivel de seguridad es mucho ms extensible que la seguridad OS. Ambos tipos de seguridad, OS y .NET Framework, pueden complementarse entre s. Este captulo le acerca a varios temas relacionados con la seguridad, como el uso de funciones de Windows para determinar permisos. Aprender a solicitar y denegar permisos dentro del cdigo mientras realiza operaciones de registro. Por ltimo, aprender a usar permisos basados en atributos para definir los derechos de su cdigo en tiempo de ejecucin.
Seguridad de cdigo
La seguridad de acceso a cdigo determina si se permite a un ensamblado ejecutarse basndose en varias unidades de prueba, como la URL de la que procede el ensamblado y quin autoriza el control. Al instalar .NET Framework, estn configurados los permisos predeterminados, lo que reduce enormemente las posibilidades de que un control que no es de confianza procedente de Internet o de una intranet local pueda ejecutarse en su equipo. Puede haber visto esto si ha intentado ejecutar algunas aplicaciones o usar algunos controles desde una unidad de red que exija privilegios de seguridad especiales. Estos privilegios de seguridad especiales incluyen la escritura en un archivo de disco, leer o escribir en y desde el registro, adems de operaciones de red. Normalmente recibir una excepcin de seguridad como la siguiente cuando intente hacer estas acciones si no cambia la directiva de seguridad para que permita este tipo de comportamiento:
Unhandled Exception: System.Security.SecurityException: Request for the permission of type System.Security.Permissions.FileIOPermission ... The state of the failed permission was: <IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, Vers ion=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Read="Z:\test.dat" Write="Z:\test.dat"/>
798
La seguridad de acceso a cdigo solamente funciona en cdigo verificable. Durante la compilacin justo a tiempo (JIT), se examina el lenguaje intermedio de Microsoft (MSIL) para garantizar la seguridad de tipos. El cdigo de seguridad de tipos slo tiene acceso a las posiciones de memoria para las que tiene derechos. Acciones como las operaciones de punteros estn prohibidas, de modo que slo se puede entrar y salir de las funciones desde los puntos de entrada y salida predefinidos. Esto no es un mtodo infalible; pueden producirse errores. Sin embargo, impide que una unidad de cdigo malintencionado pueda forzar un error en su aplicacin y aprovechar algn error en el sistema operativo, consiguiendo as acceder a la pila. En estas circunstancias, cuando una unidad de cdigo malicioso ha forzado un error, el cdigo que gener el error slo puede acceder a las posiciones de memoria que el JIT determin que eran accesibles para l.
Permisos de cdigo
El CLR, cuando concede permisos de seguridad, slo concede permisos al cdigo en las operaciones que se le permite realizar. El CLR usa objetos llamados permisos para implementar este tipo de seguridad en cdigo gestionado. Los principales usos de los permisos son los siguientes: El cdigo puede solicitar los permisos que pretende usar o que posiblemente necesite. .NET Framework tiene la tarea de determinar si estas peticiones son vlidas. Las peticiones de seguridad se conceden slo si las pruebas recogidas del cdigo lo permiten. El cdigo nunca recibe ms permisos de los permitidos por la seguridad actual. Por otra parte, el cdigo puede recibir menos permisos que los especificados en la peticin.
799
El CLR concede permisos al cdigo basndose en varios factores: La identidad del cdigo (como la URL de la que fue obtenido, quin escribi el cdigo y similares), los permisos que se solicitan y la cantidad de cdigo que es de confianza, definido por las diferentes directivas de seguridad. El cdigo puede hacer una peticin para obtener un determinado permiso. Si se realiza una peticin mediante el cdigo, todo el cdigo que se ejecute en el contexto de la aplicacin debe tener acceso al permiso para que ste sea concedido.
El cdigo puede recibir tres clases de permisos, cada uno de los cules tiene un propsito especfico: Los permisos de cdigo de acceso representan el acceso a un recurso protegido o la autoridad para realizar una operacin protegida. Los permisos de identidad indican que el cdigo tiene credenciales que admiten un tipo de identidad particular, como el cdigo que pueda tener una identidad "Administrador" y, por tanto, ejecutarse con todos los permisos que pueda tener un administrador. Los permisos de seguridad basada en funciones proporcionan un mecanismo para descubrir si un usuario (o el agente que acta en nombre del usuario) tiene una identidad particular o es un miembro de un cargo especfico. PrincipalPermission es el nico permiso de seguridad basada en funciones.
El tiempo de ejecucin proporciona clases de permisos integradas en varios espacios de nombres, y proporciona compatibilidad para disear e implementar clases de permisos personalizadas.
Seguridad de usuario
Casi todos los sistemas de seguridad actuales implementan algo llamado seguridad de usuario. Estos tipos de sistemas de seguridad requieren informacin de los usuarios que solicitan acceso. Por ejemplo, deben saber quin es esa persona y a qu elementos tiene acceso ese usuario. La seguridad de usuario desempea un papel muy importante en los sistemas computerizados porque, cuando ejecuta una aplicacin en su equipo, la aplicacin suele guardar la identidad de la persona que la est ejecutando. Por tanto, si ejecuta una aplicacin, esa aplicacin tiene todos los derechos y permisos en su equipo local y a travs de la red que tendramos nosotros. A diferencia de los servicios Windows, que le permiten configurar quin parece estar ejecutando la aplicacin, una aplicacin Windows normal nunca haba otorgado este tipo de control con anterioridad. Este hecho ha facilitado la prolife-
800
racin de muchos virus y troyanos a los que tienen que enfrentarse diariamente los usuarios de ordenadores y empresarios. Al permitirle determinar el tipo de permisos que tienen las aplicaciones en su equipo, se reduce enormemente la posibilidad de un ataque por parte de un cdigo maligno. Operaciones como leer el registro, sobrescribir archivos de sistema o recorrer su libreta de direcciones personal, no sern posibles. Puede probar rpidamente si sus aplicaciones se ejecutan segn el usuario que las ejecuta probando el programa del listado 37.1.
Listado 37.1. Variables de entorno para tareas de seguridad sencillas using System; namespace SimpleSecurity { class Class1 { [STAThread] static void Main(string[] args) { Console.WriteLine("I am currently running as:"); Console.WriteLine("User : {0}", Environment.UserName); Console.WriteLine("Domain: {0}", Environment.UserDomainName); } } }
Cuando ejecute este programa, debera ver el nombre que usa para conectarse a Windows, adems del nombre de su dominio, como muestra la figura 37.1.
c:\ C:\WINDOWS\System32\cmd.exe C:\>SimpleSecurity.exe I am currently running as: User : Francisco Domain: FRAN C:\>
Figura 37.1. La clase Environment puede ser utilizada para tareas de seguridad sencillas
Si no est conectado a un dominio de red, simplemente ver el nombre de su sistema como el nombre de dominio. El tipo de seguridad ms sencillo que proba-
801
blemente pueda implementar en este momento sera hacer que se compare el nombre de usuario y el nombre de dominio para validar una operacin y, si todo es correcto, continuar con el programa. Esto es vlido hasta que lleva su aplicacin a otro equipo, y deja de funcionar porque ha incluido nombres seguros en su cdigo. La siguiente seccin revisa este tipo de seguridad junto con otros tipos sencillos.
Al utilizar cdigo administrado puede emplear comprobaciones de seguridad imperativa o declarativa para determinar si un objeto principal concreto es miembro de una funcin conocida, tiene una identidad conocida o representa una identidad conocida que acta en una funcin. Para realizar la comprobacin de seguridad utilizando seguridad imperativa o declarativa, se debe efectuar una solicitud de seguridad para un objeto PrincipalPermission. Durante la comprobacin de seguridad, el entorno comn de ejecucin examina el objeto principal de quien efecta la llamada para determinar si su identidad y su funcin coinciden con las representadas por el objeto PrincipalPermission que se demanda. Si el objeto principal no coincide, se inicia una excepcin SecurityException. Cuando esto sucede, slo se comprueba el objeto principal del subproceso actual. La clase PrincipalPermission no produce un recorrido de pila con permisos de acceso a cdigo, ya que podra causar graves problemas de seguridad. Adems, se puede acceder directamente a los valores del objeto principal y realizar comprobaciones sin un objeto PrincipalPermission. En este caso, basta con leer los valores del subproceso Principal actual o utilizar la autorizacin de ejecucin del mtodo IsInRole.
802
Windows 2000 y Windows XP. En lugar de aadir privilegios para cada usuario, puede crear un nuevo grupo con ciertos derechos de acceso y luego agregar los usuarios al grupo en concreto. Estas funciones ahorran una cantidad considerable de tiempo y permiten a los administradores del servidor controlar una gran cantidad de usuarios. Empecemos agregando un nuevo grupo en Windows 2000/ Windows XP: 1. Haga clic con el botn derecho del ratn en Mi PC y seleccione Administrar. Cuando se abra la consola de Administracin de equipos, expanda la vista del rbol en el panel izquierdo haciendo clic en Usuarios locales y grupos para que aparezca Grupos, y haga clic en Grupos. 2. Cuando haga clic en Grupos, ver una lista de aproximadamente siete grupos integrados en el sistema operativo Windows, como muestra la figura 37.2.
3. Haga clic con el botn derecho del ratn en el panel de la derecha y seleccione Grupo nuevo. Llame a este grupo Developers, como muestra la figura 37.3. 4. Tras crear este grupo, haga clic en el botn Agregar y agregue su cuenta de usuario al grupo. Para este ejemplo, asegrese de que no est en el grupo Administradores. Si es un administrador, quizs quiera probar la siguiente aplicacin con otra cuenta Windows.
803
Una vez que ha colocado el nuevo grupo, vamos a estudiar las clases WindowsPrincipal y WindowsIdentity. Cuando se usan en conjunto, estas dos clases pueden determinar si el usuario actual de Windows pertenece a algn grupo especfico. Examine la aplicacin de ejemplo del listado 37.2.
Listado 37.2. WindowsPrincipal le permite comprobar la pertenencia a una funcin using System; using System.Security.Principal; class Class1 static void Main() { WindowsIdentity wi = WindowsIdentity.GetCurrent(); WindowsPrincipal wp = new WindowsPrincipal(wi); // Esto comprueba los derechos del administrador local // si se encuentra en un dominio if (wp.IsInRole(WindowsBuiltInRole.Administrator()) Console.WriteLine("You are an Administrator!"); else Console.WriteLine("You are not an Administrator."); (wp.IsInRole("POWERHOUSE\\Developer")) Console.WriteLine("You are in the Developer group!"); else Console.WriteLine("You are not in the Developer group."); } } if
804
Este cdigo crea un nuevo objeto WindowsIdentity (basndose en la identidad del usuario actual) con el mtodo GetCurrent. El objeto WindowsPrincipal usa este objeto de identidad como parmetro en su constructor, de modo que pueda recuperar cierta informacin sobre la persona u objeto. A continuacin llama al mtodo IsInRole de la clase WindowsPrincipal para determinar si el usuario pertenece al grupo Administradores. El mtodo IsInRole tiene tres variaciones sobrecargadas de las cules puede usar dos. La primera recibe una enumeracin WindowsBuiltInRole. Cuando compruebe la pertenencia a alguno de los grupos integrados en Windows, debe usar esta enumeracin. Dependiendo de si es un administrador, ver uno de los mensajes. A continuacin, el cdigo comprueba si el usuario actual pertenece al nuevo grupo Developer usando la segunda versin del mtodo IsInRole. Esta versin simplemente recibe un parmetro de cadena que especifica el equipo o el nombre de dominio seguido por el nombre de grupo. En el cdigo anterior, sustituya la palabra POWERHOUSE por el nombre de su dominio o equipo. Si no pertenece al grupo Administrador y Developer podr observar que esta aplicacin de ejemplo slo le reconoce en el grupo Administradores, como muestra la figura 37.4.
c:\ C:\WINDOWS\System32\cmd.exe C:\>IsAdmin.exe You are an Administrator! You are not in the Developer group. C:\>_
Esta confusin se produce porque, si es un administrador, forma parte inherente de todos los grupos y tiene acceso a todo. Por tanto, cuando compruebe la pertenencia a funciones en sus aplicaciones, es aconsejable comprobar la pertenencia al grupo especfico y a todos los otros grupos que estn por encima del grupo que est comprobando (por ejemplo, Administradores, Usuarios preferentes y similares).
805
Principales
Cada subproceso de una aplicacin .NET est asociada con un principal del CLR. El principal contiene una identidad que representa la identidad del usuario que est ejecutando ese subproceso. Si usa una propiedad esttica llamada Thread.CurrentPrincipal, puede devolver el principal actual asociado al subproceso. Los objetos principales implementan la interfaz IPrincipal, que slo contiene un mtodo y una propiedad. La propiedad Identity devuelve el objeto actual de identidad, y el mtodo IsInRole se usa para determinar si un usuario pertenece a una funcin o grupo de seguridad determinado. Actualmente .NET Framework contiene dos clases principal: W i n d o w s P r i n c i p a l y GenericPrincipal. La clase GenericPrincipal se emplea cuando hace falta implementar un principal propio. La clase WindowsPrincipal representa un usuario de Windows y sus funciones o grupos asociados. Un objeto Identity implementa la interfaz IIdentity, que slo tiene tres propiedades. Name es la cadena asociada a la identidad actual. El sistema operativo del proveedor de autenticacin pasa la cadena al entorno comn de ejecucin. Un ejemplo de proveedor de autenticacin es NTLM (Windows NT Challenge/Response), que autentifica conexiones de Windows NT. IsAuthenticated es un valor booleano que indica si el usuario ha sido autenticado. AuthenticationType es una cadena que indica qu tipo de autenticacin se ha usado. Algunos tipos posibles de autenticacin son autenticacin bsica, Forms, Kerberos, NTLM y autenticacin de pasaporte.
806
Tabla 37.1. Permisos de acceso a cdigo ms comunes Recurso DNS Variables de entorno Registro de eventos Permiso DNSPermission EnvironmentPermission EventLogPermission Descripcin Acceso al sistema de nombres de dominio. Acceso a las variables de entorno del sistema. Acceso a los registros de eventos, incluyendo los registros de eventos existentes y la creacin de nuevos registros de eventos. Acceso a realizar operaciones como leer un archivo y escribir en un archivo. Acceso al registro de Windows. Acceso a la funcionalidad de la interfaz. Acceso a realizar o aceptar conexiones en una direccin Web.
Operaciones de archivo
FileIOPermission
807
tud de permiso, se inicia una SecurityException. Si efecta esta solicitud en un bloque try/catch, no obtendr ninguna advertencia porque el error es controlado. Aunque pueda saber que posee este tipo de permiso en su equipo, no puede predecir las directivas de seguridad que pueden bloquear este acceso en otros equipos o redes. Tras crear una solicitud de permiso, slo tiene que llamar al mtodo Demand() de la clase RegistryPermission. Si Demand() se ejecuta sin producir ninguna excepcin, se ha aceptado su solicitud de permiso. El listado 37.3 contiene el ejemplo de aplicacin.
Listado 37.3. Solicitud de permiso con un controlador de errores estructurado using System; using Microsoft.Win32; using System.Security.Permissions; class Class1 { static void Main(string[] args) { try { RegistryPermission regPermission = new RegistryPermission(RegistryPermissionAccess.AllAccess, "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"); regPermission.Demand(); } catch (Exception e) { Console.WriteLine(e.Message); return; } RegistryKey myRegKey=Registry.LocalMachine; myRegKey=myRegKey.OpenSubKey ("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"); try { Object oValue=myRegKey.GetValue("RegisteredOwner"); Console.Writeline("OS Registered Owner: { 0 } " , oValue.ToString); } catch (NullReferenceException) { } } }
No olvide que aunque las directivas de seguridad .NET permitan a este cdigo ejecutarse, las directivas de seguridad del sistema operativo subyacente tambin
808
deben concederle permiso para ejecutarse. Tras solicitar los permisos para la clave de registro adecuada, slo tiene que leer la clave RegisteredOwner y mostrar la informacin en la ventana de consola.
Denegacin de permisos
Al igual que el mtodo Demand(), tambin puede llamar al mtodo Deny(), que elimina los permisos para una operacin. Por lo general, es aconsejable eliminar, antes de hacer la llamada, cualquier permiso que sepa que no va a necesitar. Puede solicitar permisos a medida que el cdigo los vaya necesitando. Use el mtodo Deny() cuando haya completado una operacin y sepa que ya no van a ser necesarias ms operaciones. La denegacin de permisos tiene varias funciones. Por ejemplo, si est usando bibliotecas de terceros, querr asegurarse de que, tras manipular el registro, ningn otro cdigo pueda hacerlo. La denegacin de permisos es un modo de conseguirlo. El cdigo del listado 37.4 usa una versin modificada del ejemplo anterior para denegar en primer lugar un permiso del registro. Tras negar el permiso, intenta leer la clave de registro, lo que da como resultado una SecurityException. Si quiere deshacer una operacin Deny() en el cdigo, slo tiene que usar el mtodo RevertDeny() para eliminar la denegacin de permiso; y cualquier intento posterior de leer la clave de registro solicitada se llevar a cabo con xito.
Listado 37.4. Denegacin de permisos a los que no desea que se acceda using System; using Microsoft.Win32; using System.Security.Permissions; class Class1 { static void Main(string[] args) { try { RegistryPermission regPermission = new RegistryPermission(RegistryPermissionAccess.AllAccess, "HKEY_LOCAL MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"); regPermission.Deny(); } catch (Exception e) { Console.WriteLine(e.Message); return; }
809
RegistryKey myRegKey=Registry.LocalMachine; myRegKey=myRegKey.OpenSubKey ("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"); try { Object oValue=myRegKey.GetValue("RegisteredOwner"); Console.WriteLine("OS Registered Owner: {0}", oValue.ToString()); } catch (NullReferenceException) { } } }
Si est ejecutando este ejemplo en Visual Studio, la aplicacin deber detenerse en las lneas de manipulacin del registro. Al ejecutar esta aplicacin desde la consola se genera una larga lista de errores de excepcin que indican cul es el problema.
810
RegistryKey myRegKey=Registry.LocalMachine; myRegKey=myRegKey.OpenSubKey ("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"); try { Object oValue=myRegKey.GetValue("RegisteredOwner"); Console.WriteLine("OS Registered Owner: {0}", oValue.ToString()); } catch (NullReferenceException) { } } }
Directivas de seguridad
Las directivas de seguridad son el corazn de la seguridad basada en pruebas. Despus de que se obtiene una prueba de un ensamblado, ese cdigo es asignado a un grupo de cdigo. Este grupo de cdigo, a su vez, tiene un conjunto de permisos que definen lo que el cdigo puede y no puede hacer. No slo puede modificar las directivas de seguridad para que se ajusten a sus necesidades, puede modificarlas a varios niveles y puede crear grupos de cdigo personalizados que complementen las directivas de seguridad que ha definido.
Empresa
Equipo
Dominio de aplicacin
Usuario
Figura 37.5. Los niveles se solapan para determinar un nivel de seguridad final
811
Si cambia las directivas de su equipo para permitir ciertos tipos de operaciones de, por ejemplo, el cdigo descargado de Internet, su administrador de red puede aplicar una directiva de seguridad de empresa para prohibir esas operaciones.
Grupos de cdigo
Todos los niveles de directivas de seguridad contienen grupos de cdigo que, a su vez, contienen zonas para cada grupo de cdigo. Este resultado es un ajuste de configuracin de seguridad muy detallado a lo largo de todos los niveles de directivas, y permite que haya diferentes tipos de seguridad en cada nivel de directiva dependiendo de la zona del cdigo en cuestin. Inmediatamente dentro del grupo de cdigo se sita un nodo All_Code. Como el propio nombre indica, estos conjuntos de permisos se aplican a todo el cdigo. Adems de este nodo All_Code, puede agregar ms nodos para satisfacer sus necesidades. Por ejemplo, puede crear nodos para el cdigo que recibe de los consultores o de cualquier otro tipo de fuente. Cuando evale niveles de seguridad, no olvide el modo en el que la directiva de cdigo se evala realmente. Los permisos para un ensamblado se unen a cada nivel de directivas de seguridad. Al unir todos estos permisos, debe trabajar con un enorme conjunto de permisos. Cada uno de estos conjuntos de permisos se solapan para que se pueda realizar una comparacin, y el valor ms restrictivo para cada permiso se usa para el conjunto de permisos final.
812
Descripcin Conjunto de permisos de directivas predeterminado, adecuado para contenidos de origen desconocido Conjunto de permisos de directivas predeterminado establecido en una empresa Todos los permisos estndar (integrados) excepto el permiso de omitir la comprobacin Acceso completo a todos los recursos
813
Resumen
.NET Framework se basa en una inmensa cantidad de cdigo de seguridad que vigila cada aspecto de una aplicacin o usuario. Este entorno de seguridad permi-
814
te al programador y al administrador de empresa controlar lo que permite realizar a una aplicacin. Hemos estudiado las seguridades de identidad de usuario y de acceso a cdigo. Si se usan conjuntamente con la seguridad del sistema operativo subyacente, puede crear aplicaciones ms seguras.
815
Parte VI
Apndices
817
A Manual de XML
A menos que haya vivido en una cueva durante los ltimos aos, ya habr odo hablar de XML. Sin lugar a dudas, XML ha recibido muy buenas crticas, ms de las que merece. Sin embargo, a pesar de lo que pueda haber odo en algn ilustre folleto de marketing, no es probable que XML solucione el hambre del mundo, traiga la paz mundial o cure todas las enfermedades. Tras leer esta seccin, dominar las bases de XML y sus estndares asociados, como esquemas y espacios de nombres. En pocas palabras, XML es un dialecto SGML simplificado diseado para la interoperabilidad, y est considerado el ASCII del futuro. En la ltima dcada, ASCII ha sido el estndar tradicional para el intercambio de datos de texto, pero est siendo desplazado rpidamente por XML como el nuevo estndar. En este captulo aprender a apreciar la peculiar elegancia de XML: una combinacin nica de extremada sencillez y enorme potencia. Tambin aprender que otros estndares complementan a XML. La familia XML de estndares complementarios ha crecido mucho en los ltimos aos, de modo que, para abreviar, slo le mostraremos los estndares ms relevantes.
819
(lenguaje extensible de marcas), pero merece la pena sealar este hecho evidente porque capta la esencia de XML. Extensible significa que puede agregar nuevas palabras al lenguaje para que se adecen a sus propsitos especficos. Un lenguaje de marcas incluye smbolos especiales en un documento para cumplir alguna funcin especfica. Esta funcin vara de un lenguaje de marcas a otro. Uno de los puntos fuertes de XML es que sus funciones son muy amplias: sirve como lenguaje universal de texto para los datos estructurados. El Lenguaje de marcas de hipertexto (HTML), el Lenguaje estndar universal de marcas (SGML) y el Formato de texto enriquecido (RTF) son otros ejemplos de lenguajes de marcas de los que seguramente habr odo hablar. NOTA: Como XML es un lenguaje informtico universal, se ha acuado el trmino "Esperanto para ordenadores" como un modo de referirse a XML. ste es un buen smil, salvo porque el esperanto no puede considerarse ningn xito. Antes de introducirnos en la sintaxis y la gramtica de XML, merece la pena examinar los diez objetivos de diseo de XML como los estipularon sus creadores. Estos objetivos se enumeran a continuacin y se explican con detalle ms adelante. Algunos de estos objetivos son de naturaleza bastante tcnica y se aclararan ms tarde en este apndice, cuando algunos de los trminos que mencionan (por ejemplo, definicin del tipo de documento) sean explicados. Sin embargo, la mayor parte de estos objetivos proporcionan una importante comprensin de las pretensiones de XML. 1. XML debe ser fcilmente utilizable en Internet. 2. XML debe admitir una amplia variedad de aplicaciones. 3. XML debe ser compatible con SGML. 4. Debe ser sencillo escribir programas que procesen documentos XML. 5. El nmero de caractersticas opcionales en XML debe mantenerse al mnimo, preferentemente a cero. 6. Los documentos XML deben ser legibles para las personas y razonablemente claros. 7. El diseo de XML debe ser preparado rpidamente. 8. El diseo de XML debe ser formal y conciso. 9. Los documentos XML deben ser fciles de crear. 10. La concisin de las marcas XML es de mnima importancia.
820
821
Objetivo 4: Debe ser sencillo escribir programas que procesen documentos XML
Este objetivo se meda originalmente con la prueba de que un licenciado en informtica debera ser capaz de escribir un procesador XML bsico en una o dos semanas. A posteriori, este objetivo cuantitativo ha resultado demasiado ambicioso, pero la gran cantidad de procesadores XML disponibles (la mayora gratuitos) es un claro indicador de que se ha conseguido este objetivo cualitativamente. Sin embargo, la reciente proliferacin de estndares relacionados con XML (XML Schema, X-Path, X-Link, etc.) ha hecho que se comente que XML no ha logrado alcanzar este objetivo concreto.
Objetivo 5: El nmero de caractersticas opcionales en XML debe mantenerse al mnimo, preferentemente a cero
Este objetivo se formul para garantizar que exista una caracterstica coherente entre todos los procesadores XML porque slo habr una caracterstica posible de implementar. Por tanto, cada procesador XML existente debe ser capaz de leer todos los documentos XML existentes (siempre que pueda decodificar sus caracteres). SGML, por otra parte, tiene muchas caractersticas opcionales en su especificacin. En la prctica, esto significa que la posibilidad de intercambiar un documento SGML, creado con un procesador SGML, con otro, depende de las caractersticas opcionales implementadas en cada procesador.
Objetivo 6: Los documentos XML deben ser legibles para las personas y razonablemente claros
Este objetivo habla por s mismo y tiene la ventaja de que se puede emplear un editor de texto, incluso uno muy bsico como el Bloc de notas, para crear documentos XML funcionales.
822
823
nen cmo debe ser presentado el contenido en un navegador Web. Un marcador inicial y un marcador final (a partir de ahora los llamaremos etiquetas) rodean al contenido, por ejemplo: <etiqueta>contenido</etiqueta>. La etiqueta inicial, el contenido y la etiqueta final reciben el nombre de elementos. La etiqueta inicial y la etiqueta final estn rodeadas por comillas angulares (< y >). La etiqueta final usa la misma palabra contenida en la etiqueta inicial precedida por una barra diagonal ( / ). De modo que si la etiqueta inicial es <font>, la etiqueta final debe ser </font>. En XML las etiquetas distinguen entre maysculas y minsculas, de modo que las palabras usadas en las etiquetas inicial y final deben tener los mismos caracteres. Por tanto, en XML no puede usar <font> (con f minscula) en la etiqueta inicial y </Font> (con F mayscula) en la etiqueta final. En HTML las etiquetas no distinguen entre maysculas y minsculas, de modo que se aceptan etiquetas con diferente uso de maysculas y minsculas. En HTML las etiquetas que se pueden usar estn predefinidas. Ejemplos de etiquetas HTML son h1 (<h1> y </h1>) para cabecera 1, y b (<b> y </b>) para negrita. Conocer HTML significa saber cundo usar cada etiqueta predefinida. Por ejemplo, para que la palabra "Abbreviation" aparezca en negrita en el navegador, escribira <b>Abbreviation</b>. Cuando el navegador lee esta combinacin de etiqueta y contenido, elimina las etiquetas y muestra el contenido en negrita. Una combinacin aleatoria de etiquetas HTML y contenidos no suele producir un documento HTML vlido. Una pgina HTML debe tener una cierta estructura. El contenido del documento debe estar entre <html> y </html>, y consta de una cabecera y un cuerpo. Cada una de estas secciones (llamadas, evidentemente, cabecera y cuerpo) est delimitada por etiquetas y tiene un contenido, que puede estar rodeado por etiquetas de presentacin. El listado A.1 muestra la estructura de un documento HTML. Casualmente, este listado tambin muestra cmo se incrustan comentarios en una pgina HTML: <!--EL COMENTARIO SE SITA AQU -->. NOTA: Los comentarios se pasan por alto y no afectan a la presentacin de la pgina en el navegador. Slo se usan para mostrar informacin al lector humano del cdigo fuente HTML. Algunos comentarios contienen cdigos especiales que pueden comprender programas especficos (por ejemplo, el servidor Web), pero esto queda fuera del alcance de esta breve explicacin de HTML.
824
<!--SITE AQU EL CONTENIDO DE LA CABECERA </head> <body> <!-- SITE AQU EL CONTENIDO DEL CUERPO </body> </html>
HTML tambin consta de un mecanismo para agregar ms informacin a una etiqueta: los llamados atributos. Un atributo especifica una propiedad que pertenece a una etiqueta, como el tamao de una fuente. Por ejemplo, para que la palabra "Meaning" aparezca con tamao 4, debera escribir
<font size="4">Meaning</font>
Como puede ver en el ejemplo anterior, los atributos se escriben en la etiqueta inicial y hay un espacio de separacin entre el nombre de la etiqueta y el nombre del atributo. Estos toman la forma
nombre_atributo=valor_cadena
y mostrando el elemento
<etiqueta
completo:
= valor_cadena>contenido</etiqueta>
nombre_atributo
En HTML, al igual que las etiquetas, estn predefinidos los atributos que puede usar con cada etiqueta. La etiqueta font, por ejemplo, tiene un atributo de tamao. Los valores del atributo deben estar entre comillas dobles o sencillas (no importa cul de las dos use mientras las comillas de apertura sean del mismo tipo que las de cierre). Una etiqueta puede contener ms de un atributo. Cada atributo est separado por un espacio. Por ejemplo, quizs quiera especificar el borde, altura y anchura de una tabla, que se escribira
<table border="1" width="359" height="116"></table>
En realidad HTML tambin acepta valores de atributos que no estn entre comillas. XML, por el contrario, necesita las comillas. Habr observado una tendencia: XML tiene un conjunto de reglas ms estricto que HTML. El listado A.2 muestra un sencillo documento HTML, mezclando etiquetas (algunas con uno o ms atributos) y contenidos. En caso de que est intentando descifrar las etiquetas HTML de este ejemplo, en l se muestra la creacin de una tabla HTML (una tabla HTML tiene el aspecto de una tabla en un procesador de texto). La tabla est encerrada en una etiqueta <table>. Cada fila est encerrada en una etiqueta <tr> (table row). En cada fila se crean las celdas usando la etiqueta <td> (table divisor). El resto del documento HTML se explica por s mismo. No se preocupe si no entiende algn detalle al leer el documento HTML. Esta seccin es sobre XML, de modo que trata HTML de un modo superficial.
825
826
Cuerpo:
Document Element <Document> <!-- Site aqu el documento--> </Document>
Eplogo:
<!-- Site aqu los comentarios-->
Un documento XML comienza con un prlogo. Si excluimos los comentarios opcionales, el prlogo contiene dos elementos principales (que tambin son opcionales). La declaracin XML necesita un atributo, que sirve para especificar la versin de la especificacin XML a la que se ajusta el documento. La declaracin XML tambin tiene dos atributos opcionales: uno para especificar la codificacin de caracteres usada, y otro para especificar si el documento depende de una definicin de tipo de documento (DTD). A continuacin tiene un ejemplo de una declaracin XML completa que usa los tres atributos.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
Los atributos de la declaracin XML deben usarse en el orden que aparece en el ejemplo. El atributo de versin es obligatorio y debe tener el valor "1.0". La
827
codificacin de caracteres del documento XML y las definiciones de tipo de documento se explican ms adelante. El elemento del documento deben estar dentro de la etiqueta raz. En el ejemplo anterior, esta etiqueta raz es la etiqueta <Document>, pero puede usar cualquier etiqueta para encerrar el elementos del documento. Por ltimo, todas las etiquetas de un documento XML deben anidarse adecuadamente. Si un elemento est contenido en otro elemento, recibe el nombre de secundario, y el elemento contenedor recibe el nombre de primario. Aqu tiene un ejemplo:
<Book Category="Chess"> <Title>My System</Title> <Author>Aron Nimzowitsch</Author> </Book>
En el ejemplo anterior la etiqueta <Book> es primaria para dos secundarias, los elementos <Author> y <Title>. La anidacin correcta requiere que los elementos secundarios estn siempre contenidos en sus elementos primarios. En otras palabras, la etiqueta final de un elemento secundario no puede aparecer despus de la etiqueta final de su elemento primario, como en el siguiente ejemplo:
<Book> <Title>Improper Nesting in XML Explained </Book> </Title>
El eplogo, que slo puede contener comentarios (adems de espacios en blanco e instrucciones de proceso), suele omitirse. Ya est preparado para un primer acercamiento a un documento XML, como el mostrado en el listado A.3. Los nmeros de lnea no forman parte del documento, y slo aparecen para poder hacer una explicacin lnea a lnea posterior ms sencilla.
Listado A.3. Sencillo documento XML
1: <?xml version="1.0" encoding="UTF-8" standalone="yes"?> 2: <!--Lista de libros sobre XML recomendados--> 3: <!--Compilado el 17 de Marzo del 17, 2000 por PGB --> 4: <XMLBooks> 5: <Book ISBN="0-7897-2242-9"> 6: <Title>XML By Example</Title> 7: <Category>Web Development</Category> 8: <Author>Benoit Marchal</Author> 9: </Book> 10: <Book ISBN="1-861003-11-0"> 11: <Title>Professional XML</Title> 12: <Category>Internet</Category> 13: <Category>Internet Programming</Category> 14: <Category>XML</Category> 15: <Author>Richard Anderson</Author>
828
l6: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34:
<Author>Mark Birbeck</Author> <Author>Michael Kay</Author> <Author>Steven Livingstone</Author> <Author>Brian Loesgen</Author> <Author>Didier Martin</Author> <Author>Stephen Mohr</Author> <Author>Nikola Ozu</Author> <Author>Bruce Peat</Author> <Author>Jonathan Pinnock</Author> <Author>Peter Stark</Author> <Author>Kevin Williams</Author> </Book> <Book ISBN="0-7358-0562-9"> <Title>XML in Action</Title> <Category>Internet</Category> <Category>XML</Category> <Author>William J. Pardy</Author> </Book> </XMLBooks>
La lnea 1 del listado A3 contiene una declaracin completa XML, que incluye los tres atributos. Las lneas 2 y 3 son comentarios usados en este caso para indicar la funcin de este documento. A continuacin est el cuerpo del documento XML, empezando en la lnea 4 y terminando en la lnea 34. Este documento no tiene eplogo, como suele ser habitual. El elemento del documento est dentro de la etiqueta <XMLBooks> (la etiqueta de inicio est en la lnea 4, la etiqueta final en la lnea 34). El elemento del documento tiene tres secundarios, cada uno encerrado entre una etiqueta <Book>. El secundario 1 empieza en la lnea 5 y finaliza en la lnea 9. El secundario 2 empieza en la lnea 10 y finaliza en la lnea 27. El secundario 3 empieza en la lnea 28 y finaliza en la lnea 33. Cada elemento <Book> tiene un atributo ISBN y una cantidad de secundarios: uno <Title>, uno o ms <Category> y uno o ms <Author>. Aqu puede apreciarse una ventaja significativa de XML sobre los tradicionales archivos de texto: XML est bien preparado para tratar con la estructura de primarios/secundarios. Como creador del documento, merece la pena sealar que yo invent las etiquetas y los atributos usados en este documento (XMLBooks, Book, ISBN, Title, Category, Author). Por ejemplo, otro autor podra haber preferido usar <ShelvingCateqory> en lugar de <Category>. Tambin puede hacerlo usted mismo si esta especificacin XML no llega a alcanzar el objetivo nmero 6 (los documentos XML deben ser legibles para las personas y razonablemente claros).
829
(DTD) proporciona un modo de especificar esta estructura o modelo de datos que corresponde a los datos del documento. Se puede realizar la comparacin con un esquema de base de datos que defina el modelo de datos de una base de datos. Esta comparacin funciona perfectamente porque tanto una base de datos como un documento XML contienen datos estructurados. La DTD es el esquema correspondiente al documento XML. El listado A.4 muestra la DTD correspondiente al listado A.3.
Listado A.4. Esquema DTD correspondiente al documento XML
<?xml version="1.0"?> <!-- El elemento superior, XMLBooks, es una lista de libros --> <!ELEMENT XMLBooks (Book+)> <!-- Un elemento Book contiene 1 Title, 1 o ms Cateqory, y 1 o ms Author --> <ELEMENT Book (Title, Category*, Author+)> <!-- Un Book tiene 1 atributo requerido --> <!ATTLIST Book ISBN ID #REQUIRED> <!-- Los elementos Title, Category, y Author contienen texto --> <!ELEMENT Title (#PCDATA)> <!ELEMENT Category (#PCDATA)> <!ELEMENT Author (#PCDATA)>
La estructura de la DTD es muy parecida a la de Extended Backus-Naur Form mencionada anteriormente. La DTD es un conjunto de reglas sucesivas que describen cmo ensamblar los datos en el modelo del documento XML. Cada regla describe un elemento especfico o un atributo que el modelo puede contener. Un documento XML es vlido si puede reducirse a una sola regla especfica de la DTD, sin ninguna entrada libre, mediante la aplicacin repetida de las reglas. A continuacin tiene una descripcin de la sintaxis usada en esta DTD. Observe que la DTD usa una sintaxis diferente de la de los documentos XML. Cada elemento se describe usando una lnea de descripcin de elemento:
<!ELEMENT element_name (element_content)>
element_name es la etiqueta que se usa para identificar cada elemento. En element_content debe colocar otros elementos, o bien #PCDATA para indicar que el elemento contiene texto. Los elementos hoja son elementos que no tienen secundarios. Estos elementos suelen especificarse como contenedores de #PCDATA . Los caracteres especiales tras un nombre de elemento indican la cardinalidad de los elementos contenidos. La cardinalidad indica cuntos de estos elementos
830
pueden existir y si el elemento es opcional o necesario. Hay cuatro modos de indicar la cardinalidad: Un elemento contenido sin ningn smbolo especial (como Title en el listado A.4) debe aparecer exactamente una vez en el elemento que se define (cardinalidad 1). Un elemento contenido seguido por un signo de interrogacin (?) es opcional y slo puede aparecer una vez en el elemento (cardinalidad 0..1). Los siguientes dos modos definen elementos repetidos, uno para los requeridos y otro para los opcionales: Un elemento contenido seguido por un signo de adicin (+) (como Book, y Author en el listado A.4) es obligatorio y puede aparecer repetido (cardinalidad 1..N). Un elemento contenido seguido por un asterisco (*) (como Category en el listado A.4) es opcional y puede aparecer repetido (cardinalidad 0..N).
<!ATTLIST element_name optionality> attribute_name attribute_content
Las listas de atributos se definen en una lnea separada. element_name es de nuevo la etiqueta a la que pertenece el atributo, attribute_name es el nombre del atributo (por ejemplo, ISBN en el listado A.4). El contenido del atributo se define usando una serie de palabras claves. La ms comn es CDATA , que indica que el atributo recibe datos carcter. La opcionalidad se indica mediante la palabra clave #REQUIRED para los atributos necesarios y #IMPLIED para los atributos opcionales.
Esquemas XML
El 2 de Mayo del 2001, el consejo encargado de controlar los estndares XML anunci que un importante miembro de la familia XML haba alcanzado el estatus de estndar (una recomendacin propuesta, como www.w3.org lo llama). Este estndar recibe el nombre de Esquemas XML y est destinado a reemplazar al DTD como sistema preferido para validar documentos XML. Un esquema XML ofrece dos ventajas evidentes sobre el DTD: Un esquema XML es un documento XML Un esquema XML permite especificar las caractersticas de datos (como tipo, tamao y precisin) de los elementos y atributos
831
<?xml
version="1.0"
encoding="UTF-8"?>
<xsd:schema
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
El atributo xmlns:xsd del elemento de esquema es una declaracin de espacio de nombres, que estudiaremos en la siguiente seccin. Observe que el valor de este atributo ha cambiado a lo largo de los aos, de modo que si se encuentra con un esquema con un valor diferente en este atributo (por ejemplo, www.w3.org/ 2000/10/XMLSchema), ese esquema fue creado correctamente de acuerdo con una versin de borrador del estndar de esquemas XML. El contenido del esquema consta de definiciones para los elementos y atributos que el esquema puede contener. Un elemento se define como se indica a continuacin:
< xsd:element name="theElementName"> <!-- Site aqu los detalles especficos del elemento --> </xsd:element>
<!-- Site aqu los detalles especficos del atributo --> </xsd:attribute>
Puede agregar documentacin con comentarios o insertar un elemento de anotacin dentro del elemento o definicin de atributo. El elemento de anotacin contiene un elemento de documentacin en el que puede explicar los detalles especficos del elemento o del atributo:
<xsd:annotation> <xsd:documentation>Some </xsd:annotation> explanation here...</xsd:documentation>
Puede agrupar los elementos y los atributos insertndolos en una etiqueta complexType:
<xsd:complexType> </xsd:complexType>
Este tipo de agrupacin es necesario cada vez que se encuentra con una definicin de elemento como la siguiente:
<!ELEMENT Book (Title, Category*, Author+)>
Los elementos agrupados en una secuencia deben ser mostrados en el orden en el que estn definidos:
832
<xsd:sequence> </xsd:sequence>
As, si define un elemento Book como se indica a continuacin, el elemento Book debe contener los elementos Title, Category y Author exactamente en este orden (por ejemplo, Title, Author y Category no sera vlido).
<xsd:element name="Book"> xsd:complexType> <xsd:sequence> <xsd:element name="Title"> </xsd:element> <xsd:element name="Category"/> <xsd:element name="Author"/> </xsd:sequence> </xsd:complexType> </xsd:element>
Se considera que la cardinalidad de los elementos es uno. Si quiere crear un elemento repetido, puede hacerlo agregando el atributo maxOccurs= "unbounded" a la definicin del elemento. Si quiere crear un elemento opcional, puede hacerlo agregando el atributo minOccurs="0" a la definicin del elemento. Por supuesto, puede combinar estos atributos para crear un elemento repetido opcional. Finalmente, puede especificar el tipo de dato de un elemento con el atributo type="xsd:datatype". En nuestro ejemplo, slo usamos el tipo de datos de cadena. El esquema XML permite una gran variedad de tipos de datos, como entero, largo, fecha, hora, doble, flotante, etc. El listado A.5 enumera el esquema XML correspondiente a la DTD mencionada anteriormente. La extensin de archivo de esquemas XML es .xsd y por eso a veces se les llama XSD.
Listado A.5. Esquema XML correspondiente al DTD
<?xml version="1.0" encoding="UTF-8"?> <!-- Esquema W3C para una lista de libros --> <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <xsd:element name="XMLBooks"> <xsd:annotation> <xsd:documentation>The top-level element, XMLBooks, is a list of books.</xsd:documentation> </xsd:annotation> <xsd:complexType> <xsd:sequence> <xsd:element name="Book" maxOccurs="unbounded"> <xsd:annotation> <xsd:documentation>A Book element contains 1 Title, 1 or more Category, and 1 or more Author.</ xsd:documentation> </xsd:annotation> <xsd:complexType>
833
<xsd:sequence> <xsd:element name="Title" type="xsd:string"> <xsd:annotation> <xsd:documentation>The Title, Category, and Author elements contain text.</xsd:documentation> </xsd:annotation> </xsd:element> <xsd:element name="Category" type="xsd:string" minOccurs="0" maxOccurs="unbounded"/> <xsd:element name="Author" type="xsd:string" maxOccurs="unbounded"/> </xsd:sequence> <xsd:attribute name="ISBN" type="xsd:string" use="required" id="isbn"> <xsd:annotation> <xsd:documentation>A Book has 1 required attribute.</xsd:documentation> </xsd:annotation> </xsd:attribute> </xsd:complexType> </xsd:element> </xsd:sequence> </xsd:complexType> </xsd:element> </xsd:schema>
El listado A.6 muestra cmo un documento XML puede hacer referencia a su esquema XML asociado.
Listado A.6. Documento XML que hace referencia a su esquema XML asociado <?xml version="1.0" encoding="UTF-8"?> <XMLBooks xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="./Books.xsd"> <Book ISBN="0-7897-2242-9"> <Title>XML By Example</Title> <Category>Web Development</Category> <Author>Benoit Marchal</Author> </Book> <Book ISBN="0-7356-0562-9"> <Title>XML in Action</Title> <Category>Internet</Category> <Category>XML</Category> <Author>William J. Pardy</Author> </Book> </XMLBooks>
834
una nueva torre de Babel. Afortunadamente, los programadores de los estndares XML percibieron el peligro e idearon una solucin, llamada espacios de nombres. Ya conoce el concepto de los espacios de nombres gracias a su estudio de C# (el mismo concepto aparece en C++, Java y otros lenguajes .NET). La implementacin vara un poco entre un caso y otro, pero la idea es siempre la misma. Se asocia un nombre nico a un prefijo y se usa este prefijo para calificar los nombres que podran colisionar sin el prefijo. Como XML est basado en Web, los diseadores decidieron usar las URLs como nombres nicos. El espacio de nombres usado en un esquema XML se especifica agregando el atributo targetNamespace="www.myurl.com" al esquema. Este espacio de nombres se define agregando un atributo especial xmlns al elemento de esquema. Puede anexar el prefijo del espacio de nombres usando dos puntos para separar el atributo xmlns de prefix. El diseador del esquema debe asegurarse de que el valor de este atributo es nico. Esto suele conseguirse usando la URL de la compaa.
xmlns:prefix="http://www.myurl.com"
Tras definir un prefijo de espacio de nombres, debe anexarlo a todos los elementos contenidos en el espacio de nombres.
<?xml version="1.0" encoding="UTF-8"?> <!-- Esquema W3C para una lista de libros --> <xsd:schema targetNamespace="www.myurl.com" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:book="www.myurl.com"> <xsd:element name="XMLBooks" > <xsd:annotation> <xsd:documentation>The top-level element, XMLBooks, is a list of books.</xsd:documentation> </xsd:annotation> <xsd:complexType> <xsd:sequence> <xsd:element name="Book" maxOccurs="unbounded"> <xsd:annotation> <xsd:documentation>A Book element contains 1 Title, 1 or more Category, and 1 or more Author.</ xsd:documentation> </xsd:annotation> <xsd:complexType> <xsd:sequence> <xsd:element name="Title" type="xsd:string"> <xsd:annotation> <xsd:documentation>The Title, Category, and Author elements contain text.</xsd:documentation> </xsd:annotation> </xsd:element> <xsd:element name="Category" type="xsd:string" minOccurs="0" maxOccurs="unbounded"/>
835
<xsd:element name="Author" type="xsd:string" maxOccurs="unbounded"/> </xsd:sequence> <xsd:attribute name="ISBN" type="xsd:string" use="required" id="isbn"> <xsd:annotation> <xsd:documentation>A Book has 1 required attribute.</xsd:documentation> </xsd:annotation> </xsd:attribute> </xsd:complexType> </xsd:element> </xsd:sequence> </xsd:complexType> </xsd:element> </xsd:schema>
El siguiente documento XML muestra cmo crear un documento XML que haga referencia a un esquema usando un espacio de nombres. Para ello se agregan tres atributos al elemento raz. El primer atributo define el prefijo usado por el espacio de nombres y la cadena nica asociada a este espacio de nombres. El segundo atributo especifica qu versin del esquema XML se est usando. Por ltimo, el tercer atributo indica qu espacio de nombres est usando el esquema XML y dnde est ubicado el esquema XML.
<?xml version="1.0" encoding="UTF-8"?> <book:XMLBooks xmlns:book="www.myurl.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="www.myurl.com .\Books.xsd"> <Book ISBN="0-7897-2242-9"> <Title>XML By Example</Title> <Category>Web Development</Category> <Author>Benoit Marchal</Author> </Book> <Book ISBN="0-7356-0562-9"> <Title>XML in Action</Title> <Category>Internets</Category> <Category>XML</Category> <Author>William J. Pardy</Author>
</Book>
</book:XMLBooks>
Como la mayora de los elementos de un documento XML pertenecen al mismo espacio de nombres, se puede crear un espacio de nombres predeterminado y omitir el prefijo del espacio de nombres, por ejemplo, xmlns="www.myurl. com". Para terminar, se pueden incluir varias declaraciones de espacios de nombres en el mismo documento XML. Esto se consigue agregando todos los atributos del espacio de nombres al elemento raz. No obstante, tenga en cuenta que un documento slo puede apuntar a un esquema XML.
836