Desarrollo Profesional de Aplicaciones c#

155
Desarrollo Profesional de Aplicaciones 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. o Encapsulación: además de los modificadores de accceso convencionales: public, private y protected, C# añade

Transcript of Desarrollo Profesional de Aplicaciones c#

Page 1: Desarrollo Profesional de Aplicaciones c#

Desarrollo Profesional de Aplicaciones

Introducción al lenguaje de programación C#

Características del lenguaje C#Aspectos LéxicosTipos de datosVariables y constantesOperadores y expresionesEstructuras de controlE/S básicaC# (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 .NETCaracterí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.

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.

Page 2: Desarrollo Profesional de Aplicaciones c#

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.

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.

Page 3: Desarrollo Profesional de Aplicaciones c#

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éxicosEn 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 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 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.

Page 4: Desarrollo Profesional de Aplicaciones c#

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.ComentariosLos 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:

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 */

IdentificadoresUn 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.

Page 5: Desarrollo Profesional de Aplicaciones c#

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 reservadasLas 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, true, try, typeof, uint, ulong, unchecked, unsafe, ushort, using, virtual, void, volatile, whileLiteralesUn 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 Si no tiene sufijo, su tipo es int, uint, long o ulong. o Si tiene el sufijo U o u, su tipo es uint o ulong. o Si tiene el sufijo L o l, su tipo es long o ulong. o 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 // int0x7B // hexadecimal123U // unsigned123ul // unsigned long123L // 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 Si no se especifica sufijo, el tipo es double. o Si el sufijo es F o f es de tipo float. o Si el sufijo es D o d es de tipo double. o Si el sufijo es M o m es de tipo decimal.

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 // float1d, 1.5d, 1e10d, 123.456D, 123.0 y 123D // double

Page 6: Desarrollo Profesional de Aplicaciones c#

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 Nombre del carácter Codificación Unicode\' Comilla simple 0x0027\" Comilla doble 0x0022\\ Barra invertida 0x005C\0 Null 0x0000\a Alerta 0x0007\b Retroceso 0x0008\f Avance de página 0x000C\n Nueva línea 0x000A\r Retorno de carro 0x000D\t Tabulación horizontal 0x0009\v Tabulación vertical 0x000B

El tipo de un literal de caracteres es char.Literales de caracteres

'A' // caracter sencillo'\u0041' // caracter Unicode'\x0041' // unsigned short hexadecimal'\n' // caracter 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. 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, mundo!"!Hola, \t 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!" // !Hola, \t mundo!"Me dijo \"Hola\" y me asustó" // Me dijo "Hola" y me asustó@"Me dijo ""Hola"" y me asustó" // Me dijo "Hola" y me asustó"\\\\servidor\\share\\file.txt" // \\servidor\share\file.txt@"\\servidor\share\file.txt" // \\servidor\share\file.txt@"uno // Esta es una cadena distribuida dos" // en dos lineas. Literal null.

Su único valor es null y su tipo es el tipo null.Órdenes

Page 7: Desarrollo Profesional de Aplicaciones c#

Delimitadas por punto y coma (;) como en C, C++ y Java. Los bloques { ... } no necesitan punto y coma al final.

Tipos de datosLos 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. 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 Tipos referencia

La variable contiene un valor La variable contiene una referenciaEl dato se almacena en la pila El dato se almacena en el heapEl dato siempre tiene valor El dato puede no tener valor nullUna asignación copia el valor 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 valorLos 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.

Nombre (.NET Framework)

AliasDescripció

n

Tamaño

(bytes)Rango

System.Sbyte sbyteBytes con signo

1 -128 ... 127

System.Int16 shortEnteros cortos

2 -32.768 ... 32.767

System.Int32 int Enteros 4 -2.147.483.648 ...

Page 8: Desarrollo Profesional de Aplicaciones c#

2.147.483.647

System.Int64 longEnteros largos

8

-9.223.372.036.854.775.808 ... 9.223.372.036.854.775.807

System.Byte byteBytes (sin signo)

1 0 ... 255

System.Uint16 ushortEnteros cortos (sin signo)

2 0 ... 65.535

System.UInt32 uintEnteros (sin signo)

40 ... 18.446.744.073.709.551.615

System.Uint64 ulongEnteros largos (sin signo)

80 ... 18.446.744.073.709.551.615

System.Single floatReales (7 decimales)

4±1.5 x 10-45 ... ±3.4 x 10+38

System.Double doubleReales (15-16 decimales)

8±5.0 x 10-324 ... ±1.7 x 10+308

System.Decimal

decimal

Reales (28-29 decimales)

12±1.0 x 10-28 ... ±7.9 x 10+28

System.Char charCaracteres Unicode

2 Cualquier carácter Unicode

System.Boolean

boolValores lógicos

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 referenciaSi 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.

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.

Page 9: Desarrollo Profesional de Aplicaciones c#

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 direccion de memoria que "o3" o4.Valor = 20; // Igual que hacer o3.Valor = 20; PintaDatos ("o3", "o4", o3, o4);

ObjetoDemo o5 = new ObjetoDemo(); // new llama a un constructor o5.Valor = 10; ObjetoDemo o6 = new ObjetoDemo(); // new llama a un constructor o6.Valor = o5.Valor; PintaDatos ("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); }}

Page 10: Desarrollo Profesional de Aplicaciones c#

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. Variables y constantesVariablesUna 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 variablesLos 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 variablesAntes 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:

Page 11: Desarrollo Profesional de Aplicaciones c#

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 variablesUna 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; // DeclaracionRadio = 10.0F; // Inicializacion

pueden simplificarse en una sola línea:

float Radio = 10.0F; // Declaracion e Inicializacion 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;

Page 12: Desarrollo Profesional de Aplicaciones c#

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' }

ConstantesUna 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 constantesEl á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.

Page 13: Desarrollo Profesional de Aplicaciones c#

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++):

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 expresionesUn 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 OperadoresAritmé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 newInformación de tipos as is sizeof typeofControl de excepciones de desbordamiento

checked unchecked

Direccionamiento indirecto y dirección * -> [] & 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).

Page 14: Desarrollo Profesional de Aplicaciones c#

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. 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.

Page 15: Desarrollo Profesional de Aplicaciones c#

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.

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 : expr2de 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 typedonde <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" ); } Console.ReadLine(); }}

El resultado es: 0:no es string1:no es string2:'Hola'3:no es string

Page 16: Desarrollo Profesional de Aplicaciones c#

4:no es string5:no es stringEl 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 typedonde <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."); } } 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

Page 17: Desarrollo Profesional de Aplicaciones c#

o es de Clase2o 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); } public static void Main() { Clase1 c1 = new Clase1(); Clase2 c2 = new Clase2(); Prueba (c1); Prueba (c2);

Console.ReadLine(); }}

El resultado es: --> Clase1Clase1Clase2

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.

Page 18: Desarrollo Profesional de Aplicaciones c#

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; // Maximo valor short static short y = 32767;

public static int UnCh() { int z = unchecked((short)(x + y)); return z; // -2 } public static int UnCh2() { 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-: -2Valor por defecto: -2Si 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 OperadoresPrimarios Paréntesis: (x)

Page 19: Desarrollo Profesional de Aplicaciones c#

Acceso a miembros: x.y

Llamada a métodos: f(x)

Acceso con índices: a[x]

Post-incremento: x++

Post-decremento: x--

Llamada a un constructor: new

Consulta de tipo: typeof

Control de desbordamiento activo: checked

Control de desbordamiento inactivo: unchecked

Unarios

Valor positivo: +

Valor negative: -

No: !

Complemento a nivel de bit: ~

Pre-incremento: ++x

Post-decremento: --x

Conversión de tipo -cast-: (T)x

Multiplicativos

Multiplicación: *

División: /

Resto: %

Aditivos

Suma: +

Resta: -

Desplazamiento

Desplazamiento de bits a la izquierda: <<

Desplazamiento de bits a la derecha: >>

Relacionales Menor: <

Mayor: >

Menor o igual: <=

Mayor o igual: >=

Igualdad o compatibilidad de tipo: is

Page 20: Desarrollo Profesional de Aplicaciones c#

Conversión de tipo: as

Igualdad ==Desigualdad !=Bitwise AND &Bitwise XOR ^Bitwise OR |Logical AND &&Logical OR ||Condicional ternario ?:Asignación =, *=, /=, %=, +=, -=, <<=, >>=, &=, ^=, |=

AsociatividadComo 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) + zEstructuras de controlLas 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 condicionalesif, if-elseLa 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) if (a > 0) { Console.WriteLine ("Positivo"); Console.WriteLine ("Positivo"); ContPositivos++; }

Si se actúa cuando la condición es falsa se emplea la palabra reservada else:

if (a > 0) if (a > 0) { Console.WriteLine ("Positivo"); Console.WriteLine ("Positivo"); else ContPositivos++; Console.WriteLine ("No Positivo"); } else { Console.WriteLine ("No Positivo");

Page 21: Desarrollo Profesional de Aplicaciones c#

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.switchLa 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.

Page 22: Desarrollo Profesional de Aplicaciones c#

Un ejemplo que usa un datos string para controlar la selección:

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("")); // 0 Console.WriteLine (Test("A")); // 2 Console.WriteLine (Test("B")); // 1 Console.WriteLine (Test("C")); // 1 Console.WriteLine (Test("?")); // 0 Console.ReadLine(); }

Page 23: Desarrollo Profesional de Aplicaciones c#

}}

Estructuras repetitivasLas 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++;}

do...while

int i = 0;

do { ... i++;} while (i < 5);

for

int i;

for (i=0; i < 5; i++) { ...}

foreachUn 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):

foreach (int i in v) for (int i=0; i < v.Length; i++)

Page 24: Desarrollo Profesional de Aplicaciones c#

i *= 2; 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.SaltosgotoAunque 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(); } }}

breakLo usaremos únicamente en sentencias switch. continueMejor no lo usamos. returnProcuraremos emplearlo sólo al final de un método para facilitar la legibilidad del código. E/S básicaLas 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

Page 25: Desarrollo Profesional de Aplicaciones c#

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:

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++ ... int TuEdad = 25; string TuNombre = "Pepe"; Console.WriteLine ("Tienes {0} años, {1}.", TuEdad, TuNombre); // Escribe: Tienes 25 años, Pepe.

Page 26: Desarrollo Profesional de Aplicaciones c#

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.

Aplicaciones WindowsUna 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.

Page 27: Desarrollo Profesional de Aplicaciones c#

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:

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.

Tipos de datos

Tipos básicosEl sistema unificado de tipos. El tipo ObjectCadenas de caracteresVectores y matricesEstructurasEnumeracionesCuando 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).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ásicosLos 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:

Page 28: Desarrollo Profesional de Aplicaciones c#

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:

C# Tipo en System Características Símbolosbyte System.Sbyte entero, 1 byte con signobyte System.Byte entero, 1 byte sin signoshort System.Short entero, 2 bytes con signoushort System.UShort entero, 2 bytes sin signoint System.Int32 entero, 4 bytes con signouint System.UInt32 entero, 4 bytes sin signo Ulong System.Int64 entero, 8 bytes con signo Lulong System.ULong64 entero, 8 bytes sin signo ULfloat System.Single real, IEEE 754, 32 bits Fdouble System.Double real, IEEE 754, 64 bits Ddecimal System.Decimal real, 128 bits (28 dígitos significativos) Mbool System.Boolean (Verdad/Falso) 1 bytechar System.Char Carácter Unicode, 2 bytes ´ ´string System.String Cadenas de caracteres Unicode " "

object System.ObjectCualquier 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 ObjectEn 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.

Page 29: Desarrollo Profesional de Aplicaciones c#

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++.

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 tiposUna conversión de tipo (casting) puede ser implícita o explícita.

Implícitas ExplícitasOcurren automáticamente Requieren un castingSiempre tienen éxito Pueden fallarNo se pierde información Se puede perder información

int x = 123456;long y = x; // implicitashort z = (short) x; // explicita (riesgo)

float f1 = 40.0F;long l1 = (long) f1; // explicita (riesgo por redondeo)short s1 = (short) l1; // explicita (riesgo por desbordamiento)int i1 = s1; // implicita, no hay riesgouint i2 = (uint) i1; // explicita (riesgo de error por signo)

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 objectPor 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). 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.

Page 30: Desarrollo Profesional de Aplicaciones c#

Por ejemplo, dada la siguiente declaración: object o;todas estas instrucciones son válidas: o = 10; // int o = "Una cadena para el objeto"; // cadena o = 3.1415926; // double o = new int [24]; // vector de int o = false; // booleanDado 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: 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 unboxing-

Page 31: Desarrollo Profesional de Aplicaciones c#

Boxing (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));

BoxingBoxing 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 intusing 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);

Page 32: Desarrollo Profesional de Aplicaciones c#

Console.WriteLine("Valor (tipo object)= {0}", o); }}

El resultado de su ejecución es: Valor (tipo valor) = 456Valor (tipo object)= 123UnboxingUna 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) // ERRORUna 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; // unboxingAfortunadamente 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; // unboxingo alternativamente: string s2 = o as string; // conversion if (s2 != null) // OK, la conversion funcionoConclusionesVentajas 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. La necesidad de utilizar boxing disminuirá cuando el CLR permita genéricos (algo similar a los templates en C++). Cadenas de caracteresUna 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++)

Page 33: Desarrollo Profesional de Aplicaciones c#

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++"; 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 = Esto es una cadena... como en C++s4 = Esto es una cadena... como en C++s5 = Esto es una cadena... como en C++s1 == s4 == s5Un 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 espaciosEn 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 matricesUn 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).

Page 34: Desarrollo Profesional de Aplicaciones c#

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. La sintaxis es ligeramente distinta a la del C++ porque las tablas son objetos de tipo referencia:

double [] array; // Declara un a referencia // (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 [] Mat1D; // 1 dimension string [,] Mat2D; // 2 dimensiones string [,,] Mat3D; // 3 dimensiones string [,,,] Mat4D; // 4 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:

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:

Page 35: Desarrollo Profesional de Aplicaciones c#

