F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
Volver al MENU PRINCIPAL
Contenido [ocultar] 1 Capítulo 1 o 1.1 La plataforma .NET 1.1.1 Independencia de lenguaje 1.1.2 Librería de clases común 1.1.3 Multiplataforma 1.1.4 Windows Forms, Web Forms, Web Services 1.1.5 Estandarización o 1.2 Un resúmen introductorio sobre el lenguaje C# 1.2.1 C# frente a Java 1.2.2 C# frente a C++ 1.2.3 ¿Por qué C#? o 1.3 Instalando lo necesario para empezar
[editar] Capítulo 1 Como hemos dicho C# (C Sharp) es parte de la plataforma .NET. C# es un lenguaje orientado a objetos simple, seguro, moderno, de alto rendimiento y con especial énfasis en internet y sus estándares (como XML). Es también la principal herramienta para programar en la plataforma .NET. Tal vez os habréis preguntado ¿Qué es la plataforma .NET? ¿Porqué Microsoft está invirtiendo tanto en esta nueva tecnología? ¿Qué es lo que es tan novedoso? ¿Como es que con .NET se pueden producir aplicaciones multi-plataforma? A continuación hablaremos un poco de la plataforma .NET
[editar] La plataforma .NET
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
Marco de trabajo .NET La plataforma .NET es una plataforma de desarrollo de software con especial énfasis en el desarrollo rápido de aplicaciones, la independencia de lenguaje y la transparencia a través de redes. La plataforma consta de las siguientes partes: Un conjunto de lenguajes de programación (C#, J#, JScript, C++ gestionado, Visual Básic.NET, y otros proyectos independientes). Un conjunto de herramientas de desarrollo (entre ellos Monodevelop o Visual Studio.NET de Microsoft ) Una libreria de clases amplia y común para todos los lenguajes. Un sistema de ejecucion de Lenguaje Común. (CLR). Un conjunto de servidores .NET Un conjunto de servicios .NET Dispositivos electrónicos con soporte .NET (PDA,Celulares, etc). Los puntos fuertes de la plataforma son:
[editar] Independencia de lenguaje Todos los lenguajes que conformen con los estándares .NET, sin importar cual, podrán interoperar entre sí de forma totalmente transparente, las clases podrán ser heredadas entre unos lenguajes y otros, y se podrá disfrutar de polimorfismo entre lenguajes. Por ejemplo, si yo tengo una clase en C#, esta clase podrá ser heredada y utilizada en Visual Basic o JScript o cualquier lenguaje .NET. Todo esto es posible por medio de una de las características de .NET llamado Common Type System (CTS). También tiene la cualidad de que se pueden incluir más lenguajes a la plataforma. En la actualidad existen proyectos independientes de incluir PHP, Python, Ada y otros lenguajes en la plataforma.
[editar] Librería de clases común
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
Más de 4000 clases, objetos y métodos incluidos en la plataforma .NET están disponibles para todos los lenguajes.
[editar] Multiplataforma Cuando un programa es compilado, no es compilado en un archivo ejecutable sino en un lenguaje intermedio llamado “Lenguaje Intermedio” (IL) el cual podrá ser ejecutado por el CLR (Common Language Runtime) en la plataforma en que el CLR esté disponible (hasta el día de hoy Microsoft solamente tiene un CLR para los sistemas operativos Windows, pero el proyecto Mono (www.mono-project.com) y dotGNU (www.dotGNU.org) han puesto a disposición un CLR para GNU/Linux, MacOS y muchas otras plataformas). Los sistemas operativos Windows XP o superiores incluyen el CLR nativamente y SuSE Linux 9.3 o superior planea incorporar el CLR (Mono) en su distribución lo que quiere decir que un programa .NET podrá ser compilado y ejecutado en cualquiera de estas plataformas, o en cualquier plataforma que incluya un CLR. El CLR compilará estos archivos IL nuevamente en código de máquina en un proceso que se conoce como JIT (justo a tiempo) el cual se ejecutará cuando se requiera. Este proceso producirá código de máquina bien eficiente que se reutilizará si es que hubiera código que se repitiera, haciendo que los programas sean ejecutados muy eficientemente. El CRL
[editar] Windows Forms, Web Forms, Web Services La plataforma .NET incluye un conjunto de clases especial para datos y XML que son la base de 3 tecnologías claves: Servicios Web (Web Services), Web Forms, y Windows Forms los cuales son poderosas herramientas para la creación de aplicaciones tanto para la plataforma como para el Web.
[editar] Estandarización Además de los méritos técnicos, una de las razones del éxito de la plataforma .NET ha sido por el proceso de estandarización que Microsoft ha seguido (y que ha sorprendido a más de uno). Microsoft, en lugar de reservarse todos los derechos sobre el lenguaje y la plataforma, ha publicado las especificaciones del lenguaje y de la plataforma, que han sido posteriormente revisadas y ratificadas por la Asociación Europea de Fabricantes de Computadoras (ECMA). Esta especificación (que se puede descargar libremente de Internet) permite la implementación del lenguaje C# y de la plataforma .NET por terceros, incluso en entornos distintos de Windows. Mono Hispano mantiene una traducción del estándar que describe el lenguaje C# en http://monohispano.org/ecma/ (Enlace roto)
[editar] Un resúmen introductorio sobre el lenguaje C# El lenguaje es muy sencillo, sigue el mismo patrón de los lenguajes de programación modernos. Incluye un amplio soporte de estructuras, componentes, programación
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
orientada a objetos, manipulación de errores, recolección de basura, etc, que es construido sobre los principios de C++ y Java. Como sabréis, las clases son la base de los lenguajes de programación orientados a objetos, lo cual permite extender el lenguaje a un mejor modelo para solucionar problemas. C# contiene las herramientas para definir nuevas clases, sus métodos y propiedades, al igual que la sencilla habilidad para implementar encapsulación, herencia y polimorfismo, que son los tres pilares de la programación orientada a objetos. C# tiene un nuevo estilo de documentación XML que se incorpora a lo largo de la aplicación, lo que simplifica la documentación en línea de clases y métodos. C# soporta también interfaces, una forma de estipular los servicios requeridos de una clase. Las clases en C# pueden heredar de un padre pero puede implementar varias interfaces. C# también provee soporte para estructuras, un concepto el cual ha cambiado signifivamente desde C++. Una estructura es un tipo restringido que no exige tanto del sistema operativo como una clase. Una estructura no puede heredar ni dar herencias de clases pero puede implementar una interfaz. C# provee características de componentes orientados, como propiedades, eventos y construcciones declaradas (también llamados atributos). La programación orientada a componentes es soportada por el CLR. C# provee soporte para acceder directamente a la memoria usando el estilo de punteros de C++ y mucho más.
[editar] C# frente a Java C# y Java son lenguajes similares, de sintaxis basada en C/C++, orientados a objetos, y ambos incluyen las características más importantes de los lenguajes modernos, como son la gestión automática de memoria y la compilación a código intermedio. Pero por supuesto, también hay diferencias. Una de las diferencias más importantes es que C# es mucho más cercano a C++ en cuanto a diseño se refiere. C# toma casi todos sus operadores, palabras reservadas y expresiones directamente de C++. También se han mantenido algunas características que en Java se han desestimado. Por ejemplo, la posibilidad de trabajar directamente con direcciones de memoria. Si bien tanto Java como .NET proporcionan gestión automática de memoria, en C# es posible usar lo que se denomina "código no seguro". Cuando se usa código no seguro en C# es posible operar con punteros de forma muy similar a como se haría en C/C++, pero el código que utiliza punteros se queda marcado como no seguro y no se ejecuta en entornos en los que no tiene permisos.
[editar] C# frente a C++ Puesto que C# se ejecuta en una máquina virtual, ésta se hace cargo de la gestión de memoria y por lo tanto el uso de punteros es mucho menos importante en C# que en C++. C# también es mucho más orientado a objetos, hasta el punto de que todos los tipos usados derivan en última instancia el tipo 'object'. Además, muchos tipos se usan de forma distinta. Por ejemplo, en C# se comprueban los límites de los arrays antes de usarlos, evitando así que se pueda escribir pasado el final del vector. Al igual que Java, C# renuncia a la idea de herencia múltiple de clases presente en C++. Sin embargo, referido a clases, C# implementa 'propiedades' del tipo de las que existen en Visual Basic, y los métodos de las clases son accedidos mediante '.' en lugar de '::'.
[editar] ¿Por qué C#?
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
La plataforma .NET acepta varios lenguajes. Por ahora, C#, Visual Basic, C++ gestionado, Nemerle, FORTRAN, Java, Python, etc. , y con capacidad para aceptar prácticamente cualquier lenguaje. Entonces la pregunta es, ¿porqué se eligió C# en lugar de cualquier otro lenguaje?. La razón fundamental es que C# se diseñó para la plataforma .NET y es capaz de utilizar todo su potencial. También es cierto que es un lenguaje "limpio" en el sentido de que al no tener que proporcionar compatibilidad hacia atrás se ha tenido más libertad en el diseño y se ha puesto especial hincapié en la simplicidad. Por ejemplo, en C# hay un tipo de clase y siempre se le aplica el recolector de basura mientras que en C++ gestionado hay dos tipos de clases, una a la que se aplica el recolector y otra a la que no.
[editar] Instalando lo necesario para empezar Para poder empezar con nuestro curso debéis tener instalado en vuestro ordenador los archivos básicos para poder compilar y ejecutar vuestros programas. El conjunto de utilidades "Microsoft .NET Framework" y el ".NET Framework SDK" para Windows y el proyecto MONO o dotGNU para Linux, MacOS, BeOS proporcionan estas herramientas. Podréis encontrarlas en las siguientes direcciónes: Para Windows: http://msdn.microsoft.com/netframework/downloads/updates/default.aspx Para Linux u otras plataformas: Proyecto Mono http://mono-project.com/Downloads (manual que describe distintos métodos de instalación) Proyecto dotGNU http://dotgnu.org/pnet-packages.html
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
Introducción al lenguaje de programación C# Características del lenguaje C# Aspectos Léxicos Tipos de datos Variables y constantes Operadores y expresiones Estructuras de control E/S básica C# (leído en inglés "C Sharp" y en español "C Almohadilla") es el nuevo lenguaje de propósito general diseñado por Microsoft para su plataforma .NET. Aunque es posible escribir código para la plataforma .NET en muchos otros lenguajes, C# es el único que ha sido diseñado específicamente para ser utilizado en ella, por lo que programarla usando C# es mucho más sencillo e intuitivo que hacerlo con cualquiera de los otros lenguajes ya que C# carece de elementos heredados innecesarios en .NET. Por esta razón, se suele decir que C# es el lenguaje nativo de .NET
Características del lenguaje C# Aunque es pronto para entrar con detenimiento en el lenguaje C# podemos adelantar las características más relevantes de este lenguaje, características que se describen con profundidad posteriormente, durante el estudio detallado de los elementos del lenguaje. Es autocontenido. Un programa en C# no necesita de ficheros adicionales al propio código fuente, como los ficheros de cabecera (.h) de C++, lo que simplifica la arquitectura de los proyectos software desarrollados con C++. Es homogeneo. El tamaño de los tipos de datos básicos es fijo e independiente del compilador, sistema operativo o máquina en la que se compile (no ocurre lo que en C++), lo que facilita la portabilidad del código. Es actual. C# incorpora en el propio lenguaje elementos que se han demostrado ser muy útiles para el desarrollo de aplicaciones como el tipo básico decimal que representa valores decimales con 128 bits, lo que le hace adecuado para cálculos financieros y monetarios, incorpora la instrucción foreach, que permite una cómoda iteración por colecciones de datos, proporciona el tipo básico string, permite definir cómodamente propiedades (campos de acceso controlado), etc. Está orientado a objetos. C# soporta todas las características propias del paradigma de la programación orientada a objetos: encapsulación, herencia y polimorfismo.
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
o Encapsulación: además de los modificadores de accceso convencionales: public, private y protected, C# añade el modificador internal, que limita el acceso al proyecto actual. o C# sólo admite herencia simple. o Todos los métodos son, por defecto, sellados, y los métodos
redefinibles han de marcarse, obligatoriamente, con el modificador virtual. Delega la gestión de memoria. Como todo lenguaje de .NET, la gestión de la memoria se realiza automáticamente ya que tiene a su disposición el recolector de basura del CLR. Esto hace que el programador se desentienda de la gestión directa de la memoria (petición y liberación explícita) evitando que se cometan los errores habituales de este tipo de gestión en C++, por ejemplo. En principio, en C# todo el código incluye numerosas restricciones para asegurar su seguridad no permite el uso de punteros, por ejemplo. Sin embargo, y a diferencia de Java, en C# es posible saltarse dichas restricciones manipulando objetos a través de punteros. Para ello basta marcar regiones de código como inseguras (modificador unsafe) y podrán usarse en ellas punteros de forma similar a cómo se hace en C++, lo que puede resultar vital para situaciones donde se necesite una eficiencia y velocidad procesamiento muy grandes. Emplea un sistema de tipos unificado. Todos los tipos de datos (incluidos los definidos por el usuario) siempre derivarán, aunque sea de manera implícita, de una clase base común llamada System.Object, por lo que dispondrán de todos los miembros definidos en ésta clase. Esto también es aplicable, lógicamente, a los tipos de datos básicos. Proporciona seguridad con los tipos de datos. C# no admiten ni funciones ni variables globales sino que todo el código y datos han de definirse dentro de definiciones de tipos de datos, lo que reduce problemas por conflictos de nombres y facilita la legibilidad del código. C# incluye mecanismos que permiten asegurar que los accesos a tipos de datos siempre se realicen correctamente: o No pueden usarse variables que no hayan sido inicializadas. o Sólo se admiten conversiones entre tipos compatibles o Siempre se comprueba que los índices empleados para acceder a
los elementos de una tabla (vector o matriz) se encuentran en el rango de valores válidos. o Siempre se comprueba que los valores que se pasan en una llamada a métodos que pueden admitir un número indefinido de parámetros (de un cierto tipo) sean del tipo apropiado. Proporciona instrucciones seguras. En C# se han impuesto una serie de restricciones para usar las instrucciones de control más comunes. Por ejemplo, toda condición está controlada por una expresión condicional, los casos de una instrucción condicional múltiple ( switch) han de terminar con una instrucción break o goto, etc.
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
Facilita la extensibilidad de los operadores. C# permite redefinir el significado de la mayoría de los operadores -incluidos los de conversión, tanto para conversiones implícitas como explícitas- cuando se aplican a diferentes tipos de objetos. Permite incorporar modificadores informativos sobre un tipo o sus miembros. C# ofrece, a través del concepto de atributos, la posibilidad de añadir, a los metadatos del módulo resultante de la compilación de cualquier fuente, información sobre un tipo o sus miembros a la generada por el compilador que luego podrá ser consultada en tiempo ejecución a través de la biblioteca de reflexión de .NET. Esto, que más bien es una característica propia de la plataforma .NET y no de C#, puede usarse como un mecanismo para definir nuevos modificadores. Facilita el mantenimiento (es "versionable"). C# incluye una política de versionado que permite crear nuevas versiones de tipos sin temor a que la introducción de nuevos miembros provoquen errores difíciles de detectar en tipos hijos previamente desarrollados y ya extendidos con miembros de igual nombre a los recién introducidos. Apuesta por la compatibilidad. C# mantiene una sintaxis muy similar a C++ o Java que permite, bajo ciertas condiciones, incluir directamente en código escrito en C# fragmentos de código escrito en estos lenguajes. En resumen, podemos concluir que: Es un lenguaje orientado al desarrollo de componentes (módulos independientes de granularidad mayor que los objetos) ya que los componentes son objetos que se caracterizan por sus propiedades, métodos y eventos y estos aspectos de los componentes están presentes de manera natural en C#. En C# todo son objetos: desaparece la distinción entre tipos primitivos y objetos de lenguajes como Java o C++ (sin penalizar la eficiencia como en LISP o Smalltalk). El software es robusto y duradero: el mecanismo automático de recolección de basura, la gestión de excepciones, la comprobación de tipos, la imposibilidad de usar variables sin inicializar y hacer conversiones de tipo (castings) no seguras, gestión de versiones, etc. ayudan a desarrollar software fácilmente mantenible y poco propenso a errores. Además, no hay que olvidar el aspecto económico: la posibilidad de utilizar C++ puro (código no gestionado o inseguro), la facilidad de interoperabilidad (XML, SOAP, COM, DLLs...) junto con un aprendizaje relativamente sencillo (para los que ya conocen otros lenguajes de programación) hace que el dominio y uso del lenguaje junto a otras tecnologías sea muy apreciado.
Aspectos Léxicos En esta sección presentaremos las reglas sintácticas básicas que deben cumplir los programas escritos en C# y veremos los elementos fundamentales de cualquier programa en C# (identificadores, comentarios, palabras reservadas, etc.). Se trata, en
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
definitiva, de la parte instrumental y básica, además de imprescindible, de cualquier manual de programación. Es costumbre desde la época dorada del lenguaje C (quizá una de las pocas costumbres que se mantienen en el mundo de la informática) que se presente un lenguaje de programación empleando un programa que muestra en la consola el mensaje ¡Hola, mundo! (para ser más precisos deberíamos decir Hello, world!). Sigamos manteniendo esta costumbre: ¡Hola, mundo! /* Fichero: Saludo.cs Fecha: Enero de 2004 Autores: F. Berzal, F.J. Cortijo y J.C.Cubero Comentarios: Primer programa escrito en C# */ using System; public class SaludoAlMundo { public static void Main( ) { // Mostrar en la consola el mensaje: ¡Hola, mundo! Console.WriteLine("¡Hola, mundo!"); Console.ReadLine(); // Enter para terminar. } }
Lo primero que hay que resaltar es que C# es un lenguaje sensible a las mayúsculas, por lo que, por ejemplo, Main es diferente a main, por lo que deberá prestar atención a la hora de escribir el código ya que la confusión entre mayúsculas y minúsculas provocará errores de compilación. Todas las órdenes acaban con el símbolo del punto y coma (;). Los bloques de órdenes (parte iterativa de un ciclo, partes dependientes de una instrucción condicional -parte if y parte else-, código de un método, etc.) se encierran entre llaves {} y no se escribe el ; después de la llave de cierre (observar en el ejemplo el método Main). Si el lector ha programado en C++ no habrá tenido dificultad en localizar los comentarios que hemos insertado en el programa ya que la sintaxis es idéntica en ambos lenguajes: existen comentarios de línea, cuyo comienzo se especifica con los caracteres // y comentarios de formato libre, delimitados por /* y */. C# es un lenguaje orientado a objetos y todo programa debe pertenecer a una clase; en este caso la clase se llama SaludoAlMundo. La línea using System declara que se va a usar el espacio de nombres (namespace) llamado System. Esta declaración no es igual a un #include de C#, tan solo evita
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
escribir el prefijo System cada vez que se use un elemento de ese espacio de nombres. Por ejemplo, Console es un objeto del espacio de nombres System; en lugar de escribir su nombre completo (System.Console) podemos escribir solamente Console al haber declarado que se va a emplear el espacio de nombres System. Cuando este programa se compile y se proceda a su ejecución, el primera función (estrictamente hablando, método) en ejecutarse será Main. Los programadores de C++ deberán tener especial cuidado y no confundirlo con main(). La función Main() tiene los siguientes prototipos válidos: Si la función no devuelve ningún valor, puede usarse: public static void Main( ) public static void Main(string [] args)
La diferencia está en la posibilidad de suministrar argumentos en la llamada a la ejecución del programa. Main() procesan los argumentos tomándolos de la lista de cadenas args (más adelante se detalla cómo). Si la función devuelve un valor al proceso que invoca la ejecución del programa, el valor debe ser entero (int) y, como en el caso anterior, puede usarse: public static int Main( ) public static int Main(string [] args)
La convención es que el valor 0 indica que el programa termina correctamente. En cuanto a las instrucciones que efectivamente se ejecutan, el método Main() llama a los métodos WriteLine() y ReadLine() del objeto Console. El primero se encargan de mostrar una cadena en la consola (Símbolo de Sistema, en Windows XP) y el segundo de tomar una cadena de la consola (teclado). Aunque esta última instrucción pueda parecer innecesaria, de no escribirla se mostraría la cadena ¡Hola, mundo! y se cerraría inmediatamente la consola, por lo que no podríamos contemplar el resultado de la ejecución. La última instrucción detiene la ejecución del programa hasta que se introduce una cadena (pulsar ENTER es suficiente) y a continuación termina la ejecución del programa y se cierra la consola. Así, cuando usemos aplicaciones de consola siempre terminaremos con esta instrucción. A continuacón detallaremos ciertos aspectos léxicos importantes de los programas escritos en C#, algunos de los cuales ya se han comentado brevemente como explicación al programa introductorio.
Comentarios Los comentarios tienen como finalidad ayudar a comprender el código fuente y están destinados, por lo tanto, a los programadores. No tienen efecto sobre el código ejecutable ya que su contenido es ignorado por el compilador (no se procesa). La sintaxis de los comentarios en C# es idéntica a la de C++ y se distinguen dos tipos de comentarios:
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
Comentarios de línea. Están precedidos de la construcción // y su efecto (ámbito) termina en la línea en la que está inmerso. Comentarios de formato libre. Están delimitados por las construcciones /* y */ y pueden extenderse por varias líneas. Ejemplos de comentarios // En una línea, al estilo de C++ /* En múltiples líneas, como se viene haciendo desde "los tiempos de C" */ /* Este tipo de comentario ya no es habitual */
Identificadores Un identificador es un nombre con el que nos referimos a algún elemento de nuestro programa: una clase, un objeto, una variable, un método, etc. Se imponen algunas restricciones acerca de los nombres que pueden emplearse: Deben comenzar por una letra letra o con el carácter de subrayado (_), que está permitido como carácter inicial (como era tradicional en el lenguaje C). No pueden contener espacios en blanco. Pueden contener caracteres Unicode, en particular secuencias de escape Unicode. Son sensibles a mayúsculas/minúsculas. No pueden coincidir con palabras reservadas (a no ser que tengan el prefijo @ que habilita el uso de palabras clave como identificadores). Los identificadores con prefijo @ se conocen como identificadores literales. Aunque el uso del prefijo @ para los identificadores que no son palabras clave está permitido, no se recomienda por regla de estilo.
Palabras reservadas Las palabras reservadas son identificadores predefinidos reservados que tienen un significado especial para el compilador por lo que no se pueden utilizar como identificadores en un programa a menos que incluyan el carácter @ como prefijo. Las palabras reservadas en C# son, por orden alfabético: 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, params, private, protected, public, readonly, ref, return, sbyte, sealed, short, sizeof, stackalloc, static, string, struct, switch, this, throw,
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
true, try, typeof, uint, ulong, unchecked, unsafe, ushort, using, virtual, void, volatile, while
Literales Un literal es una representación en código fuente de un valor. Todo literal tiene asociado un tipo, que puede ser explícito (si se indica en el literal, mediante algún sufijo, por ejemplo) o implícito (se asume uno por defecto). Los literales pueden ser: Literales lógicos. Existen dos valores literales lógicos: true y false . El tipo de un literal lógico es bool. Literales enteros. Permiten escribir valores de los tipos enteros: int, uint, long y ulong. Los literales enteros tienen dos formatos posibles: decimal y hexadecimal. Los literales hexadecimales tienen el sufijo 0x. El tipo de un literal entero se determina como sigue: o o o o
Si no tiene sufijo, su tipo es int, uint, long o ulong. Si tiene el sufijo U o u, su tipo es uint o ulong. Si tiene el sufijo L o l, su tipo es long o ulong. Si tiene el sufijo UL, Ul, uL, ul, LU, Lu, lU o lu es de tipo ulong.
A la hora de escribir literales de tipo long se recomienda usar L en lugar de l para evitar confundir la letra l con el dígito 1. Literales enteros 123 0x7B 123U 123ul 123L
// // // // //
int hexadecimal unsigned unsigned long long
Literales reales. Los literales reales permiten escribir valores de los tipos float, double y decimal. El tipo de un literal real se determina como sigue: o o o o
Si no se especifica sufijo, el tipo es double. Si el sufijo es F o f es de tipo float. Si el sufijo es D o d es de tipo double. Si el sufijo es M o m es de tipo decimal.
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
Hay que tener en cuenta que, en un literal real, siempre son necesarios dígitos decimales tras el punto decimal. Por ejemplo, 3.1F es un literal real, pero no así 1.F. Literales reales 1f, 1.5f, 1e10f, 123.456F, 123f y 1.23e2f // float 1d, 1.5d, 1e10d, 123.456D, 123.0 y 123D // double 1m, 1.5m, 1e10m, 123.456M, 123.456m y 12.3E1M // decimal.
Literales de caracteres. Un literal de caracteres representa un carácter único y normalmente está compuesto por un carácter entre comillas simples, por ejemplo 'A'. Una secuencia de escape sencilla representa una codificación de caracteres Unicode y está formada por el carácter \ seguido de otro carácter. Las secuencias válidas se describe en la siguiente tabla. Secuencia de escape \' \" \\ \0 \a \b \f \n \r \t \v
Nombre del carácter Comilla simple Comilla doble Barra invertida Null Alerta Retroceso Avance de página Nueva línea Retorno de carro Tabulación horizontal Tabulación vertical
Codificación Unicode 0x0027 0x0022 0x005C 0x0000 0x0007 0x0008 0x000C 0x000A 0x000D 0x0009 0x000B
El tipo de un literal de caracteres es char. Literales de caracteres 'A' '\u0041' '\x0041' '\n'
// // // //
caracter caracter unsigned caracter
sencillo Unicode short hexadecimal de escape: CR+LF
Literales de cadena. C# admite dos formatos de literales de cadena: literales de cadena típicos y literales de cadena textuales. El tipo de un literal de cadena es string.
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
Un literal típico de cadena consta de cero o más caracteres entre comillas dobles y puede incluir secuencias de escape sencillas y secuencias de escape hexadecimales y Unicode. Literales tipicos de cadena "!Hola, mundo!" "!Hola, \t mundo!" ""
// !Hola, mundo! // !Hola, mundo! // La cadena vacia
Un literal de cadena textual consta del carácter @ seguido de un carácter de comillas dobles, cero o más caracteres y un carácter de comillas dobles de cierre. Por ejemplo, @"Hola". En estos literales los caracteres se interpretan de manera literal y no se procesan las secuencias de escape, con la única excepción de la secuencia \". Un literal de cadena textual puede estar en varias líneas. Literales de cadena textuales @"!Hola, \t mundo!" mundo! "Me dijo \"Hola\" y me asustó" y me asustó @"Me dijo ""Hola"" y me asustó" y me asustó "\\\\servidor\\share\\file.txt" \\servidor\share\file.txt @"\\servidor\share\file.txt" \\servidor\share\file.txt @"uno cadena distribuida dos" lineas.
// !Hola, \t // Me dijo "Hola" // Me dijo "Hola" // // //
Esta es una
//
en dos
Literal null. Su único valor es null y su tipo es el tipo null.
Órdenes Delimitadas por punto y coma (;) como en C, C++ y Java. Los bloques { ... } no necesitan punto y coma al final.
Tipos de datos Los tipos de datos ofrecidos por C# al programador forman parte de un sistema unificado en el que todos los tipos de datos (incluidos los definidos por el usuario) derivan, aunque sea de manera implícita, de la clase System.Object. Por herencia dispondrán de todos los miembros definidos en ésta clase, en particular los útiles métodos Equals(), GetHashCode(), GetType() y ToString() que describiremnos más adelante.
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
C# proporciona seguridad con los tipos de datos. C# no admiten ni funciones ni variables globales sino que todo el código y datos han de definirse dentro de definiciones de tipos de datos, lo que reduce problemas por conflictos de nombres y facilita la legibilidad del código. C# incluye mecanismos que permiten asegurar que los accesos a tipos de datos siempre se realicen correctamente: No pueden usarse variables que no hayan sido iniciadas. El tipo asignado restringe los valores que puede almacenar y las operaciones en las que puede intervenir. Siempre se comprueba que los índices empleados para acceder a los elementos de una tabla (vector o matriz) se encuentran en el rango de valores válidos. Sólo se admiten conversiones de tipo entre tipos compatibles y entre aquellos que se hayan definido explícitamente el mecanismo de conversión (En C# puede implementarse la manera en que se realiza la conversión implícita y explícita entre tipos) Los tipos de datos en C# pueden clasificarse en dos grandes categorías: tipos valor tipos referencia y pueden caracterizarse como sigue: Tipos valor La variable contiene un valor El dato se almacena en la pila El dato siempre tiene valor Una asignación copia el valor
Tipos referencia La variable contiene una referencia El dato se almacena en el heap El dato puede no tener valor null Una asignación copia la referencia
int i = 123;
// tipo valor
string s = "Hello world";
// tipo referencia
El comportamiento cuando se copian o modifican objetos de estos tipos es muy diferente.
Tipos valor Los tipos básicos son tipos valor. Si una variable es de un tipo valor contiene únicamente un valor del tipo del que se ha declarado. Los tipos predefinidos de C# son tipos disponibles en la plataforma .NET y que, por comodidad, en C# se emplean usando un alias. En la tabla siguiente enumeramos los tipos simples detallando su nombre completo, su alias, una breve descripción, el número de bytes que ocupan y el rango de valores.
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
Nombre (.NET Framework)
Alias Descripción Bytes con signo Enteros cortos
Tamaño (bytes) 1
-128 ... 127
2
-32.768 ... 32.767
System.Sbyte
sbyte
System.Int16
short
System.Int32
int
Enteros
4
System.Int64
long
Enteros largos
8
Bytes (sin signo) Enteros System.Uint16 ushort cortos (sin signo) Enteros (sin System.UInt32 uint signo) Enteros System.Uint64 ulong largos (sin signo) Reales (7 System.Single float decimales) Reales (15System.Double double 16 decimales) Reales (28System.Decimal decimal 29 decimales) Caracteres System.Char char Unicode Valores System.Boolean bool lógicos System.Byte
byte
Rango
-2.147.483.648 ... 2.147.483.647 -9.223.372.036.854.775.808 ... 9.223.372.036.854.775.807
1
0 ... 255
2
0 ... 65.535
4
0 ... 18.446.744.073.709.551.615
8
0 ... 18.446.744.073.709.551.615
4
±1.5 x 10-45 ... ±3.4 x 10+38
8
±5.0 x 10-324 ... ±1.7 x 10+308
12
±1.0 x 10-28 ... ±7.9 x 10+28
2
Cualquier carácter Unicode
1
true ó false
El comportamiento de los datos de tipos valor es el esperado cuando se inician o reciben un valor por asignación a partir de otro dato de tipo valor (son independientes).
Tipos referencia Si un dato es de un tipo referencia contiene la dirección de la información, en definitiva, una referencia al objeto que contiene los datos y/o métodos. En definitiva, distinguimos: La referencia o un nombre por el que nos referimos al objeto y que utilizamos para manipularlo. El objeto referenciado, que ocupa lugar en memoria (en el heap) y que almacenará el valor efectivo del objeto.
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
En definitiva: la variable y su contenido "lógico" están en posiciones de memoria diferentes. El valor almacenado en una variable de tipo referencia es la dirección de memoria del objeto referenciado (es una referencia) o tiene el valor null (no referencia a nada). Observe que pueden existir dos variables que referencien al mismo objeto (pueden existir dos referencias a la misma zona de memoria). C# proporciona dos tipos referencia predefinidos: object y string. Todos los demás tipos predefinidos son tipos valor. El tipo object es el tipo base del cual derivan todos los tipos básicos predefinidos y los creados por el usuario. Pueden crearse nuevos tipos referencia usando declaraciones de clases (class), interfaces (interface) y delegados (delegate), y nuevos tipos valor usando estructuras struct. Los objetos de las clases creadas por el usuario son siempre de tipo referencia. El operador new permite la creación de instancias de clases definidas por el usuario. new es muy diferente en C# y en C++: En C++ indica que se pide memoria dinámica. En C# indica que se llama al constructor de una clase. El efecto, no obstante, es similar ya que como la variable es de un tipo referencia, al llamar al constructor se aloja memoria en el heap de manera implícita. Considere el siguiente fragmento de código, en el que todas las variables son del mismo tipo: ObjetoDemo). Tipos referencia class ObjetoDemo { public int Valor; } class AppDemoRef { static void Main(string[] args) { ObjetoDemo o1 = new ObjetoDemo(); // new llama a un constructor o1.Valor = 10; ObjetoDemo o2 = new ObjetoDemo(); // new llama a un constructor o2 = o1; // La memoria que ocupaba el objeto refernciado por "o2" // se pierde: actuará el recolector de basura. PintaDatos ("o1", "o2", o1, o2); ObjetoDemo o3 = new ObjetoDemo();// new llama a un constructor o3.Valor = 10; ObjetoDemo o4 = o3; // "o4" contiene la misma
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
direccion de memoria que "o3" o4.Valor = 20; // Igual que hacer o3.Valor = 20; PintaDatos ("o3", "o4", o3, o4); ObjetoDemo a un constructor o5.Valor = ObjetoDemo a un constructor o6.Valor = PintaDatos
o5 = new ObjetoDemo(); // new llama 10; o6 = new ObjetoDemo(); // new llama o5.Valor; ("o5", "o6", o5, o6);
Console.ReadLine(); } static void PintaDatos (string st1, string st2, ObjetoDemo ob1, ObjetoDemo ob2) { Console.Write ("{0} = {1}, {2} = {3} ", st1, ob1.Valor, st2, ob2.Valor); if (ob1==ob2) Console.WriteLine ("{0} == {1}", st1, st2); else Console.WriteLine ("{0} != {1}", st1, st2); } }
El tipo string es un tipo especial de tipo referencia. De hecho, parece más un tipo valor ante la asignación. Observe el ejemplo: string s1 = "Hola"; string s2 = s1;
En este punto s2 referencia al mismo objeto que s1. Sin embargo, cuando el valor de s1 es modificado, por ejemplo con:
s1 = "Adios";
lo que ocurre es que se crea un nuevo objeto string referenciado por s1. De esta forma, s1 contiene "Adios" mientras que s2 mantiene el valor "Hola". Esto es así porque los objetos string son immutables, por lo que, para cambiar lo que referencia una variable string debe crearse un nuevo objeto string.
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
Variables y constantes Variables Una variable permite el almacenamiento de datos en la memoria. Es una abstracción que permite referirnos a una zona de memoria mediante un nombre (su identificador). Todas las variables tienen asociadas un tipo que determina los valores que pueden almacenarse y las operaciones que pueden efectuarse con los datos de ese tipo. Además, el término variable indica que el contenido de esa zona de memoria puede modificarse durante la ejecución del programa. Nombres de variables Los nombres que pueden asignarse a las variables deben regirse por unas normas básicas: Pueden contener letras, dígitos y el caracter de subrayado (_). No pueden empezar con un número: deben comenzar por una letra letra o con el carácter de subrayado (_). Finalmente, recordar que, como identificador que es, el nombre de una variable es sensible a las mayúsculas y no pueden coincidir con una palabra reservada a no ser que tenga el prefijo @, aunque no es una práctica recomendada. Declaración de variables Antes de usar una variable se debe declarar. La declaración de una variable indica al compilador el nombre de la variable y su tipo. Una declaración permite que se pueda reservar memoria para esa variable y restringir el espacio (cantidad de memoria) que requiere, los valores que pueden asignarsele y las operaciones en las que puede intervenir. La sintaxis de una declaración es sencilla: tan sólo hay que especificar el tipo de la variable y el nombre que se le asocia. La declaración debe concluir con el carácter punto y coma. Por ejemplo, si vamos a emplear una variable para guardar en ella el valor del área de un círculo debemos: Darle un nombre significativo: Area Asociarle un tipo: dado que puede tener decimales y no se requiere una gran precisión, bastará con el tipo float.
float Area;
Cuando se van a declarar múltiples variables del mismo tipo no es necesario que cada declaración se haga por separado, pueden agruparse en la misma línea compartiendo el tipo. Por ejemplo, las declaraciones:
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
float Radio; float Area;
pueden simplificarse en una línea:
float Radio, Area;
De la misma manera pueden declararse e inicializarse variables en una sola línea:
int a=1, b=2, c, d=4;
No existe ninguna zona predeterminada en el código para la declaración de variables, la única restricción es que la declaración debe realizarse antes de su uso. No es conveniente abusar de la declaración múltiple de variables en una línea. Desde el punto de vista de la legibilidad es preferible, por regla general, que cada variable se declare separadamente y que la declaración vaya acompañada de un breve comentario: float Radio; // Radio del circulo del cual se calcula el area. float Area; // Area del circulo
Acceso a variables Una variable se usa para asignarle un valor (acceso de escritura) o para utilizar el valor almacenado (acceso de lectura). Una vez declarada una variable debe recibir algún valor (es su misión, después de todo). Este valor lo puede recibir de algún dispositivo (flujo de entrada) o como resultado de evaluar una expresión. La manera más simple de proporcionar un valor a una variable es emplear la instrucción de asignación:
Radio = 10.0F;
En el ejemplo se asigna el valor (literal entero) 10 a la variable Radio. El valor que tuviera almacenado la variable Radio se pierde, quedando fijado a 10. En la misma línea de la declaración puede asignarse un valor a la variable, por lo que declaración e inicialización:
float Radio; // Declaracion Radio = 10.0F; // Inicializacion
pueden simplificarse en una sola línea:
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
float Radio = 10.0F; Inicializacion
// Declaracion e
La manera más simple de leer el valor de una variable es emplearla como parte de una expresión, por ejemplo, en la parte derecha de una instrucción de asignación:
Area = 2 * 3.1416F * Radio * Radio;
La variable Radio (su valor) se emplea en la expresión 2 * 3.1416 * Radio * Radio para calcular el área de un círculo de radio Radio. Una vez calculado el valor de la expresión, éste se asigna a la variable Area. Otra manera de acceder al valor de una variable para lectura es emplearla como el argumento de una instrucción de escritura WriteLine. Por ejemplo, Console.WriteLine(Area);
mostrará en la consola el valor de la variable Area. Leer el valor de una variable no modifica el contenido de la variable. A modo de resumen, un programa que calcula y muestra el área de un círulo de radio 10 es el siguiente: Calculo del área de un círculo (1) using System; class Area1 { static void Main(string[] args) { float Radio = 10.0F; float Area = Area = 2 * 3.1416F * Radio * Radio; Console.WriteLine(Area); Console.ReadLine(); } }
Como puede parecer evidente, emplear una variable que no ha sido declarada produce un error en tiempo de compilación. En C#, además, hay que asignarle un valor antes de utilizarla. Si no se hace se genera un error en tiempo de compilación ya que esta comprobación (igual que ocurre en Java) se efectúa por el compilador.
void f() { int i; Console.WriteLine(i); // Error: uso de la variable local no asignada 'i' }
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
Constantes Una constante se define de manera parecida a una variable: modeliza una zona de memoria que puede almacenar un valor de un tipo determinado. La diferencia reside en que esa zona de memoria (su contenido) no puede modificarse en la ejecución del programa. El valor de la constante se asigna en la declaración. Sintácticamente se especifica que un dato es constante al preceder con la palabra reservada const su declaración. Por ejemplo, para declarar un a constante de tipo float llamada PI y asignarle el valor (constante) 3.1416 se escribirá:
const float PI = 3.1416;
Solo se puede consultar el valor de una constante, nunca se debe intentar modificarlo porque se produciría un error en tiempo de compilación. Por ejemplo, la siguiente instrucción: Area = 2 * PI * Radio * Radio; utiliza la constante PI declarada anteriormente, usando su valor para evaluar una
expresión. Podemos modificar el programa anterior para que utilice la constante PI: Calculo del área de un círculo (2) using System; class Area2 { static void Main(string[] args) { const float PI = 3.1416F; float Radio = 10.0F; float Area = 2 * PI * Radio * Radio; Console.WriteLine(Area); Console.ReadLine(); } }
Ámbito de variables y constantes El ámbito (del inglés scope) de una variable y/o constante indica en qué partes del código es lícito su uso. En C# el ámbito abarca desde el lugar de su declaración hasta donde termina el bloque en el que fue declarada. En el ámbito de una variable no puede declararse otra variable con el mismo nombre (aunque sea en un bloque interno, algo que si está permitido en C++):
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
static void Main(string[] args) { float Radio = 10.0F; ... if (Radio > 0){ float Radio = 20.0F; // Error: No se puede declarar una variable // local denominada 'Radio' en este ámbito. } ... }
Operadores y expresiones Un operador está formado por uno o más caracteres y permite realizar una determinada operación entre uno o más datos y produce un resultado. Es una manera simbólica de expresar una operación sobre unos operandos. C# proporciona un conjunto fijo, suficiente y completo de operadores. El significado de cada operador está perfectamente definido para los tipos predefinidos, aunque algunos de ellos pueden sobrecargarse, es decir, cambiar su significado al aplicarlos a un tipo definido por el usuario. C# dispone de operadores aritméticos, lógicos, relacionales, de manipulación de bits, asignación, para acceso a tablas y objetos, etc. Los operadores pueden presentarse por diferentes criterios, por ejemplo, por su funcionalidad: Categorías Aritméticos Lógicos (booleanos y bit a bit) Concatenación de cadenas Incremento y decremento Desplazamiento Relacionales Asignación Acceso a miembros Acceso por índices Conversión de tipos explícita Conditional Creación de objetos Información de tipos Control de excepciones de desbordamiento Direccionamiento indirecto y dirección
Operadores + - * / % & | ^ ! ~ && || + ++ -<< >> == != < > <= >= = += -= *= /= %= &= |= ^= <<= >>= . [] () ? : new as is sizeof typeof checked unchecked * -> [] &
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
Los operadores aritméticos de C# son los que se emplean comúnmente en otros lenguajes: + (suma), - (resta), * (multiplicación), / (división) y % (módulo o resto de la división). Son operadores binarios y se colocan entre los argumentos sobre los que se aplican, proporcionando un resultado numérico (Por ejemplo, 7+3.5, 66 % 4). Los operadores lógicos proporcionan un resultado de tipo lógico (bool) y los operadores a nivel de bit actúan sobre la representación interna de sus operandos (de tipos enteros) y proporcionan resultados de tipo numérico. Los operadores binarios son: & (operación Y lógica entre argumentos lógicos u operación Y bit a bit entre operandos numéricos), | (operación O, lógica ó bit a bit, dependiendo del tipo de los argumentos) , ^ (O exclusivo, lógico ó bit a bit), && (Y lógico, que evalúa el segundo operando solo cuando es necesario) y || (O lógico, que evalúa el segundo operando solo cuando es necesario). Los operadores unarios son: ! (negación o complemento de un argumento lógico) y ~ (complemento bit a bit de un argumento numérico). El operador + para la concatenación de cadenas es un operador binario. Cuando al menos uno de los operandos es de tipo string este operador actúa uniendo las representaciones de tipo string de los operandos. Operadores de incremento y decremento. El operador de incremento (++) incrementa su operando en 1 mientras que el de decremento (--) decrementa su operando en 1. Puede aparecer antes de su operando: ++v (incremento prefijo) o después: v++ (incremento postfijo). El incremento prefijo hace que el resultado sea el valor del operando después de haber sido incrementado y el postfijo hace que el resultado sea valor del operando antes de haber sido incrementado. Los tipos numéricos y de enumeración poseen operadores de incremento y decremento predefinidos. Los operadores de esplazamiento son operadores binarios. Producen un desplazamiento a nivel de bits de la representación interna del primer operando (de un tipo entero), a la izquierda (<<) o a la derecha (>>) el número de bits especificado por su segundo operando. Los operadores relacionales proporcionan un resultado lógico, dependiendo de si sus argumentos son iguales (==), diferentes (!=), o del orden relativo entre ellos (<, >, <= y >=). La asignación simple (=) almacena el valor del operando situado a su derecha en una variable (posición de memoria) indicada por el operando situado a su izquierda.
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
Los operandos deben ser del mismo tipo o el operando de la derecha se debe poder convertir implícitamente al tipo del operando de la izquierda). El operador de asignación = produce los siguientes resultados: o En tipos simples el funcionamiento es similar al de C++, copia el
contenido de la expresión de la derecha en el objeto que recibe el valor. o En datos struct realiza una copia directa del contenido, como en C++. o En clases se copia la referencia, esto es, la dirección del objeto, provocando que el objeto sea referenciado por más de una referencia. Este comportamiento es distinto al que se produce en C++, en el que se copia el contenido del objeto. Si el objeto contiene estructuras más complejas, C++ requiere normalmente la sobrecarga del operador para personalizar la manera en que se realiza la copia para que cada instancia de la clase (fuente y destino)maqneje su propia zona de memoria. En C# no se permite la sobrecarga del operador = Los otros operadores de esta categoría realizan, además de la asignación otra operación previa a la asignación. Por ejemplo,
a += 22;
equivale a
a = a + 22;
o sea, primero se calcula la expresión a+22 y posteriormente,ese valor se almacena en a. De la misma manera actúan los demás operadores de asignación: -=, *=, /=, %=, &=, |=, ^=, <<= y >>=.
Para asignar instancias de clases (en el sentido clásico de C++) se debe redefinir el método MemberwiseCopy() que es heredado por todas las clases desde System.Object (todas las clases heredan, en última instancia de System.Object). El operador de acceso a miembros es el operador punto (.) y se emplea para acceder a los miembros (componentes) de una clase, estructura o espacio de nombres. El operador de acceso por índices es el tradicional, formado por la pareja de caracteres [ y ]. En C# también se emplea para especificar atributos.
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
El operador de conversión explícita de tipos (casting) es el clásico, formado por la pareja de caracteres ( y ). El operador ternario condicional evalúa una condición lógica y devuelve uno de dos valores. Se utiliza en expresiones de la forma:
cond ? expr1 : expr2
de manera que si cond es verdad, se evalúa expr1 y se devuelve como resultado; si cond es falsa se evalúa expr1 y se devuelve como resultado. Creación de objetos: new ObjetoDemo o1 = new ObjetoDemo() Información de tipos: as is sizeof typeof El operador as se utiliza para realizar conversiones entre tipos compatibles. El operador as se utiliza en expresiones de la forma: <expresion> as type
donde <expresion> es una expresión de un tipo de referencia, y type es un tipo de referencia.
// Ejempo de uso del operador as using System; class Clase1 {} class Clase2 {} public class Demo { public static void Main() { object [] Objetos = new object[6]; Objetos[0] = new Clase1(); Objetos[1] = new Clase1(); Objetos[2] = "Hola"; Objetos[3] = 123; Objetos[4] = 123.4; Objetos[5] = null; for (int i=0; i<Objetos.Length; ++i) { string s = Objetos[i] as string; Console.Write ("{0}:", i); if (s != null) Console.WriteLine ( "'" + s + "'" ); else Console.WriteLine ( "no es string" ); }
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
Console.ReadLine(); } }
El resultado es: 0:no es string 1:no es string 2:'Hola' 3:no es string 4:no es string 5:no es string
El operador is se utiliza para comprobar si el tipo en tiempo de ejecuci贸n de un objeto es compatible con un tipo dado. El operador is se utiliza en expresiones de la forma: <expresion> is type
donde <expresion> es una expresi贸n de un tipo de referencia, y type es un tipo de referencia.
// Ejempo de uso del operador is using System; class Clase1 {} class Clase2 {} public class Demo { public static void Prueba (object o) { Clase1 a; Clase2 b; if (o is Clase1) { Console.WriteLine ("o es de Clase1"); a = (Clase1)o; // trabajar con a } else if (o is Clase2) { Console.WriteLine ("o es de Clase2"); b = (Clase2)o; // trabajar con b } else { Console.WriteLine ("o no es ni de Clase1 ni de Clase2."); }
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
} public static void Main() { Clase1 c1 = new Clase1(); Clase2 c2 = new Clase2(); Prueba (c1); Prueba (c2); Prueba ("Un string"); Console.ReadLine(); } }
El resultado es: o es de Clase1 o es de Clase2 o no es ni de Clase1 ni de Clase2.
El operador typeof se utiliza para obtener el objeto System.Type para un tipo. Una expresi贸n typeof se presenta de la siguiente forma: typeof (tipo)
donde tipo es el tipo cuyo objeto System.Type se desea obtener.
// Ejempo de uso del operador typeof using System; class Clase1 {} class Clase2 {} public class Demo { public static void Prueba (object o) { Clase1 a; Clase2 b; Type t; if (o is Clase1) { a = (Clase1)o; Console.WriteLine ("--> {0}", typeof(Clase1).FullName); t = typeof(Clase1); } else { b = o as Clase2; t = typeof(Clase2); } Console.WriteLine (t.FullName); }
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
public static void Main() { Clase1 c1 = new Clase1(); Clase2 c2 = new Clase2(); Prueba (c1); Prueba (c2); Console.ReadLine(); } }
El resultado es: --> Clase1 Clase1 Clase2
Control de excepciones de desbordamiento: checked y unchecked C# proporciona la posibilidad de realizar operaciones de moldeado (casting) y aritméticas en un contexto verificado. En otras palabras, el entorno de ejecución .NET detecta cualquier situación de desbordamiento y lanza una excepción (OverFlowException) si ésta se manifiesta. En un contexto verificado (checked), si una expresión produce un valor fuera del rango del tipo de destino, el resultado depende de si la expresión es constante o no. Las expresiones constantes producen errores de compilación, mientras que las expresiones no constantes se evalúan en tiempo de ejecución y producen excepciones. Si no se especifican ni checked ni unchecked, las expresiones constantes utilizan la verificación de desbordamiento predeterminada en tiempo de compilación, que es checked. De lo contrario, si la expresión no es constante, la verificación de desbordamiento en tiempo de ejecución depende de otros factores tales como las opciones del compilador y la configuración del entorno. En el siguiente ejemplo, el valor por defecto es unchecked:
using System; class PruebaDesbordamiento { static short x = 32767; static short y = 32767;
// Maximo valor short
public static int UnCh() { int z = unchecked((short)(x + y)); return z; // -2 } public static int UnCh2() {
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
int z = (short)(x + y); // Por defecto es unchecked return z; // -2 } public static void Main() { Console.WriteLine("Valor -unchecked-: {0}", UnCh()); Console.WriteLine("Valor por defecto: {0}", UnCh2()); Console.ReadLine(); } }
El resultado es: Valor -unchecked-: -2 Valor por defecto: -2
Si añadimos la función:
public static int Ch() { int z = checked((short)(x + y)); return z; }
y la llamada: Console.WriteLine("Valor -checked-
: {0}", Ch());
la ejecución del programa provoca el lanzamiento de una excepción System.OverflowException que detiene la ejecución del programa, al no estar controlada. También podrían presentarse por precedencia. En la tabla siguiente los enumeramos de mayor a menor precedencia: Categorías
Operadores Paréntesis: (x) Acceso a miembros: x.y Llamada a métodos: f(x)
Primarios
Acceso con índices: a[x] Post-incremento: x++ Post-decremento: x-Llamada a un constructor: new
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
Consulta de tipo: typeof Control de desbordamiento activo: checked Control de desbordamiento inactivo: unchecked
Valor positivo: + Valor negative: No: ! Unarios
Complemento a nivel de bit: ~ Pre-incremento: ++x Post-decremento: --x Conversi贸n de tipo -cast-: (T)x Multiplicaci贸n: *
Multiplicativos
Divisi贸n: / Resto: % Suma: +
Aditivos
Resta: Desplazamiento de bits a la izquierda: <<
Desplazamiento
Desplazamiento de bits a la derecha: >> Menor: < Mayor: > Menor o igual: <=
Relacionales
Mayor o igual: >= Igualdad o compatibilidad de tipo: is Conversi贸n de tipo: as
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
Igualdad Desigualdad Bitwise AND Bitwise XOR Bitwise OR Logical AND Logical OR Condicional ternario Asignación
== != & ^ | && || ?: =, *=, /=, %=, +=, -=, <<=, >>=, &=, ^=, |=
Asociatividad Como siempre, es mejor utilizar paréntesis para controlar el orden de evaluación
x = y = z
se evalúa como
x = (y = z)
x + y + z
se evalúa como
(x + y) + z
Estructuras de control Las estructuras de control de C# son similares a las de C y C++. La diferencia más notable radica en que la instrucción condicional if y los ciclos while y do están controlados por una expresión lógica (tipo Boolean). Esta restricción hace que las instrucciones sean más seguras al evitar posibles fuentes de error, o al menos, facilitan la legibilidad del código. Por ejemplo, en la siguiente instrucción (válida en C++):
if (a)
la expresión a podría ser una expresión boolean pero también de tipo int, char, float *... y la condición se evalúa como true cuando a es distinto de cero (valor entero 0, carácter 0 ó puntero nulo, en los ejemplos). En C# se clarifica esta ambigüedad y sólo se admiten expresiones lógicas. De esta manera, la instrucción anterior será válida sólo cuando a sea una expresión boolean. A modo de resumen, las características propias de las estructuras de control de C# son: goto no puede saltar dentro de un bloque (da igual, de todas formas no lo
usaremos NUNCA). switch funciona como en Pascal (no como en C). Se añade una nueva estructura de control iterativa: foreach.
Estructuras condicionales
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
if, if-else La estructura condicional tiene la sintaxis clásica, con la diferencia indicada anteriormente acerca del tipo de la expresión. Si debe ejecutar más de una instrucción, se encierran en un bloque, delimitado por las llaves { y }. Si sólo se actúa cuando la condición es cierta:
if (a > 0) 0) { Console.WriteLine ("Positivo"); Console.WriteLine ("Positivo");
if (a >
ContPositivos++; }
Si se actúa cuando la condición es falsa se emplea la palabra reservada else:
if (a > 0) 0) { Console.WriteLine ("Positivo"); Console.WriteLine ("Positivo"); else ContPositivos++; Console.WriteLine ("No Positivo");
if (a >
} else {
Console.WriteLine ("No Positivo"); ContNoPositivos++; }
Puede escribirse una instrucción condicional dentro de otra instrucción condicional, lógicamente:
if (a > 0) { Console.WriteLine ("Positivo"); ContPositivos++; } else { if (a < 0) { Console.WriteLine ("Negativo"); ContNegativos++; } else { Console.WriteLine ("Cero"); ContCeros++; } }
La concordancia entre if y else se establece de manera sencilla: cada else se asocia al último if que no tenga asociado un bloque else.
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
switch La estructura de selección múltiple switch funciona sobre cualquier tipo predefinido (incluyendo string) o enumerado (enum) y debe indicar explícitamente cómo terminar cada caso (generalmente, con break en situaciones "normales" ó throw en situaciones "anormales", aunque es posible -pero no recomendable- emplear goto case ó return ):
using System; class HolaMundoSwitch { public static void Main(String[] args) { if (args.Length > 0) switch(args[0]) { case "José": Console.WriteLine("Hola José. Buenos días"); break; case "Paco": Console.WriteLine("Hola Paco. Me alegro de verte"); break; default: Console.WriteLine("Hola {0}", args[0]); break; } else Console.WriteLine("Hola Mundo"); } }
Especificar los parámetros al programa en Visual Studio: Utilizar el explorador de soluciones para configurar las propiedades del proyecto.
Un ejemplo que usa un datos string para controlar la selección:
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
using System; namespace ConsoleApplication14 { class Class1 { static int Test(string label) { int res; switch(label) { case null: goto case "A"; // idem case "B" o case "A" case "B": case "C": res = 1; break; case "A": res = 2; break; default: res = 0; break; } return res; } static void Main(string[] args) { Console.WriteLine (Test("")); Console.WriteLine (Test("A")); Console.WriteLine (Test("B")); Console.WriteLine (Test("C")); Console.WriteLine (Test("?")); Console.ReadLine(); }
// // // // //
0 2 1 1 0
} }
Estructuras repetitivas Las estructuras repetitivas de C# (while, do...while, for) no presentan grandes diferencias respecto a las de otros lenguajes, como C++. La aportaci贸n fundamental es la de la estructura iterativa en colecciones foreach. while
int i = 0; while (i < 5) { ... i++; }
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
do...while
int i = 0; do { ... i++; } while (i < 5);
for
int i; for (i=0; i < 5; i++) { ... }
foreach Un ciclo foreach itera seleccionando todos los miembros de un vector, matriz u otra colección sin que se requiera explicitar los índices que permiten acceder a los miembros. El siguiente ejemplo muestra todos los argumentos recibidos por el programa cuando se invoca su ejecución (argumentos en la línea de órdenes):
public static void Main(string[] args) { foreach (string s in args) Console.WriteLine(s); }
El vector (la colección) que se utiliza para iterar es args. Es un vector de datos string. El ciclo foreach realiza tantas iteraciones como cadenas contenga el vector args, y en cada iteración toma una y la procesa a través de la variable de control s. El ejemplo siguiente realiza la misma función:
public static void Main(string[] args) { for (int i=0; i < args.Lenght; i++) Console.WriteLine(args[i]); }
El ciclo foreach proporciona acceso de sólo lectura a los elementos de la colección sobre la que se itera. Por ejemplo, el código de la izquierda no compilará, aunque el de la derecha sí lo hará (v es un vector de int):
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
foreach (int i in v) v.Length; i++) i *= 2;
for (int i=0; i < v[i] *= 2;
El ciclo foreach puede emplearse en vectores y colecciones. Una colecci贸n es una clase que implementa el interfaz IEnumerable. Sobre las colecciones dicutiremos m谩s adelante.
Saltos goto Aunque el lenguaje lo permita, nunca escribiremos algo como lo que aparece a continuaci贸n:
using System; namespace DemoGoto { class MainClass { static void Busca(int val, int[,] vector, out int fil, out int col) { int i, j; for (i = 0; i < vector.GetLength(0); i++) for (j = 0; j < vector.GetLength(1); j++) if (vector[i, j] == val) goto OK; throw new InvalidOperationException("Valor no encontrado"); OK: fil = i; col = j; } static void Main(string[] args) { int [,] coleccion = new int [2,3] {{1,0,4},{3,2,5}}; int f,c; int valor = Convert.ToInt32(args[0]); Busca (valor, coleccion, out f, out c); Console.WriteLine ("El valor {0} esta en [{1},{2}]", valor,f,c); Console.ReadLine(); } } }
break
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
Lo usaremos únicamente en sentencias switch. continue Mejor no lo usamos. return Procuraremos emplearlo sólo al final de un método para facilitar la legibilidad del código.
E/S básica Las operaciones de entrada y salida tienen como objetivo permitir que el usuario pueda introducir información al programa (operaciones de entrada) y que pueda obtener información de éste (operaciones de salida). En definitiva, tratan de la comunicación entre el usuario y el programa. La manera más simple de comunicación es mediante la consola. La consola ha sido el modo tradicional de comunicación entre los programas y el usuario por su simplicidad. Las aplicaciones basadas en ventanas resultan mucho más atractivas y cómodas para el usuario y es éste, sin duda, el tipo de comunicación que deberemos emplear para productos finales. Los programas que no requieran una mucha interacción con el usuario, no obstante, se construyen y se ponen en explotación mucho más rápidamente si se utiliza la consola como medio de comunicación.
Aplicaciones en modo consola Estas aplicaciones emplean la consola para representar las secuencias de entrada, salida (y error) estándar. Una aplicación de consola se crea en Visual Studio .NET seleccionando Archivo, Nuevo y Proyecto. Cuando aparece la ventana Nuevo proyecto se selecciona Proyectos de Visual C# y Aplicación de consola:
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
El acceso a la consola lo facilita la clase Console, declarada en el espacio de nombres System. Esa clase proporciona la compatibilidad básica para aplicaciones que leen y escriben caracteres en la consola. No es necesario realizar ninguna acción para poder obtener datos de la consola a partir de la entrada estándar (teclado) o presentarlos en la salida estándar (consola) ya que estos flujos (junto con el del error estándar) se asocian a la consola de manera automática, como ocurre en C++, por ejemplo, con cin, cout y cerr. Los métodos básicos de la clase Console son WriteLine y ReadLine, junto con sus variantes Write y Read: WriteLine escribe una línea en la salida estándar, entendiendo que escribe el terminador de línea actual (por defecto la cadena "\r\n").
La versión más simple de este método recibe un único argumento (una cadena) cuyo valor es el que se muestra: Console.WriteLine ("!Hola, " + "mundo!"); // Escribe: !Hola, mundo!
Otra versión permite mostrar variables de diferentes tipos (sin necesidad de convertirlas a string. La llamada tiene un número indeterminado de argumentos: el primero es una cadena de formato en la que las variables a mostrar se indican con {0}, {1}, etc. y a continuación se enumeran las variables a mostrar, entendiendo que la primera se "casa" con {0}, la segunda con {1}, etc. Esta manera de mostrar los datos recuerda a la instrucción printf de C, que cayó en desuso con C++ ...
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
int TuEdad = 25; string TuNombre = "Pepe"; Console.WriteLine ("Tienes {0} años, {1}.", TuEdad, TuNombre); // Escribe: Tienes 25 años, Pepe.
El método Write hace lo mismo que WriteLine aunque no escribe el terminador de línea. ReadLine lee la siguiente línea de caracteres de la secuencia de entrada estándar (el teclado, por defecto), eliminando del buffer de entrada el terminador de línea. Devuelve la cadena leida, que no contiene el carácter o los caracteres de terminación. Read lee el siguiente carácter de la secuencia de entrada estándar y devuelve un valor de tipo int. La lectura se realiza del buffer de entrada y no se termina (no devuelve ningún valor) hasta que se encuentra al caracter de terminación (cuando el usuario presionó la tecla ENTER). Si existen datos disponibles en el buffer, la secuencia de entrada contiene los datos introducidos por el usuario, seguidos del carácter de terminación. Veamos un sencillo ejemplo sobre el uso de estos métodos. E/S simple using System; class PideNombre { static void Main(string[] args) { Console.Write ("Introduzca su nombre: "); // 1 string nombre = Console.ReadLine(); // 2 Console.WriteLine ("Su nombre es: " + nombre); // 3 Console.ReadLine(); // 4 } }
La instrucción 1 muestra la cadena Introduzca su nombre: pero no avanza a la siguiente línea de la consola, por lo que cuando se ejecuta la instrucción 2 lo que escribe el usuario se muestra a continuación, en la misma línea. La cadena que escribe el usuario se guarda en la variable nombre y se elimina del buffer de entrada el terminador de línea. Cuando se valida la entrada (al pulsar ENTER) se avanza a la siguiente línea. La instrucción 3 muestra una cadena, resultado de concatenar un literal y la cadena introducida por el usuario. Finalmente, la instrucción 4 es necesaria para detener la ejecución del programa (realmente, la finalización del mismo) hasta que el usuario pulse ENTER. Observar que aunque el método Readline devuelva una cadena, éste valor devuelto no es usado. En la siguiente figura mostramos dos ejemplos de ejecución.
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
Aplicaciones Windows Una aplicación basada en ventanas (aplicación Windows, en lo que sigue) utilizan ventanas y componentes específicos para interactuar con el usuario. Las peticiones de datos se realizan con componentes de entrada de texto (por ejemplo, con un TextBox) o mediante la selección en una lista de posibilidades (por ejemplo, con un ComboBox). Las salidas pueden realizarse de múltiples maneras, empleando componentes Label, ventanas de mensajes MessageBox, etc. Por ejemplo, en la figura siguiente mostramos una aplicación que responde mostrando una ventana de mensaje (MessageBox) cuando se pincha sobre el botón titulado Saludo. Basta con ejecutar este código cada vez que se pinche en dicho botón: MessageBox.Show ("¡Hola, mundo!", "Un saludo típico");
(en realidad, System.Windows.Forms.MessageBox.Show (...);)
Una aplicación más compleja podría pedir el nombre del usuario en un componente TextBox y mostrarlo empleando un componente Label cuando se pincha en el botón titulado Saludo:
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
Una aplicación de ventanas se crea fácilmente en Visual Studio .NET seleccionando Archivo, Nuevo y Proyecto. En la ventana Nuevo proyecto se selecciona ahora Proyectos de Visual C# y Aplicación para Windows.
Desarrollo Profesional de Aplicaciones Tipos de datos Tipos básicos El sistema unificado de tipos. El tipo Object Cadenas de caracteres Vectores y matrices Estructuras Enumeraciones Cuando definimos un objeto debemos especificar su tipo. El tipo determina qué valores puede almacenar ese objeto (clase y rango) y las operaciones que pueden efectuarse con él. Como cualquier lenguaje de programación, C# proporciona una serie de tipos predefinidos (int, byte, char, string, ...) y mecanismos para que el usuario cree sus propios tipos (class y struct).
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
La estructura de tipos de C# es una gran novedad ya que establece una relación jerárquica entre éstos, de manera que todos los tipos son clases y se construyen por herencia de la clase base Objet. Esta particularidad hace que la creación y gestión de tipos de datos en C# sea una de las principales novedades y potencialidades del lenguaje.
Tipos básicos Los tipos de datos básicos son los tipos de datos más comúnmente utilizados en programación. Los tipos predefinidos en C# están definidos en el espacio de nombres System, que es el espacio de nombres más numeroso (e importante) de la plataforma .NET. Por ejemplo, para representar números enteros de 32 bits con signo se utiliza el tipo de dato System.Int32 y a la hora de crear un objeto a de este tipo que represente el valor 2 se usa la siguiente sintaxis:
System.Int32 a = 2;
Al ser un tipo valor no se utiliza el operador new para crear objetos System.Int32, sino que directamente se indica el literal que representa el valor a crear, con lo que la sintaxis necesaria para crear entero de este tipo se reduce considerablemente. Es más, dado lo frecuente que es el uso de este tipo también se ha predefinido en C# el alias int para el mismo, por lo que la definición de variable anterior queda así de compacta:
int a = 2; System.Int32 no es el único tipo de dato básico incluido en C#. En el espacio de nombres System se han incluido los siguientes tipos:
Tipo en System Características System.Sbyte entero, 1 byte con signo System.Byte entero, 1 byte sin signo System.Short entero, 2 bytes con signo System.UShort entero, 2 bytes sin signo System.Int32 entero, 4 bytes con signo System.UInt32 entero, 4 bytes sin signo System.Int64 entero, 8 bytes con signo System.ULong64 entero, 8 bytes sin signo System.Single real, IEEE 754, 32 bits System.Double real, IEEE 754, 64 bits real, 128 bits (28 dígitos decimal System.Decimal significativos) bool System.Boolean (Verdad/Falso) 1 byte C# sbyte byte short ushort int uint long ulong float double
Símbolo
U L UL F D M
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
char
System.Char
string
System.String
object System.Object
Carácter Unicode, 2 bytes ´´ Cadenas de caracteres "" Unicode Cualquier objeto (ningún tipo concreto)
Los tipos están definidos de manera muy precisa y no son dependientes del compilador o de la plataforma en la que se usan. La tabla anterior incorpora la columna Símbolo para indicar cómo debe interpretarse un literal. Por ejemplo, 28UL debe interpretarse como un entero largo sin signo (ulong). Todos los tipos enumerados son tipos valor, excepto string (que no debe confundirse con un vector de caracteres) y Object que son tipos referencia. Recuerde, no obstante, que los datos string se comportaban como un tipo valor ante la asgnación.
El sistema unificado de tipos. El tipo Object En C# desaparecen las variables y funciones globales: todo el código y todos los datos de una aplicación forman parte de objetos que encapsulan datos y código (como ejemplo, recuerde cómo Main() es un método de una clase). Como en otros lenguajes orientados a objetos, en C# un tipo puede incluir datos y métodos. De hecho, hasta los tipos básicos predefinidos incluyen métodos, que, como veremos, heredan de la clase base object, a partir de la cual se construyen implícita o explícitamente todos los tipos de datos. Por ejemplo: int i = 10; string c = i.ToString();
e incluso: string c = 10.ToString();
Esta manera por la que podemos manipular los datos básicos refleja la íntima relación entre C# y la biblioteca de clase de .NET. De hecho, C# compila sus tipos básicos asociándolos a sus correspondientes en .NET; por ejemplo, hace corresponder al tipo string) con la clase System.String, al tipo int) con la clase System.Int32, etc. Así, se confirma que todo es un objeto en C# (por si aún había alguna duda). Aunque no comentaremos todos los métodos disponibles para los tipos básicos, destacaremos algunos de ellos. Todos los tipos tienen un método ToString() que devuelve una cadena (string) que representa su valor textual. char tiene propiedades acerca de su contenido (IsLetter, IsNumber, etc.) además de métodos para realizar conversiones (ToUpper(), ToLower(), etc.) El tipo básico string dispone, como puede suponerse, de muchos métodos específicos, aún más que la clase string de la biblioteca estándar de C++.
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
Algunos métodos estáticos y propiedades particularmente interesantes son: Los tipos numéricos enteros tipos tienen las propiedades MinValue y MaxValue (p.e. int.MaxValue). Los tipos float y double tienen la propiedad Epsilon, que indica el mínimo valor positivo que puede representarse en un dato de su tipo (p.e. float.Epsilon). Los tipos float y double tienen definidos los valores NaN (no es un número, no está definido), PositiveInfinite y NegativeInfinity, valores que son pueden ser devueltos al realizar ciertos cálculos. Muchos tipos, incluyendo todos los tipos numéricos, proporcionan el método Parse() que permite la conversión desde un dato string (p.e. double d = double.Parse("20.5"))
Conversiones de tipos Una conversión de tipo (casting) puede ser implícita o explícita. Implícitas Ocurren automáticamente Siempre tienen éxito No se pierde información int x = 123456; long y = x; short z = (short) x; float f1 = 40.0F; long l1 = (long) f1; redondeo) short s1 = (short) l1; desbordamiento) int i1 = s1; uint i2 = (uint) i1; error por signo)
Explícitas Requieren un casting Pueden fallar Se puede perder información
// implicita // explicita (riesgo)
// explicita (riesgo por // explicita (riesgo por // implicita, no hay riesgo // explicita (riesgo de
En C# las conversiones de tipo (tanto implícitas como explícitas) en las que intervengan tipos definidos por el usuario pueden definirse y particularizarse. Recordemos que pueden emplearse los operadores checked y unchecked para realizar conversiones (y operaciones aritméticas) en un contexto verificado: el entorno de ejecución .NET detecta cualquier situación de desbordamiento y lanza una excepción OverFlowException si ésta se manifiesta.
El tipo object Por el sistema unificado de tipos de C#, todo es un objeto. C# tiene predefinido un tipo referencia llamado object y cualquier tipo (valor o referencia, predefinido o definido por el usuario) es en última instancia, de tipo object (con otras palabras puede decirse que hereda todas las características de ese tipo).
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
El tipo object se basa en System.Object de .NET Framework. Las variables de tipo object pueden recibir valores de cualquier tipo. Todos los tipos de datos, predefinidos y definidos por el usuario, heredan de la clase System.Object. La clase Object es la superclase fundamental de todas las clases de .NET Framework; la raíz de la jerarquía de tipos.
Normalmente, los lenguajes no precisan una clase para declarar la herencia de Object porque está implícita. Por ejemplo, dada la siguiente declaración: object o;
todas estas instrucciones son válidas: o o o o o
= = = = =
10; // int "Una cadena para el objeto"; // cadena 3.1415926; // double new int [24]; // vector de int false; // boolean
Dado que todas las clases de .NET Framework se derivan de Object, todos los métodos definidos en la clase Object están disponibles en todos los objetos del sistema. Las clases derivadas pueden reemplazar, y de hecho reemplazan, algunos de estos métodos, entre los que se incluyen los siguientes: public void Object() Inicializa una nueva instancia de la clase Object. Los constructores llaman a este constructor en clases derivadas,
pero éste también puede utilizarse para crear una instancia de la clase Object directamente. public bool Equals(object obj) Determina si el objeto especificado es igual al objeto actual. protected void Finalize() Realiza operaciones de limpieza antes de que un objeto sea reclamado automáticamente por el recolector de elementos no utilizados. public int GetHashCode() Sirve como función hash para un tipo concreto, apropiado para su utilización en algoritmos de hash y estructuras de datos como las tablas hash. public string ToString() Devuelve un objeto string que representa al objeto actual. Se emplea para crear una cadena de texto legible para el usuario que describe una instancia de la clase. La implementación predeterminada devuelve el nombre completo del tipo del objeto Object. En el último ejemplo, si cada vez que asignamos un valor a o ejecutamos Console.WriteLine (o.ToString()) o simplemente Console.WriteLine (o); obtendremos este resultado:
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
10 Una cadena para el objeto 3,1415926 System.Int32[] False public Type GetType() Obtiene el objeto Type que representa el tipo
exacto en tiempo de ejecución de la instancia actual. En el último ejemplo, si cada vez que asignamos un valor a o ejecutamos Console.WriteLine (o.getType()) obtendremos este resultado: System.Int32 System.String System.Double System.Int32[] System.Boolean protected object MemberwiseClone() Crea una copia superficial del objeto Object actual. No se puede reemplazar este método; una clase derivada debe implementar la interfaz ICloneable si una copia superficial no es apropiada. MemberwiseClone() está protegido y, por
tanto, sólo es accesible a través de esta clase o de una clase derivada. Una copia superficial crea una nueva instancia del mismo tipo que el objeto original y, después, copia los campos no estáticos del objeto original. Si el campo es un tipo de valor, se realiza una copia bit a bit del campo. Si el campo es un tipo de referencia, la referencia se copia, pero no se copia el objeto al que se hace referencia; por lo tanto, la referencia del objeto original y la referencia del punto del duplicado apuntan al mismo objeto. Por el contrario, una copia profunda de un objeto duplica todo aquello a lo que hacen referencia los campos del objeto directa o indirectamente.
Polimorfismo -boxing y unboxingBoxing (y su operación inversa, unboxing) permiten tratar a los tipos valor como objetos (tipo referencia). Los tipos de valor, incluidos los struct y los predefinidos, como int, se pueden convertir al tipo object (boxing) y desde el tipo object (unboxing). La posibilidad de realizar boxing permite construir funciones polimórficas: pueden realizar una operación sobre un objeto sin conocer su tipo concreto.
void Polim(object o) { Console.WriteLine(o.ToString()); } ... Polim(42); Polim("abcd"); Polim(12.345678901234M); Polim(new Point(23,45));
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
Boxing Boxing es una conversión implícita de un tipo valor al tipo object. Cuando se realiza boxing de un valor, se asigna una instancia de objeto y se copia el valor en el nuevo objeto. Por ejemplo, considere la siguiente declaración de una variable de tipo de valor: int i = 123;
La siguiente instrucción aplica implícitamente la operación de boxing sobre la variable i: object o = i;
El resultado de esta instrucción es crear un objeto o en la pila que hace referencia a un valor del tipo int alojado en el heap. Este valor es una copia del valor del tipo de valor asignado a la variable i. La diferencia entre las dos variables, i y o se muestra en la siguiente figura:
En definitiva, el efecto del boxing es el de cualquier otro tipo de casting pero: el contenido de la variable se copia al heap se crea una referencia a ésta copia Aunque no es necesario, también es posible realizar el boxing explícitamente como en el siguiente ejemplo: int i = 123; object o = (object) i;
El siguiente ejemplo convierte una variable entera i a un objeto o mediante boxing. A continuación, el valor almacenado en la variable i se cambia de 123 a 456. El ejemplo muestra que el objeto mantiene la copia original del contenido, 123.
// Boxing de una variable int using System; class TestBoxing { public static void Main() { int i = 123; object o = i; // boxing implicito i = 456; // Modifica el valor de i Console.WriteLine("Valor (tipo valor) = {0}", i);
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
Console.WriteLine("Valor (tipo object)= {0}", o); } }
El resultado de su ejecución es: Valor (tipo valor) = 456 Valor (tipo object)= 123
Unboxing Una vez que se ha hecho boxing sobre un dato y disponemos de un object no puede hacerse demasiado con él ya que no pueden emplearse métodos o propiedades del tipo original: el compilador no puede conocer el tipo original sobre el que se hizo boxing. string s1 = "Hola"; object o = s1; // boxing ... if (o.Lenght > 0) // ERROR
Una vez que se ha hecho boxing puede deshacerse la conversión (unboxing) haciendo casting explícito al tipo de dato inicial. string s2 = (string) o;
// unboxing
Afortunadamente es posible conocer el tipo, de manera que si la conversión no es posible se lanza una excepción. Pueden utilizarse los operadores is y as para determinar la corrección de la conversión: string s2; if (o is string) s2 = (string) o;
// unboxing
o alternativamente: string s2 = o as string; // conversion if (s2 != null) // OK, la conversion funciono
Conclusiones Ventajas del sistema unificado de tipos: las colecciones funcionan sobre cualquier tipo.
Hashtable t = new Hashtable(); t.Add(0, "zero"); t.Add(1, "one"); t.Add(2, "two"); string s = string.Format("Your total was {0} on {1}", total, date);
Desventajas del sistema unificado de tipos: Eficiencia.
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
La necesidad de utilizar boxing disminuirá cuando el CLR permita genéricos (algo similar a los templates en C++).
Cadenas de caracteres Una cadena de caracteres no es más que una secuencia de caracteres Unicode. En C# se representan mediante objetos del tipo string, que no es más que un alias del tipo System.String incluido en la BCL. El tipo string es un tipo referencia. Representa una serie de caracteres inmutable. Se dice que una instancia de String es inmutable porque no se puede modificar su valor una vez creada. Los métodos que aparentemente modifican una cadena devuelven en realidad una cadena nueva que contiene la modificación. Recuerde la particularidad de este tipo sobre el operador de asignación: su funcionamiento es como si fuera un tipo valor. Este es, realmente, el funcionamiento obtenido con el método Copy(). Si no se desea obtener una copia (independiente) sino una copia en el sentido de un tipo valor emplee el método Clone(). Puede accederse a cada uno de los caracteres de la cadena mediante índice, como ocurre habitualmente en otros lenguajes, siendo la primera posición asociada al índice cero. Este acceso, no obstante, sólo está permitido para lectura. string s = "!!Hola, mundo!!";; for (int i=0; i < s.Length; i++) Console.Write (s[i]);
Este ejemplo muestra todos los caracteres que componen la cadena, uno a uno. El ciclo realiza tantas iteraciones como número de caracteres forman la cadena (su longitud) que se consulta usando la propiedad Length. Por definición, un objeto String, incluida la cadena vacía (""), es mayor que una referencia nula y dos referencias nulas son iguales entre sí. El carácter null se define como el hexadecimal 0x00. Puede consultarse si una cadena es vacía empleando la propiedad estática (sólo lectura) Empty: el valor de este campo es la cadena de longitud cero o cadena vacía, "". Una cadena vacía no es igual que una cadena cuyo valor sea null. Los procedimientos de comparación y de búsqueda distinguen mayúsculas de minúsculas de forma predeterminada. Pueden emplearse los métodos Compare() y Equals() para realizar referencias combinadas y comparaciones entre valores de instancias de Object y String. Los operadores relacionales == y != se implementan con el método Equals(). El método Concat() concatena una o más instancias de String o las representaciones de tipo String de los valores de una o más instancias de Object. El operador + está sobrecargado para realizar la concatenación. Por ejemplo, el siguiente código: string s1 = "Esto es una cadena... como en C++"; string s2 = "Esto es una cadena... "; string s3 = "como en C++";
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
string s4 = s2 + s3; string s5 = String.Concat(s2, s3); Console.WriteLine ("s1 = {0}", s1); Console.WriteLine ("s4 = {0}", s4); Console.WriteLine ("s5 = {0}", s5); if ((s1 == s4) && (s1.Equals(s5))) Console.WriteLine ("s1 == s4 == s5");
produce este resultado: s1 s4 s5 s1
= Esto es una cadena... como en C++ = Esto es una cadena... como en C++ = Esto es una cadena... como en C++ == s4 == s5
Un método particularmente útil es Split(), que devuelve un vector de cadenas resultante de "partir" la cadena sobre la que se aplica en palabras: string linea; string [] palabras; ... palabras = linea.Split (null); // null indica dividir por espacios
En definitiva, la clase String incluye numerosos métodos, que pueden emplease para: Realizar búsquedas en cadenas de caracteres: IndexOf(), LastIndexOf(), StartsWith(), EndsWith()
Eliminar e insertar espacios en blanco: Trim(), PadLeft(), PadRight()
Manipular subcadenas: Insert(), Remove(), Replace(), Substring(), Join()
Modificar cadenas de caracteres: ToLower(), ToUpper(), Format() (al estilo del printf de C, pero seguro).
Vectores y matrices Un vector (matriz) en C# es radicalmente diferente a un vector (matriz) en C++: más que una colección de variables que comparten un nombre y accesibles por índice, en C# se trata de una instancia de la clase System.Array, y en consecuencia se trata de una colección que se almacena en el heap y que está bajo el control del gestor de memoria. Todas las tablas que definamos, sea cual sea el tipo de elementos que contengan, son objetos que derivan de System.Array. Ese espacio de nombres proporciona métodos para la creación, manipulación, búsqueda y ordenación de matrices, por lo tanto, sirve como clase base para todas las matrices de la CLR (Common Language Runtime). En C# las tablas pueden ser multidimensionales, se accede a los elementos por índice, siendo el índice inicial de cada dimensión 0. Siempre se comprueba que se esté accediendo dentro de los límites. Si se intenta acceder a un elemento de un vector (matriz) especificando un índice fuera del rango, se detecta en tiempo de ejecución y se lanza una excepción IndexOutOfBoundsException.
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
La sintaxis es ligeramente distinta a la del C++ porque las tablas son objetos de tipo referencia:
double [] array; referencia
// Declara un a
// (no se instancia ningún objeto) array = new double[10]; // Instancia un objeto de la clase // System.Array y le asigna 10 casillas.
que combinadas resulta en (lo habitual):
double [] array = new double[10];
El tamaño del vector se determina cuando se instancia, no es parte de la declaración. string [] texto; // OK string [10] texto; // Error
La declaración emplea los paréntesis vacíos [ ] entre el tipo y el nombre para determinar el número de dimensiones (rango). string string string string ......
[] [,] [,,] [,,,]
Mat1D; Mat2D; Mat3D; Mat4D;
// // // //
1 2 3 4
dimension dimensiones dimensiones dimensiones
En C# el rango es parte del tipo (es obligatorio en la declaración). El número de elementos no lo es (está asociado a la instancia concreta). Otros ejemplos de declaración de vectores:
string[] a = new string[10]; // "a" es un vector de 10 cadenas int[] primes = new int[9]; // "primes" es un vector de 9 enteros
Un vector puede inicializarse a la misma vez que se declara. Las tres definiciones siguientes son equivalentes:
int[] prime1 = new int[10] {1,2,3,5,7,11,13,17,19,23}; int[] prime2 = new int[] {1,2,3,5,7,11,13,17,19,23}; int[] prime3 = {1,2,3,5,7,11,13,17,19,23};
Los vectores pueden dimensionarse dinámicamente (en tiempo de ejecución). Por ejemplo, el siguiente código:
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
Console.Write ("Num. casillas: "); string strTam = Console.ReadLine(); int tam = Convert.ToInt32(strTam); int[] VecDin = new int[tam]; for (int i=0; i<tam; i++) VecDin[i]=i; Console.Write ("Contenido de VecDin = "); foreach (int i in VecDin) Console.Write(i + ", "); Console.ReadLine();
produce este resultado: Num. casillas: 6 Contenido de VecDin = 0, 1, 2, 3, 4, 5,
Esta facilidad es una gran ventaja, aunque una vez que el constructor actúa y se crea una instancia de la clase System.Array no es posible redimensionarlo. Si se desea una estructura de datos con esta funcionalidad debe emplearse una estructura colección disponible en System.Collections (por elemplo, System.Collections.ArrayList). En el siguiente ejemplo observe cómo el vector palabras se declara como un vector de string. Se instancia y se inicia después de la ejecución del método Split(), que devuelve un vector de string.
string frase = "Esto es una prueba de particion"; string [] palabras; // vector de cadenas (no tiene tamaño asignado) palabras = frase.Split (null); Console.WriteLine ("Frase = {0}", frase); Console.WriteLine ("Hay = {0} palabras", palabras.Length); for (int i=0; i < palabras.Length; i++) Console.WriteLine (" Palabra {0} = {1}", i, palabras[i]);
El resultado es: Frase = Esto es una prueba de particion Hay = 6 palabras Palabra 0 = Esto Palabra 1 = es Palabra 2 = una Palabra 3 = prueba Palabra 4 = de Palabra 5 = particion
Matrices
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
Las diferencias son importantes respecto a C++ ya que C# permite tanto matrices rectangulares (todas las filas tienen el mismo número de columnas) como matrices dentadas o a jirones.
int [,] array2D; referencia
// Declara un a
// ningún objeto) array2D = new int [2,3]; // de la clase // asigna 6 casillas // columnas cada una)
(no se instancia Instancia un objeto System.Array y le (2 filas con 3
que combinadas (y con inicialización) podría quedar:
int [,] array2D = new int [2,3] {{1,0,4}, {3,2,5}};
El número de dimensiones puede ser, lógicamente, mayor que dos:
int [,,] array3D = new int [2,3,2];
El acceso se realiza con el operador habitual [ ], aunque el recorrido se simplica y clarifica en C# con el ciclo foreach:
for (int i= 0; i< vector1.Length; i++) vector1[i] = vector2[i]; ... foreach (float valor in vector2) Console.Wtite (valor);
Una matriz dentada no es más que una tabla cuyos elementos son a su vez tablas, pudiéndose así anidar cualquier número de tablas. Cada tabla puede tener un número propio de casillas. En el siguiente ejemplo se pide el número de filas, y para cada fila, el número de casillas de ésa.
Console.WriteLine ("Introduzca las dimensiones: "); // Peticion de numero de filas (num. vectores) Console.Write ("Num. Filas: "); string strFils = Console.ReadLine(); int Fils = Convert.ToInt32(strFils);
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
// Declaracion de la tabla dentada: el numero de // casillas de cada fila es deconocido. int[][] TablaDentada = new int[Fils][]; // Peticion del numero de columnas de cada vector for (int f=0; f<Fils; f++) { Console.Write ("Num. Cols. de fila {0}: ", f); string strCols = Console.ReadLine(); int Cols = Convert.ToInt32(strCols); // Peticion de memoria para cada fila TablaDentada[f] = new int[Cols]; } // Rellenar todas las casillas de la tabla dentada for (int f=0; f<TablaDentada.Length; f++) for (int c=0; c<TablaDentada[f].Length; c++) TablaDentada[f][c]=((f+1)*10)+(c+1); // Mostrar resultado Console.WriteLine ("Contenido de la matriz: "); for (int f=0; f<TablaDentada.Length; f++) { for (int c=0; c<TablaDentada[f].Length; c++) Console.Write (TablaDentada[f][c] + " "); Console.WriteLine(); } Console.ReadLine();
El resultado es: Introduzca las dimensiones: Num. Filas: 4 Num. Cols. de fila 0: 2 Num. Cols. de fila 1: 5 Num. Cols. de fila 2: 3 Num. Cols. de fila 3: 7 Contenido de la matriz: 11 12 21 22 23 24 25 31 32 33 41 42 43 44 45 46 47
Estructuras Una estructura (struct) se emplea para definir nuevos tipos de datos. Los nuevos tipos así definidos son tipos valor (se almacenan en la pila). La sintaxis para la definición de estructuras es similar a la empleada para las clases (se emplea la palabra reservada struct en lugar de class). No obstante, La herencia y aspectos relacionados (p.e. métodos virtuales, métodos abstractos) no se admiten en los struct.
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
No se puede especificar (explícitamente) un constructor sin parámetros. Los datos struct tienen un constructor sin parámetros predefinido y no puede reemplazarse, sólo se permiten constructores con parámetros. En este sentido son diferentes a los struct en C++. En el caso de especificar algún constructor con parámetros deberíamos asegurarnos de iniciar todos los campos. En el siguiente ejemplo se proporciona un único constructor con parámetros:
public struct Point { public int x, y; public Point(int x, int y) { this.x = x; this.y = y; } public string Valor() { return ("[" + this.x + "," + this.y + "]"); } }
Sobre esta declaración de tipo Point, observe estas declaraciones de datos Point: Point p = new Point(2,5); Point p2 = new Point();
Ambas crean una instancia de la clase Point en la pila y asigna los valores oportunos a los campos del struct con el constructor: En el primer caso actúa el constructor suministrado en la implementación del tipo. En el segundo actúa el constructor por defecto, que inicia los campos numéricos a cero, y los de tipo referencia a null. Puede comprobarse fácilmente: Console.WriteLine ("p = " + p.Valor()); Console.WriteLine ("p2 = " + p2.Valor());
produce como resultado: p = [2,5] p2 = [0,0]
En cambio, la siguiente declaración: Point p3;
crea una instancia de la clase Point en la pila sin iniciar (cuidado: no inicia p3 a null, no es un tipo referencia). Por lo tanto, la instrucción: Console.WriteLine ("p3 = " + p3.Valor());
produce un error de compilación, al intentar usar una variable no asignada. Observe las siguientes operaciones con struct. Su comportamiento es previsible:
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
p.x += 100; int px = p.x; p3.x = px;
// p.x==102 // p3.x==102
p2 = p; p2.x += 100; p3.y = p.y + p2.y;
// p2.x==102, p2.y==5 // p2.x==202, p2.y==5 // p3.y==10
Console.WriteLine ("p = " + p.Valor()); Console.WriteLine ("p2 = " + p2.Valor()); Console.WriteLine ("p3 = " + p3.Valor());
el resultado es: p = [102,5] p2 = [202,5] p3 = [102,10]
Enumeraciones Una enumeración o tipo enumerado es un tipo especial de estructura en la que los literales de los valores que pueden tomar sus objetos se indican explícitamente al definirla. La sintaxis es muy parecida a la empleada en C++: enum State { Off, On };
aunque el punto y coma final es opcional ya que una enumeración es una definición de un struct y ésta no requiere el punto y coma: enum State { Off, On }
Al igual que en C++ y en C, C# numera a los elementos de la enumeración con valores enteros sucesivos, asignando el valor 0 al primero de la enumeración, a menos que se especifique explícitamente otra asignación: enum Tamanio Pequeño = Mediano = Grande = Inmenso }
{ 1, 3, 5
En el ejemplo, el valor asociado a Inmenso es 6. Para acceder a los elementos de una enumeración debe cualificarse completamente: Tamanio Ancho = Tamanio.Grande;
lo que refleja que cada enumeración es, en última instancia, un struct. Si no se declara ningún tipo subyacente de forma explícita, se utiliza Int32. En el siguiente ejemplo se especifica el tipo base byte: enum Color: byte { Red = 1, Green = 2, Blue = 4,
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c
F -X C h a n ge
F -X C h a n ge
c u -tr a c k
N y bu to k lic
Black = 0, White = Red | Green | Blue }
La clase Enum (System.Enum) proporciona la clase base para las enumeraciones. Proporciona métodos que permiten comparar instancias de esta clase, convertir el valor de una instancia en su representación de cadena, convertir la representación de cadena de un número en una instancia de esta clase y crear una instancia de una enumeración y valor especificados. Por ejemplo: Color c1 = Color.Black; Console.WriteLine((int) c1); Console.WriteLine(c1); Console.WriteLine(c1.ToString());
// 0 // Black // Black
Pueden convertirse explícitamente en su valor entero (como en C). Admiten determinados operadores aritméticos (+,-,++,--) y lógicos a nivel de bits (&,|,^,~). ejemplo: Color c2 = Color.White; Console.WriteLine((int) c2); // 7 Console.WriteLine(c2.ToString()); // White
Otro ejemplo más complejo: enum ModArchivo { Lectura = 1, Escritura = 2, Oculto = 4, Sistema = 8 } ... ModArchivo st = ModArchivo.Lectura | ModArchivo.Escritura; ... Console.WriteLine (st.ToString("F")); Lectura, Escritura Console.WriteLine (Enum.Format(typeof(ModArchivo), st, "G")); Console.WriteLine (Enum.Format(typeof(ModArchivo), st, "X")); 00000003 Console.WriteLine (Enum.Format(typeof(ModArchivo), st, "D"));
© Francisco Cortijo Bon
// // 3 // // 3
.d o
o
.c
m
C
m
w
o
.d o
w
w
w
w
w
C
lic
k
to
bu
y
N
O W !
PD
O W !
PD
c u -tr a c k
.c