Num. casillas: 6Contenido 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 particionHay = 6 palabras Palabra 0 = Esto Palabra 1 = es Palabra 2 = una Palabra 3 = prueba Palabra 4 = de Palabra 5 = particionMatricesLas 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; // Declara un a referencia // (no se instancia ningún objeto) array2D = new int [2,3]; // Instancia un objeto de la clase // System.Array y le asigna 6 casillas // (2 filas con 3 columnas cada una)

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);

Page 36: Desarrollo Profesional de Aplicaciones c#

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);

// 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: 4Num. Cols. de fila 0: 2Num. Cols. de fila 1: 5Num. Cols. de fila 2: 3Num. Cols. de fila 3: 7Contenido de la matriz:11 1221 22 23 24 2531 32 3341 42 43 44 45 46 47Estructuras

Page 37: Desarrollo Profesional de Aplicaciones c#

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.

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: p.x += 100; int px = p.x; // p.x==102 p3.x = px; // p3.x==102

p2 = p; // p2.x==102, p2.y==5

Page 38: Desarrollo Profesional de Aplicaciones c#

p2.x += 100; // p2.x==202, p2.y==5 p3.y = p.y + p2.y; // 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]EnumeracionesUna 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 = 1, Mediano = 3, Grande = 5 Inmenso }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, 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); // 0 Console.WriteLine(c1); // Black Console.WriteLine(c1.ToString()); // BlackPueden 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

Page 39: Desarrollo Profesional de Aplicaciones c#

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")); // 3 Console.WriteLine (Enum.Format(typeof(ModArchivo), st, "X")); // 00000003 Console.WriteLine (Enum.Format(typeof(ModArchivo), st, "D")); // 3

Clases (1)

IntroducciónModificadores de accesoVariables de instancia y miembros estáticosCampos, constantes, campos de sólo lectura y propiedades MétodosConstructores y "destructores"Sobrecarga de operadoresConversiones de tipoClases y structs

Introducción

Un objeto es un agregado de datos y de métodos que permiten manipular dichos datos, y un programa en C# no es más que un conjunto de objetos que interaccionan unos con otros a través de sus métodos.

Una clase es la definición de las características concretas de un determinado tipo de objetos: cuáles son los datos y los métodos de los que van a disponer todos los objetos de ese tipo. Se dice que los datos y los métodos son los miembros de la clase. En C# hay muchos tipos de miembros, que podemos clasificar de manera muy genérica en las dos categorías antes mencionadas: datos y métodos.

Datos o campos o constantes y campos de sólo lectura o propiedades

Métodos o métodos generales

Page 40: Desarrollo Profesional de Aplicaciones c#

o métodos constructores o métodos de sobrecarga de operadores e indexadores

Un campo es un dato común a todos los objetos de una clase. La declaración de un dato se realiza, sintácticamente, como cualquier variable.

Un método es una función, un conjunto de instrucciones al que se le asocia un nombre.

La palabra reservada this es una variable predefinida disponible dentro de las funciones no estáticas de un tipo que se emplea, en un método de una clase, para acceder a los miembros del objeto sobre el que se está ejecutando el método en cuestión. En definitiva, permite acceder al objeto "activo". El siguiente ejemplo muestra de manera clara su aplicación:

Una clase muy sencilla (uso de this)

class Persona // Clase Persona{ private string nombre; // campo privado

public Persona (string nombre) // Constructor { this.nombre = nombre; // acceso al campo privado }

public void Presenta (Persona p) // Metodo { if (p != this) // Una persona no puede presentarse a si misma Console.WriteLine("Hola, " + p.nombre + ", soy " + this.nombre); }}...Persona yo = new Persona ("YO");Persona tu = new Persona ("TU");yo.Presenta(tu);tu.Presenta(yo);yo.Presenta(yo); // Sin efectotu.Presenta(tu); // Sin efecto

La ejecución del código anterior produce el siguiente resultado:

Hola, TU, soy YOHola, YO, soy TU

En el siguiente ejemplo mostramos más miembros de una clase:

Una clase sencilla (CocheSimple)

public class CocheSimple

Page 41: Desarrollo Profesional de Aplicaciones c#

{ private int VelocMax; // Campo private string Marca; // Campo private string Modelo; // Campo

// Método constructor sin argumentos public CocheSimple () { this.VelocMax = 0; this.Marca = "Sin marca"; this.Modelo = "Sin modelo"; }

// Método constructor con argumentos public CocheSimple (string marca, string mod, int velMax) { this.VelocMax = velMax; this.Marca = marca; this.Modelo = mod; }

// Método public void MuestraCoche () { Console.WriteLine (this.Marca + " " + this.Modelo + " (" + this.VelocMax + " Km/h)"); }

} // class CocheSimple

recordemos que el operador new se emplea para crear objetos de una clase especificada. Cuando se ejecuta se llama a un método especial llamado constructor y devuelve una referencia al objeto creado. Si no se especifica ningún constructor C# considera que existe un constructor por defecto sin parámetros. Una buena costumbre es proporcionar siempre algún constructor.

Una aplicación que usa la clase CocheSimple

class CocheSimpleApp { static void Main(string[] args) { // "MiCoche" y "TuCoche" son variables de tipo "CocheSimple" // que se inicializan llamando al constructor. CocheSimple MiCoche = new CocheSimple ("Citröen", "Xsara", 220); CocheSimple TuCoche = new CocheSimple ("Opel", "Corsa", 190);

Console.Write ("Mi coche: ");

Page 42: Desarrollo Profesional de Aplicaciones c#

MiCoche.MuestraCoche(); // LLamada al método "MuestraCoche()"

Console.Write ("El tuyo: "); TuCoche.MuestraCoche(); // LLamada al método "MuestraCoche()"

Console.ReadLine ();

} // Main

} // class CocheSimpleApp

Modificadores de acceso

Los modificadores de acceso nos permiten especificar quién puede usar un tipo o un miembro del tipo, de forma que nos permiten gestionar la encapsulación de los objetos en nuestras aplicaciones:

Los tipos de nivel superior (aquéllos que se encuentran directamente en un namespace) pueden ser public o internal

Los miembros de una clase pueden ser public, private, protected, internal o protected internal

Los miembros de un struct pueden ser public, private o internal

Modificador de acceso

Un miembro del tipo T definido en el assembly A es accesible...

public desde cualquier sitioprivate (por defecto)

sólo desde dentro de T (por defecto)

protected desde T y los tipos derivados de Tinternal desde los tipos incluidos en Aprotected internal

desde T, los tipos derivados de T y los tipos incluidos en A

Variables de instancia y miembros estáticos

Por defecto, los miembros de una clase son variables de instancia: existe una copia de los datos por cada instancia de la clase y los métodos se aplican sobre los datos de una instancia concreta.

Se pueden definir miembros estáticos que son comunes a todas las instancias de la clase. Lógicamente, los métodos estáticos no pueden acceder a variables de instancia, ni a la variable this que hace referencia al objeto actual.

Page 43: Desarrollo Profesional de Aplicaciones c#

using System;

class Mensaje { public static string Bienvenida = "¡Hola!, ¿Cómo está?"; public static string Despedida = "¡Adios!, ¡Vuelva pronto!";}

class MiembrosStaticApp { static void Main() { Console.WriteLine(Mensaje.Bienvenida); Console.WriteLine(" Bla, bla, bla ... "); Console.WriteLine(Mensaje.Despedida); Console.ReadLine(); }}

De cualquier forma, no conviene abusar de los miembros estáticos, ya que son básicamente datos y funciones globales en entornos orientados a objetos.

Campos, constantes, campos de sólo lectura y propiedades

Campos

Un campo es una variable que almacena datos, bien en una una clase, bien en una estructura.

Constantes (const)

Una constante es un dato cuyo valor se evalúa en tiempo de compilación y, por tanto, es implícitamente estático (p.ej. Math.PI).

public class MiClase { public const string version = "1.0.0"; public const string s1 = "abc" + "def";

Page 44: Desarrollo Profesional de Aplicaciones c#

public const int i3 = 1 + 2; public const double PI_I3 = i3 * Math.PI; public const double s = Math.Sin(Math.PI); //ERROR ...}

Campos de sólo lectura (readonly)

Similares a las constantes, si bien su valor se inicializa en tiempo de ejecución (en su declaración o en el constructor). A diferencia de las constantes, si cambiamos su valor no hay que recompilar los clientes de la clase. Además, los campos de sólo lectura pueden ser variables de instancia o variables estáticas.

public class MiClase{ public static readonly double d1 = Math.Sin(Math.PI); public readonly string s1; public MiClase(string s) { s1 = s; } } ...... MiClase mio = new MiClase ("Prueba"); Console.WriteLine(mio.s1); Console.WriteLine(MiClase.d1); ......

Produce como resultado:

Prueba1,22460635382238E-16

Propiedades

Las propiedades son campos virtuales, al estilo de Delphi o C++Builder. Su aspecto es el de un campo (desde el exterior de la clase no se diferencian) pero están implementadas con código, como los métodos. Pueden ser de sólo lectura, de sólo escritura o de lectura y escritura.

Considere de nuevo la clase CocheSimple. Podemos añadir la propiedad MaxSpeed:

// Propiedad public float MaxSpeed { get { return VelocMax / 1.6F; } set { VelocMax = (int) ((float) value * 1.6F);}

Page 45: Desarrollo Profesional de Aplicaciones c#

}

de manera que si las siguientes líneas se añaden al final del método main en CocheSimpleApp: Console.WriteLine (); Console.WriteLine ("My car's Max Speed: " + MiCoche.MaxSpeed +" Mph" ); // get Console.WriteLine ("Tunning my car..."); MiCoche.MaxSpeed = 200; // set Console.WriteLine ("After tunning my car (Incr. max Speed to 200 Mph"); Console.WriteLine ("My car's Max Speed: " + MiCoche.MaxSpeed + " Mph"); // get

Console.WriteLine (); Console.Write ("Mi coche: "); MiCoche.MuestraCoche(); // LLamada al método "MuestraCoche()"

el resultado obtenido es: Mi coche: Citröen Xsara (220 Km/h)El tuyo: Opel Corsa (190 Km/h)

My car's Max Speed: 137,5 MphTunning my car...After tunning my car (Incr. max Speed to 200 MphMy car's Max Speed: 200 Mph

Mi coche: Citröen Xsara (320 Km/h)

Un ejemplo con campos, métodos y propiedades

using System;

class Coche{ // Campos protected double velocidad=0; public string Marca; public string Modelo; public string Color; public string NumBastidor;

// Método constructor public Coche(string marca, string modelo, string color, string numbastidor) { this.Marca=marca; this.Modelo=modelo; this.Color=color; this.NumBastidor=numbastidor; }

Page 46: Desarrollo Profesional de Aplicaciones c#

// Propiedad (solo lectura) public double Velocidad { get { return this.velocidad; } }

// Método public void Acelerar(double c) { Console.WriteLine("--> Incrementando veloc. en {0} km/h", c); this.velocidad += c; }

// Método public void Girar(double c) { Console.WriteLine("--> Girando {0} grados", c); }

// Método public void Frenar(double c) { Console.WriteLine("--> Reduciendo veloc. en {0} km/h", c); this.velocidad -= c; }

// Método public void Aparcar() { Console.WriteLine("-->Aparcando coche"); this.velocidad = 0; }

} // class Coche

class EjemploCocheApp{

static void Main(string[] args) { Coche MiCoche = new Coche("Citröen", "Xsara Picasso", "Rojo","1546876");

Console.WriteLine("Los datos de mi coche son:"); Console.WriteLine(" Marca: {0}", MiCoche.Marca); Console.WriteLine(" Modelo: {0}", MiCoche.Modelo); Console.WriteLine(" Color: {0}", MiCoche.Color); Console.WriteLine(" Número de bastidor: {0}", MiCoche.NumBastidor); Console.WriteLine();

MiCoche.Acelerar(100);

Page 47: Desarrollo Profesional de Aplicaciones c#

Console.WriteLine("La velocidad actual es de {0} km/h", MiCoche.Velocidad);

MiCoche.Frenar(75); Console.WriteLine("La velocidad actual es de {0} km/h", MiCoche.Velocidad);

MiCoche.Girar(45);

MiCoche.Aparcar(); Console.WriteLine("La velocidad actual es de {0} km/h", MiCoche.Velocidad);

Console.ReadLine(); } } // class EjemploCocheApp

Métodos

Implementan las operaciones que se pueden realizar con los objetos de un tipo concreto. Constructores, destructores y operadores son casos particulares de métodos. Las propiedades y los indexadores se implementan con métodos (get y set).

Como en cualquier lenguaje de programación, los métodos pueden tener parámetros, contener órdenes y devolver un valor (con return).

Por defecto, los parámetros se pasan por valor (por lo que los tipos "valor" no podrían modificarse en la llamada a un método). El modificador ref permite que pasemos parámetros por referencia. Para evitar problemas de mantenimiento, el modificador ref hay que especificarlo tanto en la definición del método como en el código que realiza la

Page 48: Desarrollo Profesional de Aplicaciones c#

llamada. Además, la variable que se pase por referencia ha de estar inicializada previamente.

void RefFunction (ref int p) { p++;}......

int x = 10;

RefFunction (ref x); // x vale ahora 11

El modificador out permite devolver valores a través de los argumentos de un método. De esta forma, se permite que el método inicialice el valor de una variable. En cualquier caso, la variable ha de tener un valor antes de terminar la ejecución del método. Igual que antes, Para evitar problemas de mantenimiento, el modificador out hay que especificarlo tanto en la definición del método como en el código que realiza la llamada.

void OutFunction(out int p) { p = 22;}......

int x; // x aún no está inicializada

OutFunction (out x);

Console.WriteLine(x); // x vale ahora 22

Sobrecarga de métodos: Como en otros lenguajes, el identificador de un método puede estar sobrecargado siempre y cuando las signaturas de las distintas implementaciones del método sean únicas (la signatura tiene en cuenta los argumentos, no el tipo de valor que devuelven).

void Print(int i);void Print(string s);void Print(char c);void Print(float f);

int Print(float f); // Error: Signatura duplicada

Vectores de parámetros: Como en C, un método puede tener un número variable de argumentos. La palabra clave params permite especificar un parámetro de método que acepta un número variable de argumentos. No se permiten parámetros adicionales después de la palabra clave params, ni varias palabras clave params en una misma declaración de método.

Page 49: Desarrollo Profesional de Aplicaciones c#

El siguiente código emplea una función que suma todos los parámetros que recibe. El número de éstos es indeterminado, aunque debe asegurarse que sean de tipo int:

public static int Suma(params int[] intArr) { int sum = 0; foreach (int i in intArr) sum += i; return sum; } ...... int sum1 = Sum(13,87,34); // sum1 vale 134 Console.WriteLine(sum1); int sum2 = Sum(13,87,34,6); // sum2 vale 140 Console.WriteLine(sum2);

produce el siguiente resultado: 134140

El siguiente código es algo más complejo.

public static void UseParams1(params int[] list) { for ( int i = 0 ; i < list.Length ; i++ ) Console.Write(list[i] + ", "); Console.WriteLine(); }

public static void UseParams2(params object[] list) { for ( int i = 0 ; i < list.Length ; i++ ) Console.Write((object)list[i] + ", "); Console.WriteLine(); } ...... UseParams1(1, 2, 3); UseParams2(1, 'a', "test");

int[] myarray = new int[3] {10,11,12}; UseParams1(myarray);

Observe su ejecución: 1, 2, 3,1, a, test,10, 11, 12,

Constructores y "destructores"

Los constructores son métodos especiales que son invocados cuando se instancia una clase (o un struct).

Se emplean habitualmente para inicializar correctamente un objeto. Como cualquier otro método, pueden sobrecargarse.

Page 50: Desarrollo Profesional de Aplicaciones c#

Si una clase no define ningún constructor se crea un constructor sin parámetros (ímplícito).

No se permite un constructor sin parámetros para los struct.

C# permite especificar código para inicializar una clase mediante un constructor estático. El constructor estático se invoca una única vez, antes de llamar al constructor de una instancia particular de la clase o a cualquier método estático de la clase. Sólo puede haber un constructor estático por tipo y éste no puede tener parámetros.

Destructores: Se utilizan para liberar los recursos reservados por una instancia (justo antes de que el recolector de basura libere la memoria que ocupe la instancia).

A diferencia de C++, la llamada al destructor no está garantizada por lo que tendremos que utilizar una orden using e implementar el interfaz IDisposable para asegurarnos de que se liberan los recursos asociados a un objeto). Sólo las clases pueden tener destructores (no los struct).

class Foo { ~Foo() { Console.WriteLine("Destruido {0}", this); }}

Sobrecarga de operadores

Como en C++, se pueden sobrecargar (siempre con un método static) algunos operadores unarios (+, -, !, ~, ++, --, true, false) y binarios (+, -, *, /, %, &, |, ^, ==, !=, <, >, <=, >=, <, >).

No se puede sobrecargar el acceso a miembros, la invocación de métodos, el operador de asignación ni los operadores sizeof, new, is, as, typeof, checked, unchecked, &, || y ?:.

Los operadores & y || se evalúan directamente a partir de los operadores & y |.

La sobrecarga de un operador binario (v.g. *) sobrecarga implícitamente el operador de asignación correspondiente (v.g. *=).

using System;

class OverLoadApp{

Page 51: Desarrollo Profesional de Aplicaciones c#

public class Point { int x, y; // Campos

public Point() // Constructor sin parámetros { this.x = 0; this.y = 0; } public Point(int x, int y) // Constructor común { this.x = x; this.y = y; } public int X // Propiedad { get { return x; } set { x = value; } } public int Y // Propiedad { get { return y; } set { y = value; } }

// Operadores de igualdad

public static bool operator == (Point p1, Point p2) { return ((p1.x == p2.x) && (p1.y == p2.y)); } public static bool operator != (Point p1, Point p2) { return (!(p1==p2)); }

// Operadores aritméticos

public static Point operator + (Point p1, Point p2) { return new Point(p1.x+p2.x, p1.y+p2.y); } public static Point operator - (Point p1, Point p2) { return new Point(p1.x-p2.x, p1.y-p2.y); } }

static void Main(string[] args) { Point p1 = new Point(10,20); Point p2 = new Point();

Page 52: Desarrollo Profesional de Aplicaciones c#

p2.X = p1.X; p2.Y = p1.Y;

Point p3 = new Point(22,33);

Console.WriteLine ("p1 es: ({0},{1})", p1.X, p1.Y); Console.WriteLine ("p2 es: ({0},{1})", p2.X, p2.Y); Console.WriteLine ("p3 es: ({0},{1})", p3.X, p3.Y);

if (p1 == p2) Console.WriteLine ("p1 y p2 son iguales"); else Console.WriteLine ("p1 y p2 son diferentes");

if (p1 == p3) Console.WriteLine ("p1 y p3 son iguales"); else Console.WriteLine ("p1 y p3 son diferentes");

Console.WriteLine ();

Point p4 = p1 + p3; Console.WriteLine ("p4 (p1+p3) es: ({0},{1})", p4.X, p4.Y); Point p5 = p1 - p1; Console.WriteLine ("p5 (p1-p1) es: ({0},{1})", p5.X, p5.Y);

Console.WriteLine (); Console.WriteLine ("p1 es: ({0},{1})", p1.X, p1.Y); Console.WriteLine ("p2 es: ({0},{1})", p2.X, p2.Y); Console.WriteLine ("p3 es: ({0},{1})", p3.X, p3.Y); Console.WriteLine ("p4 es: ({0},{1})", p4.X, p4.Y); Console.WriteLine ("p5 es: ({0},{1})", p5.X, p5.Y);

Console.ReadLine (); }}

Page 53: Desarrollo Profesional de Aplicaciones c#

Para asegurar la compatibilidad con otros lenguajes de .NET:

// Operadores aritméticos public static Point operator + (Point p1, Point p2) { return SumaPoints (p1, p2); } public static Point operator - (Point p1, Point p2) { return RestaPoints (p1, p2); } public static Point RestaPoints (Point p1, Point p2) { return new Point(p1.x-p2.x, p1.y-p2.y); } public static Point SumaPoints (Point p1, Point p2) { return new Point(p1.x+p2.x, p1.y+p2.y); }

Y respecto a los operadores de comparación:

// Operadores de igualdad public static bool operator == (Point p1, Point p2) { return (p1.Equals(p2)); } public static bool operator != (Point p1, Point p2) { return (!p1.Equals(p2));

Page 54: Desarrollo Profesional de Aplicaciones c#

} public override bool Equals (object o) { // Por valor if ( (((Point) o).x == this.x) && (((Point) o).y == this.y) ) return true; else return false; } public override int GetHashCode() { return (this.ToString().GetHashCode()); }

La sobrecarga de los operadores relacionales obliga a implementar la interface IComparable, concretamente el método CompareTo:

public class Point : IComparable {

...... // Operadores relacionales

public int CompareTo(object o) { Point tmp = (Point) o; if (this.x > tmp.x) return 1; else if (this.x < tmp.x) return -1; else return 0; }

public static bool operator < (Point p1, Point p2) { IComparable ic1 = (IComparable) p1; return (ic1.CompareTo(p2) < 0); } public static bool operator > (Point p1, Point p2) { IComparable ic1 = (IComparable) p1; return (ic1.CompareTo(p2) > 0); } public static bool operator <= (Point p1, Point p2) { IComparable ic1 = (IComparable) p1; return (ic1.CompareTo(p2) <= 0); } public static bool operator >= (Point p1, Point p2) { IComparable ic1 = (IComparable) p1; return (ic1.CompareTo(p2) >= 0); }

......} // class Point

Page 55: Desarrollo Profesional de Aplicaciones c#

static void Main(string[] args) { ...... if (p1 > p2) Console.WriteLine ("p1 > p2"); if (p1 >= p2) Console.WriteLine ("p1 >= p2"); if (p1 < p2) Console.WriteLine ("p1 < p2"); if (p1 <= p2) Console.WriteLine ("p1 <= p2"); if (p1 == p2) Console.WriteLine ("p1 == p2"); Console.WriteLine ();

if (p1 > p3) Console.WriteLine ("p1 > p3"); if (p1 >= p3) Console.WriteLine ("p1 >= p3"); if (p1 < p3) Console.WriteLine ("p1 < p3"); if (p1 <= p3) Console.WriteLine ("p1 <= p3"); if (p1 == p3) Console.WriteLine ("p1 == p3"); Console.WriteLine (); }}

El operador de asignación (=) cuando se aplica a clases copia la referencia, no el contenido. Si se desea copiar instancias de clases lo habitual en C# es redefinir (overrride) el método MemberwiseCopy() que heredan, por defecto, todas las clases en C# de System.Object.

Conversiones de tipo

Pueden programarse las conversiones de tipo, tanto explícitas como implícitas):

using System;

class ConversionesApp{

public class Euro { private int cantidad; // Campo

public Euro (int v) // Constructor común { cantidad = v;} public int valor // Propiedad { get { return cantidad; } } // Conversión implícita "double <-- Euro" public static implicit operator double (Euro x) { return ((double) x.cantidad); }

// Conversión explícita "Euro <-- double" public static explicit operator Euro(double x)

Page 56: Desarrollo Profesional de Aplicaciones c#

{ double arriba = Math.Ceiling(x); double abajo = Math.Floor(x); int valor = ((x+0.5 >= arriba) ? (int)arriba : (int)abajo); return new Euro(valor); } } // class Euro

static void Main(string[] args) { double d1 = 442.578; double d2 = 123.22; Euro e1 = (Euro) d1; // Conversión explícita a "Euro" Euro e2 = (Euro) d2; // Conversión explícita a "Euro" Console.WriteLine ("d1 es {0} y e1 es {1}", d1, e1.valor); Console.WriteLine ("d2 es {0} y e2 es {1}", d2, e2.valor);

double n1 = e1; // Conversión implícita "double <--Euro" double n2 = e2; // Conversión implícita "double <--Euro"

Console.WriteLine ("n1 es {0}", n1); Console.WriteLine ("n2 es {0}", n2);

Console.ReadLine (); }}

C# no permite definir conversiones entre clases que se relacionan mediante herencia. Dichas conversiones están ya disponibles: de manera implícita desde una clase derivada a una antecesora y de manera explícita a la inversa.

Clases y structs

Page 57: Desarrollo Profesional de Aplicaciones c#

Tanto las clases como los structs permiten al usuario definir sus propios tipos, pueden implementar múltiples interfaces y pueden contener datos (campos, constantes, eventos...), funciones (métodos, propiedades, indexadores, operadores, constructores, destructores y eventos) y otros tipos internos (clases, structs, enums, interfaces y delegados).

Observe las similitudes entre ambas en el siguiente ejemplo.

struct SPoint - class CPoint

using System;

struct SPoint { private int x, y; // Campos public SPoint(int x, int y) // Constructor { this.x = x; this.y = y; } public int X // Propiedad { get { return x; } set { x = value; } } public int Y // Propiedad { get { return y; } set { y = value; } }}

class CPoint { private int x, y; // Campos public CPoint(int x, int y) // Constructor { this.x = x; this.y = y; } public int X // Propiedad { get { return x; } set { x = value; } } public int Y // Propiedad { get { return y; } set { y = value; } }}

class Class2App{

Page 58: Desarrollo Profesional de Aplicaciones c#

static void Main(string[] args) { SPoint sp = new SPoint(2,5); sp.X += 100; int spx = sp.X; // spx = 102

CPoint cp = new CPoint(2,5); cp.X += 100; int cpx = cp.X; // cpx = 102

Console.WriteLine ("spx es: {0} ", spx); // 102 Console.WriteLine ("cpx es: {0} ", cpx); // 102 Console.ReadLine (); }}

Aunque las coincidencias son muchas, existen, no obstante, existen algunas diferencias entre ellos:

Cuando se crea un objeto struct mediante el operador new, se crea y se llama al constructor apropiado. A diferencia de las clases, se pueden crear instancias de las estructuras sin utilizar el operador new. Si no se utiliza new, los campos permanecerán sin asignar y el objeto no se podrá utilizar hasta haber inicializado todos los campos.

Clase StructTipo referencia Tipo valor

Puede heredar de otro tipo (que no esté "sellado")

Para las estructuras no existe herencia: una estructura no puede heredar de otra estructura o clase, ni puede ser la base de una clase. Sin embargo, las estructuras heredan de la clase base Object. Una estructura puede implementar interfaces del mismo modo que las clases.

Puede tener un constructor sin parámetros

No puede tener un constructor sin parámetros

No pueden crearse instancias sin emplear el operador new.

Se pueden crear instancias de las estructuras sin utilizar el operador new, pero los campos permanecerán sin asignar y el objeto no se podrá utilizar hasta haber iniciado todos los campos.

Puede tener un destructor

No puede tener destructor

Supongamos la clase MiClase y el struct de tipo MiStruct. Según sabemos de C#, las instancias de MiClase se almacenan en el heap mientras las instancias de MiStruct se almacenan en la pila:

MiClase cl; Declara una referencia (igual a la declaración de

Page 59: Desarrollo Profesional de Aplicaciones c#

un puntero no inicializado en C++).

cl = new MiClase();

Crea una instancia de MiClase. LLama al constructor sin parámetros de la clase. Además, reserva memoria en el heap.

MiStruct st;Crea una instancia de MiStruct pero no llama a ningún constructor. Los campos de st quedan sin iniciar.

st = new MiStruct();

Llama al constructor: se inicializan los campos con los valores por defecto. No se reserva memoria porque st ya existe en la pila.

Herencia

Concepto de herenciaClases abstractasClases selladasTipos anidadosOperadores especiales

Concepto de herencia

El mecanismo de herencia es uno de los pilares fundamentales en los que se basa la programación orientada a objetos. Es un mecanismo que permite definir nuevas clases a partir de otras ya definidas. Si en la definición de una clase indicamos que ésta deriva de otra, entonces la primera -a la que se le suele llamar clase hija o clase derivada- será tratada por el compilador automáticamente como si su definición incluyese la definición de la segunda -a la que se le suele llamar clase padre o clase base.

Las clases que derivan de otras se definen usando la siguiente sintaxis:

class <claseHija> : <clasePadre> { <miembrosHija> }

A los miembros definidos en la clase hija se le añadirán los que hubiésemos definido en la clase padre: la clase derivada "hereda" de la clase base.

La palabra clave base se utiliza para obtener acceso a los miembros de la clase base desde una clase derivada.

Page 60: Desarrollo Profesional de Aplicaciones c#

C# sólo permite herencia simple.

Herencia de constructores

Los objetos de una clase derivada contarán con los mismos miembros que los objetos de la clase base y además incorporarán nuevos campos y/o métodos. El constructor de una clase derivada puede emplear el constructor de la clase base para inicializar los campos heredados de la clase padre con la construcción base. En realidad se trata de una llamada al constructor de la clase base con los parámetros adecuados.

: base(<parametrosBase>)

Si no se incluye el compilador consideraría que vale :base(), lo que provocaría un error si la clase base carece de constructor sin parámetros.

Ejemplo de "herencia" de constructores

public class B { private int h; // Campo

public B () { // Constructor sin parámetros this.h = -1; } public B (int h) // Constructor con parámetro { this.h = h; } public int H // Propiedad { get { return h; } set { h = value; } }} // class B

public class D : B // "D" hereda de "B"{ private int i; // Campo

public D () : this(-1) {} // Constructor sin parámetros public D (int i) { // Constructor con un parámetro this.i = i; } public D (int h, int i) : base(h) { // Constructor con this.i = i; // dos parámetros

Page 61: Desarrollo Profesional de Aplicaciones c#

} public int I // Propiedad { get { return i; } set { i = value; } }

} // class D

...... B varB1 = new B(); // Const. sin parámetros de B B varB2 = new B(5); // Const. con 1 parámetro de B Console.WriteLine("varB1 : (H={0})", varB1.H); Console.WriteLine("varB2 : (H={0})\n", varB2.H);

D varD1 = new D(); // Const. sin parámetros de D D varD2 = new D(15); // Const. con 1 parámetro de D D varD3 = new D(25, 11); // Const. con 2 parámetros de D

Console.WriteLine("varD1 : (I={0},H={1})", varD1.I, varD1.H); Console.WriteLine("varD2 : (I={0},H={1})", varD2.I, varD2.H); Console.WriteLine("varD3 : (I={0},H={1})", varD3.I, varD3.H); Console.ReadLine(); ......

En el siguiente ejemplo se muestra cómo puede extenderse la clase CocheSimple vista anteriormente para construir, a partir de ella, la clase Taxi. Observar como se emplea la construcción base para referenciar a un constructor de la clase base y que cuando actúa el constructor sin parámetros de la clase Taxi se llama implícitamente al constructor sin parámetros de la clase CocheSimple.

Ejemplo: herencia sobre la clase CocheSimple

using System;

Page 62: Desarrollo Profesional de Aplicaciones c#

namespace DemoHerencia {

class CocheSimple { private int VelocMax; private string Marca; private string Modelo;

public CocheSimple () { this.VelocMax = 0; this.Marca = "??"; this.Modelo = "??"; } public CocheSimple (string marca, string mod, int velMax) { this.VelocMax = velMax; this.Marca = marca; this.Modelo = mod; }

public void MuestraCoche () { Console.WriteLine (this.Marca + " " + this.Modelo + " (" + this.VelocMax + " Km/h)"); }

} // class CocheSimple

class Taxi : CocheSimple

private string CodLicencia;

public Taxi () {} public Taxi (string marca, string mod, int vel, string lic) : base (marca, mod, vel) { this.CodLicencia = lic; } public string Licencia { get { return this.CodLicencia; } } } // class Taxi class DemoHerenciaApp {

static void Main(string[] args) { CocheSimple MiCoche = new CocheSimple ("Citröen", "Xsara Picasso", 220); CocheSimple TuCoche = new CocheSimple ("Opel", "Corsa", 190); CocheSimple UnCoche = new CocheSimple ();

Page 63: Desarrollo Profesional de Aplicaciones c#

Console.Write ("Mi coche: "); MiCoche.MuestraCoche(); Console.Write ("El tuyo: "); TuCoche.MuestraCoche(); Console.Write ("Un coche sin identificar: "); UnCoche.MuestraCoche();

Console.WriteLine();

Taxi ElTaxiDesconocido = new Taxi (); Console.Write ("Un taxi sin identificar: "); ElTaxiDesconocido.MuestraCoche();

Taxi NuevoTaxi= new Taxi ("Ford", "KA", 150, "GR1234"); Console.Write ("Un taxi nuevo: "); NuevoTaxi.MuestraCoche(); Console.Write (" Licencia: {0}", NuevoTaxi.Licencia);

Console.ReadLine ();

} // Main

} // class DemoHerenciaApp

} // namespace DemoHerencia

Redefinición de métodos

Siempre que se redefine un método que aparece en la clase base, hay que utilizar explícitamente la palabra reservada override y, de esta forma, se evitan redefiniciones accidentales (una fuente de errores en lenguajes como Java o C++).

Sabemos que todos los objetos (incluidas las variables de los tipos predefinidos) derivan, en última instancia, de la clase Object. Esta clase proporciona el método ToString que crea una cadena de texto legible para el usuario que describe una instancia de la clase. Si dejamos sin redefinir este método y empleando la clase CocheSimple las siguientes instrucciones:

Page 64: Desarrollo Profesional de Aplicaciones c#

CocheSimple MiCoche = new CocheSimple ("Citröen", "Xsara Picasso", 220); Console.WriteLine ("Mi coche: " + MiCoche.ToString());

producen el siguiente resultado:

Mi coche: DemoHerencia.CocheSimple

lo que nos invita a redefinir el método ToString en la clase CocheSimple:

class CocheSimple { ... public override string ToString() { return (this.Marca + " " + this.Modelo + " (" + this.VelocMax + " Km/h)"); } ... }

Las dos instrucciones siguientes son equivalentes:

Console.WriteLine ("Mi coche: " + MiCoche.ToString());

Console.WriteLine ("Mi coche: " + MiCoche);

por lo que podemos sutituir las instrucciones que muestran los datos de los objetos CocheSimple por:

Console.WriteLine ("Mi coche: " + MiCoche); Console.WriteLine ("El tuyo: " + TuCoche); Console.WriteLine ("Un coche sin identificar: " + UnCoche);

y eliminamos el (innecesario) método MuestraCoche, el resultado de la ejecución del programa anterior es:

Page 65: Desarrollo Profesional de Aplicaciones c#

La palabra reservada base sirve para hacer referencia a los miembros de la clase base que quedan ocultos por otros miembros de la clase actual. Por ejemplo, podríamos redefinir también el método ToString de la clase Taxi empleando el método redefinido ToString de la clase base CocheSencillo:

class CocheSimple { ... public override string ToString() { return (this.Marca + " " + this.Modelo + " (" + this.VelocMax + " Km/h)"); } ... } class Taxi : CocheSimple { ... public override string ToString() { return (base.ToString() + "\n Licencia: " + this.Licencia); } ... } ...... Taxi ElTaxiDesconocido = new Taxi (); Console.WriteLine ("Un taxi sin identificar: " + ElTaxiDesconocido);

Taxi NuevoTaxi= new Taxi ("Citröen", "C5", 250, "GR1234"); Console.WriteLine ("Un taxi nuevo: " + NuevoTaxi); ......

y el resultado es:

Page 66: Desarrollo Profesional de Aplicaciones c#

En la sección dedicada a la classes.xml#Sobrecarga

sobrecarga de operadores

introdujimos la clase Point. No había ningún método que mostrara los datos de interés de un objeto de tipo Point. Podemos sobreescribir el método ToString de manera que fuera:

public class Point { ... public override string ToString() { return ("["+this.X+", "+this.Y+"]"); } ... }

Ahora las instrucciones de escritura se convierten en llamadas a este método, por ejemplo:

Console.WriteLine ("p1 es: " + p1); // Console.WriteLine ("p1 es: " + p1.ToString()

El resultado de la ejecución de ese programa será:

Page 67: Desarrollo Profesional de Aplicaciones c#

Métodos virtuales

Un método es virtual si puede redefinirse en una clase derivada. Los métodos son no virtuales por defecto.

Los métodos no virtuales no son polimórficos (no pueden reemplazarse) ni pueden ser abstractos.

Los métodos virtuales se definen en una clase base (empleando la palabra reservada virtual) y pueden ser reemplazados (empleando la palabra reservada override) en las subclases (éstas proporcionan su propia -específica- implementación).

Generalmente, contendrán una implementación por defecto del método (si no, se deberían utilizar métodos abstractos).

class Shape // Clase base{ // "Draw" es un método virtual public virtual void Draw() { ... } }

class Box : Shape { // Reemplaza al método Draw de la clase base public override void Draw() { ... }}

class Sphere : Shape { // Reemplaza al método Draw de la clase base public override void Draw() { ... }}

void HandleShape(Shape s) { ... s.Draw(); // Polimorfismo ...}

HandleShape(new Box());HandleShape(new Sphere());HandleShape(new Shape());

NOTA: Propiedades, indexadores y eventos también pueden ser virtuales.

Clases abstractas

Una clase abstracta es una clase que no puede ser instanciada. Se declara empelando la palabra reservada abstract.

Page 68: Desarrollo Profesional de Aplicaciones c#

Permiten incluir métodos abstractos y métodos no abstractos cuya implementación hace que sirvan de clases base (herencia de implementación). Como es lógico, no pueden estar "selladas".

Métodos abstractos

Un método abstracto es un método sin implementación que debe pertenecer a una clase abstracta. Lógicamente se trata de un método virtual forzoso y su implementación se realizará en una clase derivada.

abstract class Shape // Clase base abstracta{ public abstract void Draw(); // Método abstracto }

class Box : Shape { public override void Draw() { ... }}

class Sphere : Shape { public override void Draw() { ... }}

void HandleShape(Shape s) { ... s.Draw(); ...}

HandleShape(new Box());HandleShape(new Sphere());HandleShape(new Shape()); // Error !!!

Clases selladas

Una clase sellada (sealed), es una clase de la que no pueden derivarse otras clases (esto es, no puede utilizarse como clase base). Obviamente, no puede ser una clase abstracta.

Los struct en C# son implícitamente clases selladas.

¿Para qué sirve sellar clases? Para evitar que se puedan crear subclases y optimizar el código (ya que las llamadas a las funciones de una clase sellada pueden resolverse en tiempo de compilación).

Tipos anidados

Page 69: Desarrollo Profesional de Aplicaciones c#

C# permite declarar tipos anidados, esto es, tipos definidos en el ámbito de otro tipo. El anidamiento nos permite que el tipo anidado pueda acceder a todos los miembros del tipo que lo engloba (independientemente de los modificadores de acceso) y que el tipo esté oculto de cara al exterior (salvo que queramos que sea visible, en cuyo caso habrá que especificar el nombre del tipo que lo engloba para poder acceder a él).

Operadores especiales

is

Se utiliza para comprobar dinámicamente si el tipo de un objeto es compatible con un tipo especificado (instanceof en Java).

No conviene abusar de este operador (es preferible diseñar correctamente una jerarquía de tipos).

static void DoSomething(object o) { if (o is Car) ((Car)o).Drive();}

as

Intenta convertir de tipo una variable (al estilo de los casts dinámicos de C++). Si la conversión de tipo no es posible, el resultado es null. Es más eficiente que el operador is, si bien tampoco es conveniente abusar del operador as.

static void DoSomething(object o) { Car c = o as Car;

if (c != null) c.Drive();}

typeof

El operador typeof devuelve el objeto derivado de System.Type correspondiente al tipo especificado. De esta forma se puede hacer reflexión para obtener dinámicamente información sobre los tipos (como en Java).

...Console.WriteLine(typeof(int).FullName);Console.WriteLine(typeof(System.Int).Name);Console.WriteLine(typeof(float).Module);Console.WriteLine(typeof(double).IsPublic);

Page 70: Desarrollo Profesional de Aplicaciones c#

Console.WriteLine(typeof(Point).MemberType);...

Clases (2)

Indexadores (indexers)InterfacesDelegados

Indexadores (indexers)

C# no permite, hablado con rigor, la sobrecarga del operador de acceso a tablas [ ]. Si permite, no obstante, definir lo que llama un indexador para una clase que permite la misma funcionalidad.

Los indexadores permiten definir código a ejecutar cada vez que se acceda a un objeto del tipo del que son miembros usando la sintaxis propia de las tablas, ya sea para leer o escribir. Esto es especialmente útil para hacer más clara la sintaxis de acceso a elementos de objetos que puedan contener colecciones de elementos, pues permite tratarlos como si fuesen tablas normales.

A diferencia de las tablas, los índices que se les pase entre corchetes no tiene porqué ser enteros, pudiéndose definir varios indexadores en un mismo tipo siempre y cuando cada uno tome un número o tipo de índices diferente.

La sintaxis empleada para la definición de un indexador es similar a la de la definición de una propiedad.

public class MiClase{ ... public string this[int x] { get { // Obtener un elemento } set {

Page 71: Desarrollo Profesional de Aplicaciones c#

// Fijar un elemento } } ...}...MiClase MiObjeto = new MiClase();

El código que aparece en el bloque get se ejecuta cuando la expresión MiObjeto[x] aparece en la parte derecha de una expresión mientras que el código que aparece en el bloque set se ejecuta cuando MiObjeto[x] aparece en la parte izquierda de una expresión.

Algunas consideraciones finales:

Igual que las propiedades, pueden ser de sólo lectura, de sólo escritura o de lectura y escritura.

El nombre dado a un indexador siempre ha de ser this. Lo que diferenciará a unos indexadores de otros será el número y tipo de sus

índices.

Ejemplo de indexador

using System;

namespace IndexadorCoches{ public class Coche { private int VelocMax; private string Marca; private string Modelo;

public Coche (string marca, string modelo, int velocMax) { this.VelocMax = velocMax; this.Marca = marca; this.Modelo = modelo; }

public override string ToString () { return (this.Marca + " " + this.Modelo + " (" + this.VelocMax + " Km/h)"); }

} // class Coche

public struct DataColeccionCoches { public int numCoches; public int maxCoches;

Page 72: Desarrollo Profesional de Aplicaciones c#

public Coche[] vectorCoches;

public DataColeccionCoches (int max) { this.numCoches = 0; this.maxCoches = max; this.vectorCoches = new Coche[max]; } } // struct DataColeccionCoches

public class Coches { // Los campos se encapsulan en un struct DataColeccionCoches data;

// Constructor sin parámetros public Coches() { this.data = new DataColeccionCoches(10); // Si no se pone un valor se llamará al // constructor sin parámetros (por defecto) del // struct y tendremos problemas: no se puede // explicitar el constructor sin parámetros // para un struct. }

// Constructor con parámetro public Coches(int max) { this.data = new DataColeccionCoches(max); }

public int MaxCoches { set { data.maxCoches = value; } get { return (data.maxCoches); } } public int NumCoches { set { data.numCoches = value; } get { return (data.numCoches); } } public override string ToString () { string str1 = " --> Maximo= " + this.MaxCoches; string str2 = " --> Real = " + this.NumCoches; return (str1 + "\n" + str2); }

// El indexador devuelve un objeto Coche de acuerdo // a un índice numérico public Coche this[int pos] { // Devuelve un objeto del vector de coches

Page 73: Desarrollo Profesional de Aplicaciones c#

get { if(pos < 0 || pos >= MaxCoches) throw new IndexOutOfRangeException("Fuera de rango"); else return (data.vectorCoches[pos]); } // Escribe en el vector set { this.data.vectorCoches[pos] = value;} }

} // class Coches

class IndexadorCochesApp { static void Main(string[] args) { // Crear una colección de coches Coches MisCoches = new Coches (); // Por defecto (10)

Console.WriteLine ("***** Mis Coches *****"); Console.WriteLine ("Inicialmente: "); Console.WriteLine (MisCoches);

// Añadir coches. Observar el acceso con [] ("set") MisCoches[0] = new Coche ("Opel", "Zafira", 200); MisCoches[1] = new Coche ("Citröen", "Xsara", 220); MisCoches[2] = new Coche ("Ford", "Focus", 190);

MisCoches.NumCoches = 3;

Console.WriteLine ("Despues de insertar 3 coches: "); Console.WriteLine (MisCoches);

// Mostrar la colección Console.WriteLine (); for (int i=0; i<MisCoches.NumCoches; i++) { Console.Write ("Coche Num.{0}: ", i+1); Console.WriteLine (MisCoches[i]); // Acceso ("get") } Console.ReadLine ();

// *********************************************

Page 74: Desarrollo Profesional de Aplicaciones c#

// Crear una colección de coches Coches TusCoches = new Coches (4);

Console.WriteLine ("***** Tus Coches *****"); Console.WriteLine ("Inicialmente: "); Console.WriteLine (TusCoches);

// Añadir coches. Observar el acceso con [] TusCoches[TusCoches.NumCoches++] = new Coche ("Opel", "Corsa", 130); TusCoches[TusCoches.NumCoches++] = new Coche ("Citröen", "C3", 140);

Console.WriteLine ("Despues de insertar 2 coches: "); Console.WriteLine (TusCoches);

// Mostrar la colección Console.WriteLine (); for (int i=0; i<TusCoches.NumCoches; i++) { Console.Write ("Coche Num.{0}: ", i+1); Console.WriteLine (TusCoches[i]); // Acceso ("get") }

Console.ReadLine ();

} // Main

} // class IndexadorCochesApp

} // namespace IndexadorCoches

Page 75: Desarrollo Profesional de Aplicaciones c#

Interfaces

Un interfaz define un contrato semántico que ha de respetar cualquier clase (o struct) que implemente el interfaz.

La interfaz no contiene implementación alguna. La clase o struct que implementa el interfaz es la que tiene la funcionalidad especificada por el interfaz.

Una interfaz puede verse como una forma especial de definir clases que sólo cuenten con miembros abstractos. Sin embargo, todo tipo que derive de una interfaz ha de dar una implementación de todos los miembros que hereda de esta, y no como ocurre con las clases abstractas donde es posible no darla si se define como abstracta también la clase hija.

La especificación del interfaz puede incluir métodos, propiedades, indexadores y eventos, pero no campos, operadores, constructores o destructores.

Aunque solo se permite la herencia simple de clases, como ocurre en Java, se permite y herencia múltiple de interfaces. Esto significa que es posible definir tipos que deriven de más de una interfaz.

Los interfaces (como algo separado de la implementación) permiten la existencia del polimorfismo, al poder existir muchas clases o structs que implementen el interfaz.

Ejemplo de polimorfismo

using System;

namespace Interface1{ class Interface1App { // Definición de una interface

public interface IDemo { void MetodoDeIDemo (); }

// "Clase1" y "Clase2" implementan la interface

public class Clase1 : IDemo { public void MetodoDeIDemo() { Console.WriteLine ("Método de Clase1"); } }

public class Clase2 : IDemo

Page 76: Desarrollo Profesional de Aplicaciones c#

{ public void MetodoDeIDemo() { Console.WriteLine ("Método de Clase2"); } } static void Main(string[] args) { Clase1 c1 = new Clase1(); Clase2 c2 = new Clase2(); IDemo demo; // objeto de una interface // Ejemplo de polimorfismo demo = c1; demo.MetodoDeIDemo();

demo = c2; demo.MetodoDeIDemo();

Console.ReadLine(); } }}

Otro ejemplo:

Ejemplo de interface "heredada"

using System;

class InterfaceApp{

interface IPresentable { void Presentar(); }

class Triangulo : IPresentable { private double b, a; public Triangulo(double Base, double altura)

Page 77: Desarrollo Profesional de Aplicaciones c#

{ this.b=Base; this.a=altura; } public double Base { get { return b; } } public double Altura { get { return a; } } public double Area { get { return (Base*Altura/2); } } public void Presentar() { Console.WriteLine("Base del triángulo: {0}", Base); Console.WriteLine("Altura del triángulo: {0}", Altura); Console.WriteLine("Área del triángulo: {0}", Area); } } class Persona : IPresentable { private string nbre, apell, dir; public Persona (string nombre, string apellidos, string direccion) { this.nbre = nombre; this.apell = apellidos; this.dir = direccion; } public string Nombre { get { return nbre; } } public string Apellidos { get { return apell; } } public string Direccion { get { return dir; } } public void Presentar() { Console.WriteLine("Nombre: {0}", Nombre); Console.WriteLine("Apellidos: {0}", Apellidos); Console.WriteLine("Dirección: {0}", Direccion); } }

static void VerDatos(IPresentable IP)

Page 78: Desarrollo Profesional de Aplicaciones c#

{ IP.Presentar(); }

static void Main(string[] args) { Triangulo t=new Triangulo(10,5); Persona p=new Persona ("Paco", "Pérez", "su casa");

Console.WriteLine("Ya se han creado los objetos"); Console.WriteLine("\nINTRO para VerDatos(triangulo)"); Console.ReadLine(); VerDatos(t);

Console.WriteLine("\nINTRO para VerDatos(proveedor)"); Console.ReadLine(); VerDatos(p);

Console.ReadLine(); }}

El principal uso de las interfaces es indicar que una clase implementa ciertas características. Por ejemplo, el ciclo foreach trabaja internamente comprobando que la clase sobre la que se aplica implementa el interfaz IEnumerable y llamando a los métodos definidos en esa interfaz.

Herencia múltiple

La plataforma .NET no permite herencia múltiple de implementación, aunque sí se puede conseguir herencia múltiple de interfaces. Clases, structs e interfaces pueden heredar de múltiples interfaces (como en Java).

Page 79: Desarrollo Profesional de Aplicaciones c#

Herencia de interfaces

using System;

namespace Interface2{ class Interface2App { // Definición de interfaces

public interface IDemo1 { void Metodo1DeInterface1 (); string Metodo2DeInterface1 (); }

public interface IDemo2 { void Metodo1DeInterface2 (); }

public interface IDemo3 : IDemo1 { void Metodo1DeInterface3 (string mensaje); }

// "Clase1" implementan la interface "IDemo1"

public class Clase1 : IDemo1 { public void Metodo1DeInterface1() { Console.WriteLine ("Mét1 de Int1 en Clase1"); }

public string Metodo2DeInterface1() { return ("En Mét2 de Int1 en Clase1"); }

}

// "Clase1" implementan las interfaces // "IDemo1" e "IDemo2"

public class Clase2 : IDemo1, IDemo2 { public void Metodo1DeInterface1() { Console.WriteLine ("Mét1 de Int1 en Clase2"); }

public string Metodo2DeInterface1() { return ("En Mét2 de Int1 en Clase2"); }

public void Metodo1DeInterface2() { Console.WriteLine ("Mét1 de Int2 en Clase2"); } }

Page 80: Desarrollo Profesional de Aplicaciones c#

// "Clase3" implementan la interface "IDemo3", la // cual ha heredado de "IDemo1"

public class Clase3 : IDemo3 { public void Metodo1DeInterface1() { Console.WriteLine ("Mét1 de Int1 en Clase3"); }

public string Metodo2DeInterface1() { return ("En Mét2 de Int1 en Clase3"); }

public void Metodo1DeInterface3 (string m) { Console.WriteLine (m + "Mét1 de Int3 en Clase3"); }

} static void Main(string[] args) { Clase1 c1 = new Clase1(); Clase2 c2 = new Clase2(); Clase3 c3 = new Clase3();

IDemo1 i1; IDemo2 i2; IDemo3 i3; c1.Metodo1DeInterface1(); Console.WriteLine(c1.Metodo2DeInterface1()); Console.WriteLine();

i1 = c3; Console.WriteLine("Cuando i1 = c3 "); i1.Metodo1DeInterface1(); Console.WriteLine(i1.Metodo2DeInterface1()); Console.WriteLine();

i3 = c3; Console.WriteLine("Cuando i3 = c3 "); i3.Metodo1DeInterface1(); Console.WriteLine(i3.Metodo2DeInterface1()); i3.Metodo1DeInterface3("Aplicado a i3: "); Console.WriteLine();

i1 = c2; Console.WriteLine("Cuando i1 = c2 "); i1.Metodo1DeInterface1(); Console.WriteLine(i1.Metodo2DeInterface1()); i2 = c2; Console.WriteLine("Ahora i2 = c2 "); i2.Metodo1DeInterface2(); Console.WriteLine();

Console.ReadLine(); } }

Page 81: Desarrollo Profesional de Aplicaciones c#

}

Resolución de conflictos de nombres

Si dos interfaces tienen un método con el mismo nombre, se especifica explícitamente el interfaz al que corresponde la llamada al método para eliminar ambigüedades

interface IControl { void Delete();}

interface IListBox: IControl { void Delete();}

interface IComboBox: ITextBox, IListBox { void IControl.Delete(); void IListBox.Delete();}

Delegados

Un delegado es un tipo especial de clase que define la signatura de un método. Su función es similar a la de los punteros a funciones en lenguajes como C y C++ (C# no soporta punteros a funciones).

Los delegados pueden pasarse a métodos y pueden usarse para llamar a los métodos de los que contienen referencias.

Page 82: Desarrollo Profesional de Aplicaciones c#

Los delegados proporcionan polimorfismo para las llamadas a funciones:

Un "tipo" delegado

using System;

class Delegate1App{ // Declaración del "tipo delegado" llamado // "Del": funciones que devuelven un double // y reciben un double. delegate double Del (double x);

static double incremento (double x) { return (++x); }

static void Main(string[] args) { // Instanciación Del del1 = new Del (Math.Sin); Del del2 = new Del (Math.Cos); Del del3 = new Del (incremento); // Llamadas Console.WriteLine (del1(0)); // 0 Console.WriteLine (del2(0)); // 1 Console.WriteLine (del3(10)); // 11

Console.ReadLine(); }}

Para hacer más evidente el polimorfismo el método Main podría escribirse como:

static void Main(string[] args) { Del f1 = new Del (Math.Sin); Del f2 = new Del (Math.Cos); Del f3 = new Del (incremento);

Del f; f = f1; Console.WriteLine (f(0)); // 0 f = f2; Console.WriteLine (f(0)); // 1 f = f3; Console.WriteLine (f(10)); // 11 }

o bien así:

Page 83: Desarrollo Profesional de Aplicaciones c#

static void Main(string[] args) { Del[] d = new Del[3]; d[0] = new Del (Math.Sin); d[1] = new Del (Math.Cos); d[2] = new Del (incremento);

for (int i=0; i<2; i++) Console.WriteLine (d[i](0)); // 0, 1 Console.WriteLine (d[2](10)); // 11 }

Los delegados son muy útiles ya que permiten disponer de objetos cuyos métodos puedan ser modificados dinámicamente durante la ejecución de un programa.

En general, son útiles en todos aquellos casos en que interese pasar métodos como parámetros de otros métodos.

Los delegados son la base sobre la que se monta la gestión de eventos en la plataforma .NET.

Multicasting

Un delegado es un tipo especial de clase cuyos objetos pueden almacenar referencias a uno o más métodos, de tal manera que a través del objeto sea posible solicitar la ejecución en cadena de todos ellos.

Un delegado puede contener e invocar múltiples métodos. De esta forma se puede hacer multicasting de una forma sencilla y elegante. Para que un delegado pueda contener varios métodos, éstos no pueden devolver ningún valor (si lo intentasen devolver, se generaría una excepción en tiempo de ejecución).

"Multicasting"

using System;

class Delegate2App{ delegate void SomeEvent (int x, int y);

static void Func1(int x, int y) { Console.WriteLine(" Desde Func1"); }

static void Func2(int x, int y) { Console.WriteLine(" Desde Func2"); }

Page 84: Desarrollo Profesional de Aplicaciones c#

static void Main(string[] args) { SomeEvent func = new SomeEvent(Func1);

func += new SomeEvent(Func2);

Console.WriteLine("Llamada a func"); func(1,2); // Se llama tanto a Func1 como a Func2

func -= new SomeEvent(Func1);

Console.WriteLine("Llamada a func"); func(2,3); // Sólo se llama a Func2

Console.ReadLine(); }}

Cada delegado tiene una lista ordenada de métodos que se invocan secuencialmente (en el mismo orden en el que fueron añadidos al delegado). Los operadores += y -= se utilizan para añadir y eliminar métodos de la lista asociada a un delegado.

Delegados vs. interfaces

Siempre se pueden utilizar interfaces en vez de delegados. Los interfaces son más versátiles, ya que pueden encapsular varios métodos y permiten herencia, si bien los delegados resultan más adecuados para implementar manejadores de eventos. Con los delegados se escribe menos código y se pueden implementar fácilmente múltiples manejadores de eventos en una única clase.

Eventos

Muchas aplicaciones actuales se programan en función de eventos. Cuando se produce algún hecho de interés para nuestra aplicación, éste se notifica mediante la generación de un evento, el cual será procesado por el manejador de eventos correspondiente (modelo "publish-subscribe"). Los eventos nos permiten enlazar código personalizado a componentes creados previamente (mecanismo de "callback").

Page 85: Desarrollo Profesional de Aplicaciones c#

El callback consiste en que un cliente notifica a un servidor que desea ser informado cuando alguna acción tenga lugar. C# usa los eventos de la misma manera que Visual Basic usa los mensajes.

Las aplicaciones en Windows se programan utilizando eventos, pues los eventos resultan especialmente indicados para la implementación de interfaces interactivos. Cuando el usuario hace algo (pulsar una tecla, hacer click con el ratón, seleccionar un dato de una lista...), el programa reacciona en función de la acción del usuario.

El uso de eventos, no obstante, no está limitado a la implementación de interfaces. También son útiles en el desarrollo de aplicaciones que deban realizar operaciones periódicamente o realizar operaciones de forma asíncrona (p.ej. llegada de un correo electrónico, terminación de una operación larga...).

El lenguaje C# da soporte a los eventos mediante el uso de delegados. Al escribir nuestra aplicación, un evento no será más que un campo que almacena un delegado. Los usuarios de la clase podrán registrar delegados (mediante los operadores += y -=), pero no podrán invocar directamente al delegado.

Eventos en C#

public delegate void EventHandler ( object sender, EventArgs e);

public class Button { public event EventHandler Click;

protected void OnClick (EventArgs e) { // This is called when button is clicked if (Click != null) Click(this, e); }}

public class MyForm: Form { Button okButton;

static void OkClicked(object sender, EventArgs e) { ShowMessage("You pressed the OK button"); }

public MyForm() { okButton = new Button(...); okButton.Caption = "OK"; okButton.Click += new EventHandler(OkClicked); }

Page 86: Desarrollo Profesional de Aplicaciones c#

}

Aspectos avanzados de C#

ExcepcionesReflexiónAtributos

Excepciones

Las excepciones ofrecen varias ventajas respecto a otros métodos de notificación de error, como los códigos devueltos (órdenes return) ya que ningún error pasa desapercibido (las excepciones no pueden ser ignoradas) y no tienen por qué tratarse en el punto en que se producen. Los valores no válidos no se siguen propagando por el sistema. No es necesario comprobar los códigos devueltos. Es muy sencillo agregar código de control de excepciones para aumentar la confiabilidad del programa.

Una excepción es cualquier situación de error o comportamiento inesperado que encuentra un programa en ejecución. Las excepciones se pueden producir a causa de un error en el código o en código al que se llama (como una biblioteca compartida), que no estén disponibles recursos del sistema operativo, condiciones inesperadas que encuentra Common Language Runtime (por ejemplo, código que no se puede comprobar), etc. La aplicación se puede recuperar de algunas de estas condiciones, pero de otras no.

Las excepciones pueden generarse en un proceso o hebra de nuestra aplicación (con la sentencia throw) o pueden provenir del entorno de ejecución de la plataforma .NET.

En .NET Framework, una excepción es un objeto derivado de la clase Exception. El mecanismo de control de excepciones en C# es muy parecido al de C++ y Java: la excepción se inicia en un área del código en que se produce un problema. La excepción asciende por la pila hasta que la aplicación la controla o el programa se detiene.

El proceso es el siguiente:

La sentencia throw lanza una excepción (una instancia de una clase derivada de System.Exception, que contiene información sobre la excepción: Message, StackTrace, HelpLink, InnerException...).

El bloque try delimita código que podría generar una excepción. El bloque catch indica cómo se manejan las excepciones. Se puede relanzar

la excepción capturada o crear una nueva si fuese necesario. Se pueden especificar distintos bloques catch para capturar distintos tipos de excepciones. En ese caso, es recomendable poner primero los más específicos (para asegurarnos de que capturamos la excepción concreta).

Page 87: Desarrollo Profesional de Aplicaciones c#

El bloque finally incluye código que siempre se ejecutará (se produzca o no una excepción).

Ejemplo 1

El siguiente ejemplo pone de manifiesto el flujo de ejecución que sigue un programa que lanza y procesa una excepción.

try { Console.WriteLine("try"); throw new Exception("Mi excepcion");} catch { Console.WriteLine("catch");} finally { Console.WriteLine("finally");}

La ejecución de este programa produce el siguiente resultado:

trycatchfinally

Ejemplo 2

Se puede profundizar en el tratamiento de la excepción, por ejemplo, comprobando alguna propiedad del objeto Exception generado.

La clase Exception es la clase base de la que derivan las excepciones. La mayoría de los objetos de excepción son instancias de alguna clase derivada de Exception, pero se puede iniciar cualquier objeto derivado de la clase Object como excepción. En casi todos los casos, es recomendable iniciar y detectar sólo objetos Exception.

La clase Exception tiene varias propiedades que facilitan la comprensión de una excepción. Entre éstas destacamos la propiedad Message. Esta propiedad proporciona información sobre la causa de una excepción. Veamos cómo se utiliza:

try { Console.WriteLine("try"); throw new Exception("Mi excepcion");} catch (Exception e) { Console.WriteLine("catch"); Console.WriteLine("Excepción detectada: " + e.Message);

Page 88: Desarrollo Profesional de Aplicaciones c#

}catch { Console.WriteLine("catch");} finally { Console.WriteLine("finally");}

La ejecución de este programa produce el siguiente resultado:

trycatchExcepción detectada: Mi excepcionfinally

La mayoría de las clases derivadas de la clase Exception no implementan miembros adicionales ni proporcionan más funcionalidad, simplemente heredan de Exception. Por ello, la información más importante sobre una excepción se encuentra en la jerarquía de excepciones, el nombre de la excepción y la información que contiene la excepción.

Ejemplo 3

El siguiente ejemplo muestra cómo el uso de execpciones puede controlar un número importante de situaciones de error.

static void Main(string[] args) { int numerador = 10; Console.WriteLine ("Numerador es = {0}", numerador); Console.Write ("Denominador = "); string strDen = Console.ReadLine();

int denominador, cociente;

try { Console.WriteLine("--> try"); denominador = Convert.ToInt16(strDen); cociente = numerador / denominador; Console.WriteLine ("Cociente = {0}", cociente); }

catch (ArithmeticException e) { Console.WriteLine("--> catch"); Console.WriteLine("Excep. aritmética"); Console.WriteLine("ArithmeticException Handler: {0}", e.ToString()); }

Page 89: Desarrollo Profesional de Aplicaciones c#

catch (ArgumentNullException e) { Console.WriteLine("--> catch"); Console.WriteLine("Excep. de argumento nulo"); Console.WriteLine("ArgumentNullException Handler: {0}", e.ToString()); } catch (Exception e) { Console.WriteLine("--> catch"); Console.WriteLine("generic Handler: {0}", e.ToString()); } finally { Console.WriteLine("--> finally"); } Console.ReadLine(); }}

Cuando todo funciona sin problemas:

Cuando se intenta dividir por cero:

Cuando se produce desbordamiento:

Page 90: Desarrollo Profesional de Aplicaciones c#

Cuando se produce otro problema (cadena vacía, por ejemplo):

Ejemplo 4

Hemos visto que pueden conocerse los detalles de la excepción que se haya producido. Podemos conocer más detalles usando la propiedad StackTrace. Esta propiedad contiene un seguimiento de pila que se puede utilizar para determinar dónde se ha producido un error. El seguimiento de pila contiene el nombre del archivo de código fuente y el número de la línea del programa si está disponible la información de depuración.

using System;using System.Diagnostics;

class ExcepApp{ static void Main(string[] args) { int numerador = 10;

Page 91: Desarrollo Profesional de Aplicaciones c#

Console.WriteLine ("Numerador es = {0}", numerador);

Console.Write ("Denominador = "); string strDen = Console.ReadLine();

int denominador, cociente;

try { Console.WriteLine("--> try"); denominador = Convert.ToInt16(strDen); cociente = numerador / denominador; Console.WriteLine ("Cociente = {0}", cociente); }

catch (Exception e) { Console.WriteLine("--> catch"); Console.WriteLine("Generic Handler: {0}", e.ToString()); Console.WriteLine();

StackTrace st = new StackTrace(e, true);

Console.WriteLine("Traza de la pila:"); for (int i = 0; i < st.FrameCount; i++) { StackFrame sf = st.GetFrame(i); Console.WriteLine(" Pila de llamadas, Método: {0}",

sf.GetMethod() ); Console.WriteLine(" Pila de llamadas, Línea : {0}", sf.GetFileLineNumber()); } Console.WriteLine(); } finally { Console.WriteLine("--> finally"); }

Console.ReadLine(); }}

Page 92: Desarrollo Profesional de Aplicaciones c#

Reflexión

La capacidad de reflexión de la plataforma .NET (similar a la de la plataforma Java) nos permite explorar información sobre los tipos de los objetos en tiempo de ejecución.

La instrucción GetType() obtiene el objeto Type de la instancia actual sobre el que se aplica. El valor devuelto es representa el tipo exacto, en tiempo de ejecución, de la instancia actual.

Page 93: Desarrollo Profesional de Aplicaciones c#

Un sencillo ejemplo con Type y GetType()

public class Test { private int n;

public Test (int n) { this.n = n; }}

// Acceso a información acerca de una clase

public static void Main(string[] args) { Type tipoClase = typeof(Test); Console.WriteLine("El nombre del tipo de tipoClase es: {0}", tipoClase.Name);

Test t = new Test(0); Type tipoVariable = t.GetType(); Console.WriteLine("El tipo de la variable t es: {0}", tipoVariable.Name);}

El programa anterior muestra como resultado:

El nombre del tipo de tipoClase es: TestEl tipo de la variable t es: Test

Page 94: Desarrollo Profesional de Aplicaciones c#

Otro ejemplo del uso de Type y GetType():

Un ejemplo más complejo con Type y GetType()

Using System;

public class ClaseBase : Object {}

public class ClaseDerivada : ClaseBase {}

public class Test {

public static void Main() {

ClaseBase ibase = new ClaseBase(); ClaseDerivada iderivada = new ClaseDerivada(); Console.WriteLine("ibase: Type is {0}", ibase.GetType()); Console.WriteLine("iderivada: Type is {0}", iderivada.GetType());

object o = iderivada; ClaseBase b = iderivada; Console.WriteLine("object o = iderivada: Type is {0}", o.GetType()); Console.WriteLine("ibase b = iderivada: Type is {0}", b.GetType()); Console.ReadLine(); }}

La reflexión puede emplearse para examinar los métodos, propiedades, ... de una clase:

Page 95: Desarrollo Profesional de Aplicaciones c#

Métodos de una clase

using System;using System.Reflection;

class ReflectApp{ public class Test { private int n;

public Test (int n) { this.n = n; } public void Metodo1DeTest (int n) { // ..... } public int Metodo2DeTest (int a, float b, string c) { // ..... return 0; } }

public static void Main(string[] args) { Type t = typeof(Test); MethodInfo[] MetInf = t.GetMethods(); foreach (MethodInfo m in MetInf) { Console.WriteLine (); Console.WriteLine ("Método: " + m.Name ); Console.WriteLine (" Características: " + ((m.IsPublic) ? " (public)" : "") + ((m.IsVirtual) ? " (virtual)" : ""));

// Parámetros

ParameterInfo[] ParInf = m.GetParameters();

if (ParInf.Length > 0) { Console.WriteLine (" Parámetros: " );

Page 96: Desarrollo Profesional de Aplicaciones c#

foreach (ParameterInfo p in ParInf) Console.WriteLine(" " + p.ParameterType + " " + p.Name); } } Console.ReadLine (); }}

Atributos

Un atributo es información que se puede añadir a los metadatos de un módulo de código. Los atributos nos permiten "decorar" un elemento de nuestro código con información adicional.

C# es un lenguaje imperativo, pero, como todos los lenguajes de esta categoría, contiene algunos elementos declarativos. Por ejemplo, la accesibilidad de un método de una clase se especifica mediante su declaración como public, protected, private o internal. C# generaliza esta capacidad permitiendo a los programadores inventar nuevas formas de información declarativa, anexarlas a distintas entidades del programa y recuperarlas en tiempo de ejecución. Los programas especifican esta información declarativa adicional mediante la definición y el uso de atributos.

Esta información puede ser referente tanto al propio módulo o el ensamblado al que peretenezca, como a los tipos de datos definidos en él, sus miembros, los parámetros de sus

Page 97: Desarrollo Profesional de Aplicaciones c#

métodos, los bloques set y get de sus propiedades e indexadores o los bloques add y remove de sus eventos. Se pueden emplear en ensamblados, módulos, tipos, miembros, valores de retorno y parámetros.

Atributos predefinidos

Si bien el programador puede definir cuantos atributos considere necesarios, algunos atributos ya están predefinidos en la plataforma .NET.

Atributo Descripción

Browsable Propiedades y eventos que deben mostrarse en el inspector de objetos.

SerializableClases y estructuras que pueden "serializarse" (esto es, volcarse en algún dispositivo de salida, p.ej. disco), como en Java.

Obsolete El compilador se quejará si alguien los utiliza (deprecated en Java).

ProgId COM Prog IDTransaction Características transaccionales de una clase.

Observar como al marcar como obsoleta la clase A se genera un error al compilar el módulo ya que se emplea en la línea comentada.

...

[Obsolete("Clase A desfasada. Usar B en su lugar")]class A { public void F() {}}class B { public void F() {}}

class SimpleAtrPredefApp{

static void Main(string[] args) {

A a = new A(); // Avisos a.F(); ... }}

Page 98: Desarrollo Profesional de Aplicaciones c#

Declarar una clase atributo

Declarar un atributo en C# es simple: se utiliza la forma de una declaración de clase que hereda de System.Attribute y que se ha marcado con el atributo AttributeUsage, como se indica a continuación:

// La clase HelpAttribute posee un parámetro posicional (url) // de tipo string y un parámetro con nombre -opcional- (Topic) // de tipo string.

[AttributeUsage(AttributeTargets.All)]public class HelpAttribute: Attribute { public string Topic = null; private string url;

public HelpAttribute(string url) { this.url = url; } public string Url { get { return url; } } public override string ToString() { string s1 = " Url = " + this.Url; string dev = (this.Topic != null) ? (s1 + " - Topic: " + this.Topic) : s1; return (dev); }} // class HelpAttribute

Page 99: Desarrollo Profesional de Aplicaciones c#

El atributo AttributeUsage especifica los elementos del lenguaje a los que se puede aplicar el atributo.

Las clases de atributos son clases públicas derivadas de System.Attribute que disponen al menos de un constructor público.

Las clases de atributos tienen dos tipos de parámetros: o Parámetros posicionales, que se deben especificar cada vez que se

utiliza el atributo. Los parámetros posicionales se especifican como argumentos de constructor para la clase de atributo. En el ejemplo anterior, url es un parámetro posicional.

o Parámetros con nombre, los cuales son opcionales. Si se especifican al usar el atributo, debe utilizarse el nombre del parámetro. Los parámetros con nombre se definen mediante un campo o una propiedad no estáticos. En el ejemplo anterior, Topic es un parámetro con nombre.

Los parámetros de un atributo sólo pueden ser valores constantes de los siguientes tipos:

o Tipos simples (bool, byte, char, short, int, long, float y double)

o string o System.Type o enumeraciones o object (El argumento para un parámetro de atributo del tipo object

debe ser un valor constante de uno de los tipos anteriores.) o Matrices unidimensionales de cualquiera de los tipos anteriores

Parámetros para el atributo AttributeUsage

El atributo AttributeUsage proporciona el mecanismo subyacente mediante el cual los atributos se declaran.

AttributeUsage tiene un parámetro posicional:

AllowOn, que especifica los elementos de programa a los que se puede asignar el atributo (clase, método, propiedad, parámetro, etc.). Los valores aceptados para este parámetro se pueden encontrar en la enumeración System.Attributes.AttributeTargets de .NET Framework. El valor predeterminado para este parámetro es el de todos los elementos del programa (AttributeElements.All).

AttributeUsage tiene un parámetro con nombre:

AllowMultiple, valor booleano que indica si se pueden especificar varios atributos para un elemento de programa. El valor predeterminado para este parámetro es False.

Utilizar una clase atributo

Page 100: Desarrollo Profesional de Aplicaciones c#

A continuación, se muestra un breve ejemplo de uso del atributo declarado en la sección anterior:

[Help("http://decsai.ugr.es/Clase1.htm")]class Clase1 { /* Bla, bla, bla... */}

[Help("http://decsai.ugr.es/Clase2.htm", Topic="Atributos")]class Clase2 { /* Bla, bla, bla... */}

En este ejemplo, el atributo HelpAttribute está asociado con las clases Clase1 y Clase2.

Nota: Por convención, todos los nombres de atributo finalizan con la palabra "Attribute" para distinguirlos de otros elementos de .NET Framework. No obstante, no tiene que especificar el sufijo de atributo cuando utiliza atributos en el código (véase el ejemplo).

Acceder a los atributos por reflexión

Los atributos de un tipo o de un miembro de un tipo pueden ser examinados en tiempo de ejecución (reflexión), heredan de la clase System.Attribute y sus argumentos se comprueban en tiempo de compilación.

Los principales métodos de reflexión para consultar atributos se encuentran en la clase System.Reflection.MemberInfo. El método clave es GetCustomAttributes, que devuelve un vector de objetos que son equivalentes, en tiempo de ejecución, alos atributos del código fuente.

Ejemplo 1

El siguiente ejemplo muestra la manera básica de utilizar la reflexión para obtener acceso a los atributos:

class AtributosSimpleApp { static void Main(string[] args) { MemberInfo info1 = typeof(Clase1); object[] attributes1 = info1.GetCustomAttributes(true);

Page 101: Desarrollo Profesional de Aplicaciones c#

for (int i = 0; i < attributes1.Length; i ++) { System.Console.WriteLine(attributes1[i]); } MemberInfo info2 = typeof(Clase2); object[] attributes2 = info2.GetCustomAttributes(true); for (int i = 0; i < attributes2.Length; i ++) { System.Console.WriteLine(attributes2[i]); } Console.ReadLine();

} // Main ()} // class AtributosSimpleApp

Ejemplo 2

Este ejemplo amplía el anterior añadiendo muchas más posibilidades.

Atributos y reflexión

using System;using System.Diagnostics;using System.Reflection;

// La clase IsTested es una clase de atributo // definida por el usuario. // Puede aplicarse a cualquier definición, incluyendo: // - tipos (struct, class, enum, delegate)// - miembros (métodos, campos, events, properties, indexers)// Se usa sin argumentos.

[AttributeUsage(AttributeTargets.All)]public class IsTestedAttribute : Attribute { public override string ToString() { return (" REVISADO"); }}

// La clase HelpAttribute posee un parámetro posicional (url) // de tipo string y un parámetro con nombre -opcional- (Topic) // de tipo string.

Page 102: Desarrollo Profesional de Aplicaciones c#

[AttributeUsage(AttributeTargets.All)]public class HelpAttribute: Attribute { public string Topic = null; private string url;

public HelpAttribute(string url) { this.url = url; } public string Url { get { return url; } } public override string ToString() { string s1 = " Url = " + this.Url; string dev = (this.Topic != null) ? (s1 + ". Topic = " + this.Topic) : s1; return (dev); }

}

// La clase CodeReviewAttribute es una clase de atributo // definida por el usuario. // Puede aplicarse en clases y structs únicamente. // Toma dos argumentos de tipo string (el nombre del // revisor y la fecha de revisión) además de permitir otro // argumento opcional (Comment) de tipo string.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]public class CodeReviewAttribute: System.Attribute { public CodeReviewAttribute(string reviewer, string date) { this.reviewer = reviewer; this.date = date; this.comment = ""; } public string Comment { get { return(comment); } set { comment = value; } } public string Date { get { return(date); } }

public string Reviewer { get { return(reviewer); } }

public override string ToString() { string st1 = " Revisor : " + Reviewer + "\

Page 103: Desarrollo Profesional de Aplicaciones c#

n"; string st2 = " Fecha: " + Date; string st3; if (Comment.Length != 0) st3 = "\n" + " NOTAS: " + Comment; else st3 = ""; return (st1 + st2 + st3); }

string reviewer; string date; string comment;}

[CodeReview("Pepe", "01-12-2002", Comment="Codigo mejorable")][Help("http://decsai.ugr.es/Clase1.htm")][IsTested]class Clase1 { int c1c1; int c2c1;

public Clase1 (int n1, int n2) { this.c1c1 = n1; this.c2c1 = n2; }

[IsTested] public override string ToString() { return (this.c1c1.ToString() + this.c2c1.ToString()); } }

[CodeReview("Juani", "12-11-2002", Comment="Excelente")][Help("http://decsai.ugr.es/Clase3.htm", Topic="Atributos")]class Clase2 { string c1c2;

public Clase2 (string s) { this.c1c2 = s; }

[IsTested] public char Met1Clase2 () { return (this.c1c2[0]); }}

[CodeReview("Pepe", "12-11-2002"), IsTested()]

Page 104: Desarrollo Profesional de Aplicaciones c#

class Clase3 { int c1c3;

[IsTested] public Clase3 (int n1) { this.c1c3 = n1; }}

class Atributos1App{ private static bool IsMemberTested (MemberInfo member) { foreach (object attr in member.GetCustomAttributes(true)) if (attr is IsTestedAttribute) return true; return false; }

private static string InfoRevision (MemberInfo member) { if (IsMemberTested(member)) return ("REVISADO"); else return ("NO REVISADO"); }

private static void DumpAttributes(MemberInfo member) { Console.WriteLine(); Console.WriteLine("Información de: " + member.Name);/* object[] arr = member.GetCustomAttributes(typeof(HelpAttribute), true); if (arr.Length == 0) Console.WriteLine("Esta clase no tiene ayuda."); else { HelpAttribute ha = (HelpAttribute) arr[0]; Console.WriteLine (ha.ToString()); }*/ foreach (object attribute in member.GetCustomAttributes(true)) { if (attribute is HelpAttribute) Console.WriteLine (" Atributos de ayuda:"); if (attribute is CodeReviewAttribute) Console.WriteLine (" Atributos de Revisión:"); if (attribute is IsTestedAttribute)

Page 105: Desarrollo Profesional de Aplicaciones c#

Console.WriteLine (" Atributos de Actualización:");

Console.WriteLine(attribute); } }

static void Main(string[] args) { // t es un vector de tipos Type [] t = new Type[3];

t[0] = typeof(Clase1); t[1] = typeof(Clase2); t[2] = typeof(Clase3);

for (int i=0; i<3; i++) {

DumpAttributes(t[i]);

Console.WriteLine (" Información de los métodos:");

foreach (MethodInfo m in (t[i]).GetMethods()) { if (IsMemberTested(m)) { Console.WriteLine(" Método {0} REVISADO", m.Name); } else { Console.WriteLine(" Método {0} NO REVISADO", m.Name); } }

} Console.ReadLine(); }}

Programas en C#

Organización lógica de los tiposOrganización física de los tiposEjecución de aplicacionesCódigo no seguroPreprocesadorDocumentación

Page 106: Desarrollo Profesional de Aplicaciones c#

El compilador de C# se ocupa de abstraer al programador de la localización (ficheros) de los tipos y otros elementos. Es irrelevante el hecho de colocar el código en un único fichero o de disponerlo en varios, así como de usar una clase antes de declararla: el compilador se encargará de encontrar la localización de los elementos. La consecuencia es que no existe, propiamente, el concepto de enlace (linking): el compilador compila el código a un ensamblado (o simplemente a un módulo).

Organización lógica de los tipos

Del mismo modo que los ficheros se organizan en directorios, los tipos de datos se organizan en espacios de nombres (del inglés, namespaces).

Los espacios de nombres son mecanismos para controlar la visibilidad (ámbito) de los nombres empleados e un programa. Su propósito es el de facilitar la combinación de los componentes de un programa (que pueden provenir de varias fuentes) minimizando los conflictos entre identificadores.

Por un lado estos espacios permiten tener más organizados los tipos de datos, lo que facilita su localización. Así es como está organizada la BCL: todas las clases más comúnmente usadas en cualquier aplicación pertenecen al espacio de nombres llamado System, las de acceso a bases de datos en System.Data, las de realización de operaciones de entrada/salida en System.IO, etc.

Por otro lado, los espacios de nombres también permiten poder usar en un mismo programa clases homónimas, siempre que pertenezcan a espacios de nombres diferentes y queden perfectamente cualificadas.

En definitiva: Los espacios de nombres proporcionan una forma unívoca de identificar un tipo. Eliminan cualquier tipo de ambigüedad en los nombres de los símbolos empleados en un programa.

El siguiente ejemplo trabaja con un espacio de nombres que incluye la declaración de un clase:

namespace Graficos2D { public class Point { ... }}

Los componentes del espacio de nombres no son visibles directamente desde fuera del espacio en el que están inmersos (al igual que los componentes de un struct, de una enumeración, de una clase, ...) a no ser que se cualifiquen completamente:

namespace Graficos2D {

Page 107: Desarrollo Profesional de Aplicaciones c#

public class Point { ... }}

class MainClass{ Graficos2D.Point p; static void Main(string[] args) { ... }}

La declaración

Point p;

produciría un error de compilación. En el siguiente ejemplo se trabaja con dos espacios de nombres que incluyen una clase homónima (Point) en cada uno de ellos. Observe el uso correcto de cada una de las dos clases cuando se cualifican completamente:

using System;

namespace Graficos2D { public class Point { ... }}

namespace Graficos3D { public class Point { ... }}

class MainClass{ Graficos2D.Point p1; Graficos3D.Point p2;

static void Main(string[] args) { ... }}

No existe relación entre espacios de nombres y ficheros (a diferencia de Java).

Los espacios de nombres se pueden anidar. Observe en el siguiente ejemplo cómo la cualificación evita cualquier duda acerca de la clase de los objetos:

namespace N1 { public class C1

Page 108: Desarrollo Profesional de Aplicaciones c#

{ public class C2 {} } namespace N2 { public class C2 {} } }

class MainClass { N1.C1 o1; N1.C1.C2 o2; N1.N2.C2 o3;

static void Main(string[] args) { ... } }

La directiva using

La directiva using se utiliza para permitir el uso de tipos en un espacio de nombres, de modo que no sea necesario especificar el uso de un tipo en ese espacio de nombres (directiva using).

using System;

namespace N1 { public class C1 { public class C2 {} } namespace N2 { public class C2 {} } }

namespace DemoNamespace{ using N1;

class MainClass { C1 o1; // N1 es implícito (N1.C1) N1.C1 o1_bis; // Tipo totalmente cualificado

//C2 c; // ¡Error! C2 no está definida en N1 C1.C2 o2; // Idem a: N1.C1.C2 o2; N1.N2.C2 o3; // Tipo totalmente

Page 109: Desarrollo Profesional de Aplicaciones c#

cualificado N2.C2 o3_bis; // Idem a: N1.N2.C2 o3_bis; static void Main(string[] args) { ... } }}

La directiva using también puede usarse para crear un alias para un espacio de nombres (alias using).

using Cl2 = N1.N2.C2;using NS1 = N1.N2;

Cl2 ob1; // O sea, N1.N2.C2NS1.C2 ob2; // O sea, N1.N2.C2

Observe cómo, en ocasiones, el uso de alias simplifica y clarifica el código:

Uso de alias

using System;

namespace Graficos2D { public class Point { }}

namespace Graficos3D { public class Point { }}

namespace DemoNamespace{ using Point2D = Graficos2D.Point; using Point3D = Graficos3D.Point;

class MainClass { Point2D p1; Point3D p2;

static void Main(string[] args) { } }}

Page 110: Desarrollo Profesional de Aplicaciones c#

Cualquier declaración -propia- que use un nombre empelado en un espacio de nombres oscurece la declaración del espacio de nombres en el bloque en el que ha sido declarada.

using System;

namespace Graficos2D { public class Point { } public class Almacen {}}

namespace DemoNamespace{ using Graficos2D;

class MainClass { Point p1; int[] Almacen = new int[20];

static void Main(string[] args) { } }}

Organización física de los tipos

Los tipos se definen en ficheros

Un fichero puede contener múltiples tipos. Cada tipo está definido en un único fichero (declaración y definición

coinciden). No existen dependencias de orden entre los tipos.

Los ficheros se compilan en módulos.

Page 111: Desarrollo Profesional de Aplicaciones c#

En .NET existen dos tipos de módulos de código compilado:

Ejecutables (extensión .exe) Bibliotecas de enlace dinámico (extensión .dll)

Ambos son ficheros que contienen definiciones de tipos de datos. Se diferencian en que sólo los primeros (.exe) disponen de un método especial que sirve de punto de entrada a partir del que es posible ejecutar el código que contienen haciendo una llamada desde la línea de órdenes del sistema operativo.

Los módulos se agrupan en ensamblados o assemblies:

Un ensamblado consiste en un bloque constructivo reutilizable, versionable y autodescriptivo de una aplicación de tipo Common Language Runtime. Los ensamblados proporcionan la infraestructura que permite al motor de tiempo de ejecución comprender completamente el contenido de una aplicación y hacer cumplir las reglas del control de versiones y de dependencia definidas por la aplicación. Estos conceptos son cruciales para resolver el problema del control de versiones y para simplificar la implementación de aplicaciones en tiempo de ejecución.

Un ensamblado es una agrupación lógica de uno o más módulos o ficheros de recursos (ficheros .GIF, .HTML, etc.) que se engloban bajo un nombre común. Un programa puede acceder a información o código almacenados en un ensamblado sin tener que conocer cuál es el fichero en concreto donde se encuentran, por lo que los ensamblados nos permiten abstraernos de la ubicación física del código que ejecutemos o de los recursos que usemos.

Hay dos tipos de ensamblados: ensamblados privados y ensamblados compartidos. También para evitar problemas, se pueden mantener múltiples versiones de un mismo ensamblado. Así, si una aplicación fue compilada usando una cierta versión de un determinado ensamblado compartido, cuando se ejecute sólo podrá hacer uso de esa versión del ensamblado y no de alguna otra más moderna que se hubiese instalado. De esta forma se soluciona el problema del infierno de las DLL.

Referencias

En Visual Studio se utilizan referencias para identificar assemblies particulares, p.ej. compilador de C#

csc HelloWorld.cs /reference:System.WinForms.dll

Los espacios de nombres son una construcción del lenguaje para abreviar nombres, mientras que las referencias son las que especifican qué assembly utilizar.

Un ejercicio

Page 112: Desarrollo Profesional de Aplicaciones c#

Sobre un proyecto nuevo, trabajaremos con dos ficheros de código: uno contendrá el método Main() y el otro un espacio de nombres con una clase que se empelará en Main().

1. Añadir al proyecto un módulo de código (clase). Escribir una clase "útil" e insertarla en un espacio de nombres. Supongamos se llama MiClase.cs

2. Compilar el espacio de nombres y obtener una DLL:

csc /t:library MiClase.cs

3. Escribir en Main() código que use la clase. Supongamos que el fichero se llama Ppal.cs

4. Compilar el módulo principal con el espacio de nombres y crear un ejecutable:

csc /r:MiClase.dll Ppal

Observar el uso de una referencia en la llamada al compilador.

Ejecución de aplicaciones

Gestión de memoria

C# utiliza un recolector de basura para gestionar automáticamente la memoria, lo que elimina quebraderos de cabeza y una de las fuentes más comunes de error pero conlleva una finalización no determinísitica (no se ofrece ninguna garantía respecto a cuándo se llama a un destructor, ni siquiera podemos afirmar que llegue a llamarse el destructor).

Los objetos que deban eliminarse tras ser utilizados deberían implementar la interfaz System.IDisposable (escribiendo en el método Dispose todo aquello que haya de realizarse para liberar un objeto). El método Dispose siempre se invoca al terminar una sentencia using:

public class MyResource : IDisposable { public void MyResource() { // Acquire valuble resource } public void Dispose() { // Release valuble resource } public void DoSomething() { ... }

Page 113: Desarrollo Profesional de Aplicaciones c#

}

using (MyResource r = new MyResource()) { r.DoSomething(); } // se llama a r.Dispose()

Código no seguro

En ocasiones necesitamos tener un control total sobre la ejecución de nuestro código (cuestiones de rendimiento, compatibilidad con código existente, uso de DLLs...), por lo que C# nos da la posibilidad de marcar fragmentos de código como código no seguro (unsafe) y así poder emplear C/C++ de forma nativa: punteros, aritmética de punteros, operadores -> y *, ... sin recolección de basura. La instrucción stackalloc reserva memoria en la pila de manera similar a como malloc lo hace en C o new en C++.

public unsafe void MiMetodo () // Método no seguro{ ... }

unsafe class MiClase // Clase (struct) no segura{ ... } // Todos los miembros son no seguros

struct MiStruct { private unsafe int * pX; // Campo de tipo puntero no seguro ... }

unsafe { // Instrucciones que usan punteros }

En caso de que la compilación se vaya a realizar a través de Visual Studio .NET, la forma de indicar que se desea compilar código inseguro es activando la casilla Proyecto | Propiedades de (proyecto) | Propiedades de Configuración | Generar | Permitir bloques de código no seguro | True.

class StackallocApp { public unsafe static void Main() {

Page 114: Desarrollo Profesional de Aplicaciones c#

const int TAM = 10; int * pt = stackalloc int[TAM]; for (int i=0; i<TAM; i++) pt[i] = i; for(int i=0; i<TAM; i++) System.Console.WriteLine(pt[i]); Console.ReadLine (); } }

Para asegurarnos de que el recolector de basura no mueve nuestros datos tendremos que utilizar la sentencia fixed. El recolector puede mover los datos de tipo referencia, por lo que si un puntero contiene la dirección de un dato de tipo referencia podría apuntar a una dirección incorrecta después de que el recolector de basura trabajara. Si un conjunto de instrucciones se encierra en un bloque fixed se previene al recolector de basura para que no mueva el objeto al que se referencia mientras dura el bloque fixed.

Esta capacidad tiene su coste: al emplear punteros, el código resultante es inseguro ya que éste no se puede verificar. De modo que tendremos que extremar las precauciones si alguna vez tenemos que usar esta capacidad del lenguaje C#.

Preprocesador

C# proporciona una serie de directivas de preprocesamiento con distintas funciones. Aunque se le sigue llamando preprocesador (como en C o C++), el preprocesador no es independiente del compilador y se han eliminado algunas directivas como #include (para mejorar los tiempos de compilación, se utiliza el esquema de lenguajes como Java o Delphi en lugar de los ficheros de cabecera típicos de C/C++) o las macros de #define (para mejorar la claridad del código).

Directiva Descripción

#define, #undef Definición de símbolos para la compilación condicional.

#if, #elif, #else, Compilación condicional.

Page 115: Desarrollo Profesional de Aplicaciones c#

#endif#error, #warning Emisión de errores y avisos.#region, #end Delimitación de regiones.#line Especificación de números de línea.

Aserciones

Las aserciones nos permiten mejorar la calidad de nuestro código. Esencialmente, las aserciones no son más que pruebas de unidad que están incluidas en el propio código fuente. Las aserciones nos permiten comprobar precondiciones, postcondiciones e invariantes. Las aserciones sólo se habilitan cuando se compila el código para depurarlo, de forma que su correcto funcionamiento se compruebe continuamente. Cuando distribuyamos nuestras aplicaciones, las aserciones se eliminan para no empeorar la eficiencia de nuestro código.

El método Assert() comprueba una condición y muestra un mensaje si ésta es falsa. Puede emplearse cualquiera de estas versiones:

public static void Assert(bool) comprueba una condición y envía la pila de llamadas si ésta es falsa.

public static void Assert(bool, string) Comprueba una condición y muestra un mensaje si ésta es falsa.

ublic static void Assert(bool, string, string Comprueba una condición y muestra ambos mensajes si es false.

Compilación condicional: Aserciones

public class Debug { public static void Assert(bool cond, String s) { if (!cond) { throw new AssertionException(s); } }

void DoSomething() { ... Assert((x == y), "X debería ser igual a Y"); ... }}

Documentación

Page 116: Desarrollo Profesional de Aplicaciones c#

A los programadores no les suele gustar documentar código, por lo que resulta conveniente suministrar un mecanismo sencillo que les permita mantener su documentación actualizada. Al estilo de doxygen o Javadoc, el compilador de C# es capaz de generarla automáticamente a partir de los comentarios que el progamador escriba en los ficheros de código fuente. Los comentarios a partir de los cuales se genera la documentación se escriben en XML.

El hecho de que la documentación se genere a partir de los fuentes permite evitar que se tenga que trabajar con dos tipos de documentos por separado (fuentes y documentación) que deban actualizarse simultáneamente para evitar incosistencias entre ellos derivadas de que evolucionen de manera separada ya sea por pereza o por error.

El compilador genera la documentación en XML con la idea de que sea fácilmente legible para cualquier aplicación. Para facilitar su legibilidad a humanos bastaría añaderle una hoja de estilo XSL o usar alguna aplicación específica encargada de leerla y mostrarla de una forma más cómoda para humanos.

Los comentarios XML se denotan con una barra triple (///) y nos permiten generar la documentación del código cuando compilamos con la opción /doc.

csc programa.cs /doc:docum_programa.xml

El formato de los comentarios viene definido en un esquema XML, si bien podemos añadir nuestras propias etiquetas para personalizar la documentación de nuestras aplicaciones. Algunas de las etiquetas predefinidas se verifican cuando generamos la documentación (parámetros, excepciones, tipos...).

Estos comentarios han preceder las definiciones de los elementos a documentar. Estos elementos sólo pueden ser definiciones de miembros, ya sean tipos de datos (que son miembros de espacios de nombres) o miembros de tipos datos, y han de colocarse incluso incluso antes que sus atributos.

Etiqueta XML Descripción<summary> Descripción breve de tipos y miembros.<remarks> Descripción detallada de tipos y miembros.<para> Delimita párrafos.<example> Ejemplo de uso.<see> <seealso> Referencias cruzadas. Usa el atributo cref

<c> <code> Código de ejemplo (verbatim).<param> Parámetros de métodos. Usa el atributo name.

<paramref> Referencia a parámetros de metodos. Usa el atributo name.

<returns> Valor devuelto por el método.<exception> Descripciçon de Excepciones.

Page 117: Desarrollo Profesional de Aplicaciones c#

<value> Descripción de propiedades.<list> Generar listas. Usa el atriibuto (opcional) type.

<item> Generar listas. Usa el atriibuto (opcional) type (puede ser: bullet, number o table).

<permission> Permisos.

Veamos un ejemplo detallado:

using System;

namespace Geometria {

/// <summary> /// Clase Punto. /// </summary> /// <remarks> /// Caracteriza a los puntos de un espacio bidimensional. /// Tiene múltiples aplicaciones.... /// </remarks>

class Punto {

/// <summary> /// Campo que contiene la coordenada X de un punto /// </summary> /// <remarks> /// Es de solo lectura /// </remarks> public readonly uint X;

/// <summary> /// Campo que contiene la coordenada Y de un punto /// </summary> /// <remarks> /// Es de solo lectura /// </remarks> public readonly uint Y;

/// <summary> /// Constructor de la clase /// </summary> /// <param name="x">Coordenada x</param> /// <param name="y">Coordenada y</param> public Punto(uint x, uint y) { this.X=x; this.Y=y; }

Page 118: Desarrollo Profesional de Aplicaciones c#

} // fin de class Punto

/// <summary> /// Clase Cuadrado. Los objetos de esta clase son polígonos /// cerrados de cuatro lados de igual longitud y que /// forman ángulos rectos. /// </summary> /// <remarks> /// Los cuatro vértices pueden numerarse de manera que /// el vértice 1 es el que tiene los menores valores de /// las coordenadas X e Y. /// /// Los demás vértices se numeran a partir de éste recorriendo /// el cuadrado en sentido antihorario. /// </remarks>

class Cuadrado { /// <summary> /// Campo que contiene las coordenadas del vértice 1. /// </summary> /// protected Punto vertice1;

/// <summary> /// Campo que contiene la longitud del lado. /// </summary> protected uint lado;

/// <summary> /// Constructor de la clase. /// Construye un cuadrado a partir del vértice 1 y de /// la longitud del lado. /// </summary> /// <param name="vert1">Coordenada del vértice 1</param> /// <param name="lado">Longitud del lado</param> /// public Cuadrado(Punto vert1, uint lado) { this.vertice1=vert1; this.lado=lado; }

/// <summary> /// Constructor de la clase. /// Construye un cuadrado a partir de los vértices 1 y 3. /// </summary> /// <param name="vert1">Coordenada del vértice 1</param>

Page 119: Desarrollo Profesional de Aplicaciones c#

/// <param name="vert3">Coordenada del vértice 3</param> /// <remarks> /// Habría que comprobar si las componentes del vértice 3 /// son mayores o menores que las del vértice1. /// Vamos a presuponer que las componentes del vértice 3 son /// siempre mayores que las del uno. /// </remarks> public Cuadrado(Punto vert1, Punto vert3) { this.vertice1=vert1; this.lado=(uint) Math.Abs(vert3.X-vert1.X); }

/// <summary> /// Propiedad que devuelve el punto que representa a /// las coordenadas del vértice 1. /// </summary> public Punto Vertice1 { get { return this.vertice1; } }

/// <summary> /// Propiedad que devuelve el punto que representa a /// las coordenadas del vértice 2. /// </summary> public Punto Vertice2 { get { Punto p=new Punto(this.vertice1.X + this.lado,this.vertice1.Y); return p; } }

......

} // Fin de class Cuadrado

} // Fin de namespace Geometria

namespace PruebaGeometria { using Geometria; class GeometriaApp { ....

Para generar la documentación en Visual Studio .NET seleccionaremos el proyecto en el explorador de soluciones y daremos el nombre del fichero XML que contendrá la documentación: Ver | Páginas de propiedades | Propiedades de configuración |

Page 120: Desarrollo Profesional de Aplicaciones c#

Generar | Archivo de documentación XML y darle el nombre: DocumentacionGeometia, por ejemplo.

Para ver el resultado: Herramientas | Generar páginas Web de comentarios. Unos ejemplos:

Page 121: Desarrollo Profesional de Aplicaciones c#