193
Revisar estas caracteristicas: This, void, namespace, new, public, private, class, static, for, if, =, == Curso de C# Introducción al lenguaje de programación C# Características del lenguaje C# Aspectos Léxicos E/S básica Tipos de datos Variables y constantes Operadores y expresiones Estructuras de control 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

Curso de C Sharp

Embed Size (px)

Citation preview

Page 1: Curso de C Sharp

Revisar estas caracteristicas:

This, void, namespace, new, public, private, class, static, for, if, =, ==

Curso de C#

Introducción al lenguaje de programación C#

Características del lenguaje C#Aspectos LéxicosE/S básicaTipos de datosVariables y constantesOperadores y expresionesEstructuras de control

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.

Page 2: Curso de C Sharp

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 homogéneo. 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 modificadorvirtual.

Delega la gestión de memoria. Como todo lenguaje de .NET, la gestión de la memoria se realiza automáticamente ya que tiene a su disposición el recolector de basura del CLR. Esto hace que el programador se desentienda de la gestión directa de la memoria (petición y liberación explícita) evitando que se cometan los errores habituales de este tipo de gestión en C++, por ejemplo.

En principio, en C# todo el código incluye numerosas restricciones para asegurar su seguridad no permite el uso de punteros, por ejemplo. Sin embargo, y a diferencia de Java, en C# es posible saltarse dichas restricciones manipulando objetos a través de punteros. Para ello basta marcar regiones de código como inseguras (modificador unsafe) y podrán usarse en ellas punteros de forma similar a cómo se hace en C++, lo que puede resultar vital para situaciones donde se necesite una eficiencia y velocidad procesamiento muy grandes.

Emplea un sistema de tipos unificado. Todos los tipos de datos (incluidos los definidos por el usuario) siempre derivarán, aunque sea de manera implícita, de una clase base común llamada System.Object, por lo que dispondrán de todos los miembros definidos en ésta clase. Esto también es aplicable, lógicamente, a los tipos de datos básicos.

Proporciona seguridad con los tipos de datos. C# no admiten ni funciones ni variables globales sino que todo el código y datos han de definirse dentro de definiciones de tipos de datos, lo que reduce problemas por conflictos de nombres y facilita la legibilidad del código.

Page 3: Curso de C Sharp

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 compatibleso 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 loscomponentes 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

Page 4: Curso de C Sharp

conversiones de tipo (castings) no seguras, gestión de versiones, etc. ayudan a desarrollar software fácilmente mantenible y poco propenso a errores.

Además, no hay que olvidar el aspecto económico: la posibilidad de utilizar C++ puro (código no gestionado o inseguro), la facilidad de interoperabilidad (XML, SOAP, COM, DLLs...) junto con un aprendizaje relativamente sencillo (para los que ya conocen otros lenguajes de programación) hace que el dominio y uso del lenguaje junto a otras tecnologías sea muy apreciado.

Aspectos Léxicos

En esta sección presentaremos las reglas sintácticas básicas que deben cumplir los programas escritos en C# y veremos los elementos fundamentales de cualquier programa en C# (identificadores, comentarios, palabras reservadas, etc.). Se trata, en 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.

Page 5: Curso de C Sharp

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 está encapsulado en clases, incluso la función Main que es un método (especial) del objeto aplicación. En el ejemplo 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.

Cuando este programa se compile y se proceda a su ejecución, la 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 objetoConsole. 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,

Page 6: Curso de C Sharp

mundo! y se cerraría inmediatamente la consola, por lo que no podríamos contemplar el resultado de la ejecución. La última instrucción detiene la ejecución del programa hasta que se introduce una cadena (pulsar ENTER es suficiente) y a continuación termina la ejecución del programa y se cierra la consola. Así, cuando usemos aplicaciones de consola siempre terminaremos con esta instrucción.

A continuacón detallaremos ciertos aspectos léxicos importantes de los programas escritos en C#, algunos de los cuales ya se han comentado brevemente como explicación al programa introductorio.

Comentarios

Los comentarios tienen como finalidad ayudar a comprender el código fuente y están destinados, por lo tanto, a los programadores. No tienen efecto sobre el código ejecutable ya que su contenido es ignorado por el compilador (no se procesa). La sintaxis de los comentarios en C# es idéntica a la de C++ y se distinguen dos tipos de comentarios:

Comentarios de línea. Están precedidos de la construcción // y su efecto (ámbito) termina en la línea en la que está inmerso.

Comentarios de formato libre. Están delimitados por las construcciones /* y */ y pueden extenderse por varias líneas.

Ejemplos de comentarios

// En una línea, al estilo de C++

/* En múltiples líneas, como se viene haciendo desde "los tiempos de C" */

/* Este tipo de comentario ya no es habitual */

Identificadores

Un identificador es un nombre con el que nos referimos a algún elemento de nuestro programa: una clase, un objeto, una variable, un método, etc. Se imponen algunas restricciones acerca de los nombres que pueden emplearse:

Deben comenzar por una letra letra o con el carácter de subrayado (_), que está permitido como carácter inicial (como era tradicional en el lenguaje C).

No pueden contener espacios en blanco. Pueden contener caracteres Unicode, en particular secuencias de

escape Unicode. Son sensibles a mayúsculas/minúsculas. No pueden coincidir con palabras reservadas (a no ser que tengan el

prefijo @ que habilita el uso de palabras clave como identificadores).

Page 7: Curso de C Sharp

Los identificadores con prefijo @ se conocen como identificadores literales. Aunque el uso del prefijo @ para los identificadores que no son palabras clave está permitido, no se recomienda por regla de estilo.

Palabras reservadas

Las palabras reservadas son identificadores predefinidos reservados que tienen un significado especial para el compilador por lo que no se pueden utilizar como identificadores en un programa a menos que incluyan el carácter @ como prefijo.

Las palabras reservadas en C# son, por orden alfabético:

abstract, as, base, bool, break, byte, case, catch, char, checked, class, const, continue, decimal, default, delegate, do,double, else, enum, event, explicit, extern, false, finally, fixed, float, for, foreach, goto, if, implicit, in, int,interface, internal,  is, lock, long, namespace, new, null, object, operator, out, override, params, private, protected,public, readonly, ref, return, sbyte, sealed, short, sizeof, stackalloc, static, string, struct, switch, this, throw, true,try, typeof, uint, ulong, unchecked, unsafe, ushort, using, virtual, void, volatile, while

Literales

Un literal es una representación en código fuente de un valor. Todo literal tiene asociado un tipo, que puede ser explícito (si se indica en el literal, mediante algún sufijo, por ejemplo) o implícito (se asume uno por defecto).

Los literales pueden ser:

Literales lógicos.

Existen dos valores literales lógicos: true y false. El tipo de un literal lógico es bool.

Literales enteros.

Permiten escribir valores de los tipos enteros: int, uint, long y ulong. Los literales enteros tienen dos formatos posibles: decimal y hexadecimal. Los literales hexadecimales tienen el sufijo 0x.

El tipo de un literal entero se determina como sigue:

o Si no tiene sufijo, su tipo es int.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.

Page 8: Curso de C Sharp

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 // double1m, 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

Page 9: Curso de C Sharp

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

Page 10: Curso de C Sharp

@"!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 distribuidados" // en dos lineas.

Literal null.

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

Órdenes

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

E/S básica

Las operaciones de entrada y salida tienen como objetivo permitir que el usuario pueda introducir información al programa (operaciones de entrada) y que pueda obtener información de éste (operaciones de salida). En definitiva, tratan de la comunicación entre el usuario y el programa.

La manera más simple de comunicación es mediante la consola. La consola ha sido el modo tradicional de comunicación entre los programas y el usuario por su simplicidad. Las aplicaciones basadas en ventanas resultan mucho más atractivas y cómodas para el usuario y es éste, sin duda, el tipo de comunicación que deberemos emplear para productos finales. Los programas que no requieran una mucha interacción con el usuario, no obstante, se construyen y se ponen en explotación mucho más rápidamente si se utiliza la consola como medio de comunicación.

Aplicaciones en modo consola

Estas aplicaciones emplean la consola para representar las secuencias de entrada, salida (y error) estándar.

Una aplicación de consola se crea en Visual Studio .NET seleccionando Archivo, Nuevo y Proyecto. Cuando aparece la ventana Nuevo proyecto se selecciona  Proyectos de Visual C# y  Aplicación de consola:

Page 11: Curso de C Sharp

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

Page 12: Curso de C Sharp

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.

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

Page 13: Curso de C Sharp

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 Windows

Una aplicación basada en ventanas (aplicación Windows, en lo que sigue) utilizan ventanas y componentes específicos para interactuar con el usuario. Las peticiones de datos se realizan con componentes de entrada de texto (por ejemplo, con un TextBox) o mediante la selección en una lista de posibilidades (por ejemplo, con un ComboBox). Las salidas pueden realizarse de múltiples maneras, empleando componentes Label, ventanas de mensajes MessageBox, etc.

Por ejemplo, en la figura siguiente mostramos una aplicación que responde mostrando una ventana de mensaje (MessageBox) cuando se pincha sobre el botón titulado Saludo. Basta con ejecutar este código cada vez que se pinche en dicho botón: 

MessageBox.Show ("¡Hola, mundo!", "Un saludo típico"); (en realidad, System.Windows.Forms.MessageBox.Show (...);)

Una aplicación más compleja podría pedir el nombre del usuario en un componente TextBox y mostrarlo empleando un componente Label cuando se pincha en el botón titulado Saludo:

Page 14: Curso de C Sharp

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

Los tipos de datos ofrecidos por C# al programador forman parte de un sistema unificado en el que todos los tipos de datos (incluidos los definidos por el usuario) derivan, aunque sea de manera implícita, de la clase System.Object. Por herencia dispondrán de todos los miembros definidos en ésta clase, en particular los útiles métodos Equals(), GetHashCode(), GetType() y ToString() que describiremnos más adelante.

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

Page 15: Curso de C Sharp

La variable contiene un valor La variable contiene una referencia

El dato se almacena en la pila El dato se almacena en el heap

El dato siempre tiene valor El dato puede no tener valor null

Una 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 valor

Los tipos básicos son tipos valor. Si una variable es de un tipo valor contiene únicamente un valor del tipo del que se ha declarado.

Los tipos predefinidos de C# son tipos disponibles en la plataforma .NET y que, por comodidad, en C# se emplean usando un alias. En la tabla siguiente enumeramos los tipos simples detallando su nombre completo, su alias, una breve descripción, el número de bytes que ocupan y el rango de valores.

Nombre (.NET Framework)

AliasDescripci

ón

Tamaño

(bytes)Rango

System.Sbyte sbyte Bytes con signo

1 -128 ... 127

System.Int16 short Enteros cortos

2 -32.768 ... 32.767

System.Int32 int Enteros 4-2.147.483.648 ... 2.147.483.647

System.Int64 long Enteros largos

8

-9.223.372.036.854.775.808 ... 9.223.372.036.854.775.807

System.Byte byte Bytes (sin signo)

1 0 ... 255

System.Uint16 ushort

Enteros cortos (sin signo)

2 0 ... 65.535

System.UInt3 uint Enteros (sin signo)

4 0 ... 18.446.744.073.709.551

Page 16: Curso de C Sharp

2 .615

System.Uint64 ulong

Enteros largos (sin signo)

80 ... 18.446.744.073.709.551.615

System.Single

float Reales (7 decimales)

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

System.Double double

Reales (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 char Caracteres Unicode

2Cualquier carácter Unicode

System.Boolean

bool Valores 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 referencia

Si un dato es de un tipo referencia contiene la dirección de la información, en definitiva, una referencia al objeto que contiene los datos y/o métodos. En definitiva, distinguimos:

La referencia o un nombre por el que nos referimos al objeto y que utilizamos para manipularlo.

El objeto referenciado, que ocupa lugar en memoria (en el heap) y que almacenará el valor efectivo del objeto.

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

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

Page 17: Curso de C Sharp

En C++ indica que se pide memoria dinámica. En C# indica que se llama al constructor de una clase.

El efecto, no obstante, es similar ya que como la variable es de un tipo referencia, al llamar al constructor se aloja memoria en el heap de manera implícita.

Considere el siguiente fragmento de código, en el que todas las variables son del mismo tipo: ObjetoDemo).

Tipos referencia

class ObjetoDemo{ public int Valor;}

class AppDemoRef{

static void Main(string[] args) { ObjetoDemo o1 = new ObjetoDemo(); // new llama a un constructor o1.Valor = 10; ObjetoDemo o2 = new ObjetoDemo(); // new llama a un constructor o2 = o1; // La memoria que ocupaba el objeto refernciado por "o2" // se pierde: actuará el recolector de basura. PintaDatos ("o1", "o2", o1, o2);

ObjetoDemo o3 = new ObjetoDemo();// new llama a un constructor o3.Valor = 10; ObjetoDemo o4 = o3; // "o4" contiene la misma 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)

Page 18: Curso de C Sharp

{ Console.Write ("{0} = {1}, {2} = {3} ", st1, ob1.Valor, st2, ob2.Valor); if (ob1==ob2) Console.WriteLine ("{0} == {1}", st1, st2); else Console.WriteLine ("{0} != {1}", st1, st2); }}

El tipo string es un tipo especial de tipo referencia. De hecho, parece más un tipo valor ante la asignación. Observe el ejemplo:

string s1 = "Hola";string s2 = s1;

En este punto s2 referencia al mismo objeto que s1. Sin embargo, cuando el valor de s1 es modificado, por ejemplo con:

s1 = "Adios";

lo que ocurre es que se crea un nuevo objeto string referenciado por s1. De esta forma, s1 contiene "Adios" mientras que s2 mantiene el valor"Hola". Esto es así porque los objetos string son immutables, por lo que, para cambiar lo que referencia una variable string debe crearse un nuevo objeto string.

Variables y constantes

Variables

Una variable permite el almacenamiento de datos en la memoria. Es una abstracción que permite referirnos a una zona de memoria mediante unnombre (su identificador). Todas las variables tienen asociadas un tipo que determina los valores que pueden almacenarse y las operaciones que pueden efectuarse con los datos de ese tipo. Además, el término variable indica que el contenido de esa zona de memoria puede modificarse durante la ejecución del programa.

Nombres de variables

Page 19: Curso de C Sharp

Los nombres que pueden asignarse a las variables deben regirse por unas normas básicas:

Pueden contener letras, dígitos y el caracter de subrayado (_). No pueden empezar con un número: deben comenzar por una letra

letra o con el carácter de subrayado (_).

Finalmente, recordar que, como identificador que es, el nombre de una variable es sensible a las mayúsculas y no pueden coincidir con una palabra reservada a no ser que tenga el prefijo @, aunque no es una práctica recomendada.

Declaración de variables

Antes de usar una variable se debe declarar. La declaración de una variable indica al compilador el nombre de la variable y su tipo. Una declaración permite que se pueda reservar memoria para esa variable y restringir el espacio (cantidad de memoria) que requiere, los valores que pueden asignarsele y las operaciones en las que puede intervenir.

La sintaxis de una declaración es sencilla: tan sólo hay que especificar el tipo de la variable y el nombre que se le asocia. La declaración debe concluir con el carácter punto y coma. Por ejemplo, si vamos a emplear una variable para guardar en ella el valor del área de un círculo debemos:

Darle un nombre significativo: Area Asociarle un tipo: dado que puede tener decimales y no se requiere

una gran precisión, bastará con el tipo float.

float Area;

Cuando se van a declarar múltiples variables del mismo tipo no es necesario que cada declaración se haga por separado, pueden agruparse en la misma línea compartiendo el tipo. Por ejemplo, las declaraciones:

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;

Page 20: Curso de C Sharp

No existe ninguna zona predeterminada en el código para la declaración de variables, la única restricción es que la declaración debe realizarse antes de su uso.

No es conveniente abusar de la declaración múltiple de variables en una línea. Desde el punto de vista de la legibilidad es preferible, por regla general, que cada variable se declare separadamente y que la declaración vaya acompañada de un breve comentario:

float Radio; // Radio del circulo del cual se calcula el area.float Area; // Area del circulo

Acceso a variables

Una variable se usa para asignarle un valor (acceso de escritura) o para utilizar el valor almacenado (acceso de lectura).

Una vez declarada una variable debe recibir algún valor (es su misión, después de todo). Este valor lo puede recibir de algún dispositivo (flujo de entrada) o como resultado de evaluar una expresión. La manera más simple de proporcionar un valor a una variable es emplear la instrucción de asignación:

Radio = 10.0F;

En el ejemplo se asigna el valor (literal entero) 10 a la variable Radio. El valor que tuviera almacenado la variable Radio se pierde, quedando fijado a 10.

En la misma línea de la declaración puede asignarse un valor a la variable, por lo que declaración e inicialización:

float Radio; // 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;

Page 21: Curso de C Sharp

La variable Radio (su valor) se emplea en la expresión 2 * 3.1416 * Radio * Radio para calcular el área de un círculo de radio Radio. Una vez calculado el valor de la expresión, éste se asigna a la variable Area.

Otra manera de acceder al valor de una variable para lectura es emplearla como el argumento de una instrucción de escritura WriteLine. Por ejemplo, 

Console.WriteLine(Area);mostrará en la consola el valor de la variable Area.

Leer el valor de una variable no modifica el contenido de la variable.

A modo de resumen, un programa que calcula y muestra el área de un círulo de radio 10 es el siguiente:

Calculo del área de un círculo (1)

using System;

class Area1{ static void Main(string[] args) { float Radio = 10.0F; float Area = 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'}

Constantes

Una constante se define de manera parecida a una variable: modeliza una zona de memoria que puede almacenar un valor de un tipo determinado. La diferencia reside en que esa zona de memoria (su contenido) no puede modificarse en la ejecución del programa. El valor de la constante se asigna en la declaración.

Page 22: Curso de C Sharp

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.1416F;

Solo se puede consultar el valor de una constante, nunca se debe intentar modificarlo porque se produciría un error en tiempo de compilación. Por ejemplo, la siguiente instrucción: 

Area = 2 * PI * Radio * Radio; utiliza la constante PI declarada anteriormente, usando su valor para evaluar una expresión.

Podemos modificar el programa anterior para que utilice la constante PI:

Calculo del área de un círculo (2)

using System;

class Area2{ static void Main(string[] args) { const float PI = 3.1416F; float Radio = 10.0F; float Area = 2 * PI * Radio * Radio; Console.WriteLine(Area); Console.ReadLine(); }}

Ámbito de variables y constantes

El ámbito (del inglés scope) de una variable y/o constante indica en qué partes del código es lícito su uso.

En C# el ámbito abarca desde el lugar de su declaración hasta donde termina el bloque en el que fue declarada.

En el ámbito de una variable no puede declararse otra variable con el mismo nombre (aunque sea en un bloque interno, algo que si está permitido en C++):

static void Main(string[] args) { float Radio = 10.0F; ... if (Radio > 0){ float Radio = 20.0F; // Error: No se puede

Page 23: Curso de C Sharp

declarar una variable // local denominada 'Radio' en este ámbito. } ... }

Operadores y expresiones

Un operador está formado por uno o más caracteres y permite realizar una determinada operación entre uno o más datos y produce un resultado. Es una manera simbólica de expresar una operación sobre unos operandos.

C# proporciona un conjunto fijo, suficiente y completo de operadores. El significado de cada operador está perfectamente definido para los tipos predefinidos, aunque algunos de ellos pueden sobrecargarse, es decir, cambiar su significado al aplicarlos a un tipo definido por el usuario.

C# dispone de operadores aritméticos, lógicos, relacionales, de manipulación de bits, asignación, para acceso a tablas y objetos, etc. Los operadores pueden presentarse por diferentes criterios, por ejemplo, por su funcionalidad:

Categorías Operadores

Aritméticos+ - * / %

Lógicos (booleanos y bit a bit)& | ^ ! ~ && ||

Concatenación de cadenas +

Incremento y decremento ++ --

Desplazamiento << >>

Relacionales == != < > <= >=

Asignación = += -= *= /= %= &=|= ^= <<= >>=

Acceso a miembros .

Acceso por índices []

Conversión de tipos explícita ()

Conditional ? :

Creación de objetos new

Información de tipos as is sizeof typeof

Control de excepciones de checked unchecked

Page 24: Curso de C Sharp

desbordamiento

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

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 desplazamiento 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 (<<)

Page 25: Curso de C Sharp

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

Page 26: Curso de C Sharp

todas las clases desde System.Object (todas las clases heredan, en última instancia de System.Object).

El operador de acceso a miembros es el operador punto (.) y se emplea para acceder a los miembros (componentes) de una clase, estructura o espacio de nombres.

El operador de acceso por índices es el tradicional, formado por la pareja de caracteres [ y ]. En C# también se emplea para especificar atributos.

El operador de conversión explícita de tipos (casting) es el clásico, formado por la pareja de caracteres ( y ).

El operador ternario condicional evalúa una condición lógica y devuelve uno de dos valores.

Se utiliza en expresiones de la forma:

cond ? expr1 : expr2

de manera que si cond es verdad, se evalúa expr1 y se devuelve como resultado; si cond es falsa se evalúa expr1 y se devuelve como resultado.

Creación de objetos (new). El operador new permite crear instancias de una clase definida por el usuario. Por ejemplo: ObjetoDemo o1 = new ObjetoDemo() declara una referencia (llamada o1) a un objeto de la clase ObjetoDemo y crea una instancia de esa clase, referenciada por o1. En realidad, la creación de un objeto implica la llamada a un constructor de la clase.

Control de excepciones de desbordamiento: checked y unchecked

C# proporciona la posibilidad de realizar operaciones de moldeado (casting) y aritméticas en un contexto verificado. En otras palabras, el entorno de ejecución .NET detecta cualquier situación de desbordamiento y lanza una excepción (OverFlowException) si ésta se manifiesta.

En un contexto verificado (checked), si una expresión produce un valor fuera del rango del tipo de destino, el resultado depende de si la expresión es constante o no. Las expresiones constantes producen errores de compilación, mientras que las expresiones no constantes se evalúan en tiempo de ejecución y producen excepciones.

Si no se especifican ni checked ni unchecked, las expresiones constantes utilizan la verificación de desbordamiento predeterminada en tiempo de compilación, que es checked. De lo contrario, si la expresión no es constante, la verificación de desbordamiento en tiempo de ejecución depende de otros factores tales como las opciones del compilador y la configuración del

Page 27: Curso de C Sharp

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: -2

Si añadimos la función:

public static int Ch() { int z = checked((short)(x + y)); return z; }

y la llamada: 

Console.WriteLine("Valor -checked- : {0}", Ch());

la ejecución del programa provoca el lanzamiento de una excepción System.OverflowException que detiene la ejecución del programa, al no estar controlada.

Page 28: Curso de C Sharp

Operadores especiales: as is typeof

El operador as

El operador as se utiliza para realizar conversiones entre tipos compatibles (al estilo de los casts dinámicos de C++). El operador as se utiliza en expresiones de la forma:

<expresion> as type

donde <expresion> es una expresión de un tipo de referencia, y type es un tipo de referencia. Si la conversión de tipo no es posible, el resultado es null.

// 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 string4:no es string5:no es string

Page 29: Curso de C Sharp

En el siguiente ejemplo, la función DoSomething recibe cualquier argumento, pero sólo cuando es de tipo Car se ejecuta el método Drive sobre el objeto c (de clase Car).

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

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

El operador is

El operador is se utiliza para comprobar en tiempo de ejecución si el tipo de un objeto es compatible con un tipo dado. El operador is se utiliza en expresiones de la forma:

<expresion> is type

donde <expresion> es una expresión de un tipo de referencia, y type es un tipo de referencia.

El siguiente ejemplo realiza la misma tarea que comentamos en el ejemplo sobre el operador as solo que ahora no hay ningún objeto local con referencia explícita sobre el que se ejecute el método Drive.

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

El operador typeof

El operador typeof devuelve el objeto derivado de System.Type correspondiente al tipo especificado.

Una expresión typeof se presenta de la siguiente forma:

typeof (tipo)

donde tipo es el tipo cuyo objeto System.Type se desea obtener.

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

Page 30: Curso de C Sharp

Console.WriteLine(typeof(float).Module);Console.WriteLine(typeof(double).IsPublic);Console.WriteLine(typeof(Point).MemberType);...

Un ejemplo en el que se emplean los operadores is, as y typeof para discriminar el tipo del argumento recibido en una función polimórfica y trabajar con él es el siguiente:

// Ejempo de uso de los operadores is, as y 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) { Console.WriteLine ("\no es de Clase1");

a = (Clase1)o; Console.WriteLine ("--> {0}", typeof(Clase1).FullName); t = typeof(Clase1); }

else if (o is Clase2) { Console.WriteLine ("\no es de Clase2");

b = o as Clase2; t = typeof(Clase2); }

else

Page 31: Curso de C Sharp

{ Console.WriteLine ("\no no es ni de Clase1 ni de Clase2."); t = o.GetType(); } Console.WriteLine ("Su tipo es " + t.FullName); }

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--> Clase1Su tipo es Clase1

o es de Clase2Su tipo es Clase2

o no es ni de Clase1 ni de Clase2.Su tipo es System.String

Precedencia

También podrían presentarse por precedencia. En la tabla siguiente los enumeramos de mayor a menor precedencia:

Categorías Operadores

Primarios Paréntesis: (x)

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

Page 32: Curso de C Sharp

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

Conversión de tipo: as

Igualdad ==

Desigualdad !=

Page 33: Curso de C Sharp

Bitwise AND &

Bitwise XOR ^

Bitwise OR |

Logical AND &&

Logical OR ||

Condicional ternario ?:

Asignación =, *=, /=, %=, +=, -=, <<=, >>=, &=, ^=, |=

Asociatividad

Como siempre, es mejor utilizar paréntesis para controlar el orden de evaluación

x = y = z se evalúa como x = (y = z)

x + y + z se evalúa como (x + y) + z

Estructuras de control

Las estructuras de control de C# son similares a las de C y C++. La diferencia más notable radica en que la instrucción condicional if y los ciclos while y do están controlados por una expresión lógica (tipo  Boolean).

Esta restricción hace que las instrucciones sean más seguras al evitar posibles fuentes de error, o al menos, facilitan la legibilidad del código. Por ejemplo, en la siguiente instrucción (válida en C++):

if (a)

la expresión a podría ser una expresión boolean pero también de tipo int,  char,  float *... y la condición se evalúa como true cuando a es distinto de cero (valor entero 0, carácter 0 ó puntero nulo, en los ejemplos). En C# se clarifica esta ambigüedad y sólo se admiten expresiones lógicas. De esta manera, la instrucción anterior será válida sólo cuando a sea una expresión boolean.

A modo de resumen, las características propias de las estructuras de control de C# son:

goto no puede saltar dentro de un bloque (da igual, de todas formas no lo usaremos NUNCA).

switch funciona como en Pascal (no como en C). Se añade una nueva estructura de control iterativa: foreach.

Estructuras condicionales

Page 34: Curso de C Sharp

if, if-else

La estructura condicional tiene la sintaxis clásica, con la diferencia indicada anteriormente acerca del tipo de la expresión. Si debe ejecutar más de una instrucción, se encierran en un bloque, delimitado por las llaves { y }.

Si sólo se actúa cuando la condición es cierta:

if (a > 0) 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"); 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++; } }

Page 35: Curso de C Sharp

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.

switch

La estructura de selección múltiple switch funciona sobre cualquier tipo predefinido (incluyendo string) o enumerado (enum) y debe indicar explícitamente cómo terminar cada caso (generalmente, con  break  en situaciones "normales" ó  throw  en situaciones "anormales", aunque es posible -pero no recomendable- emplear  goto case  ó  return ):

using System;

class HolaMundoSwitch { public static void Main(String[] args) { if (args.Length > 0) switch(args[0]) { case "José": Console.WriteLine("Hola José. Buenos días"); break; case "Paco": Console.WriteLine("Hola Paco. Me alegro de verte"); break; default: Console.WriteLine("Hola {0}", args[0]); break; } else Console.WriteLine("Hola Mundo"); } }

Especificar los parámetros al programa en Visual Studio: Utilizar el explorador de soluciones para configurar las propiedades del proyecto.

Page 36: Curso de C Sharp

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

Page 37: Curso de C Sharp

Console.WriteLine (Test("C")); // 1 Console.WriteLine (Test("?")); // 0 Console.ReadLine(); } }}

Estructuras repetitivas

Las estructuras repetitivas de C# (while, do...while, for) no presentan grandes diferencias respecto a las de otros lenguajes, como C++. La aportación fundamental es la de la estructura iterativa en colecciones foreach.

while

int i = 0;

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

do...while

int i = 0;

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

for

int i;

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

foreach

Un ciclo foreach itera seleccionando todos los miembros de un vector, matriz u otra colección sin que se requiera explicitar los índices que permiten acceder a los miembros.

El siguiente ejemplo muestra todos los argumentos recibidos por el programa cuando se invoca su ejecución (argumentos en la línea de órdenes):

Page 38: Curso de C Sharp

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

Saltos

goto

Aunque el lenguaje lo permita, nunca escribiremos algo como lo que aparece a continuación:

using System;

namespace DemoGoto{ class MainClass { static void Busca(int val, int[,] vector, out int fil, out int col)

Page 39: Curso de C Sharp

{ int i, j; for (i = 0; i < vector.GetLength(0); i++) for (j = 0; j < vector.GetLength(1); j++) if (vector[i, j] == val) goto OK; throw new InvalidOperationException("Valor no encontrado"); OK: fil = i; col = j; }

static void Main(string[] args) { int [,] coleccion = new int [2,3] {{1,0,4},{3,2,5}}; int f,c;

int valor = Convert.ToInt32(args[0]);

Busca (valor, coleccion, out f, out c); Console.WriteLine ("El valor {0} esta en [{1},{2}]", valor,f,c); Console.ReadLine(); } }}

break

Lo usaremos únicamente en sentencias switch.

continue

Mejor no lo usamos.

return

Procuraremos emplearlo sólo al final de un método para facilitar la legibilidad del código.

Page 40: Curso de C Sharp

Curso de C#

Tipos de datos

Tipos básicosEl sistema unificado de tipos. El tipo ObjectCadenas de caracteresVectores y matricesEstructurasEnumeraciones

Cuando definimos un objeto debemos especificar su tipo. El tipo determina qué valores puede almacenar ese objeto (clase y rango) y las operaciones que pueden efectuarse con él.

Como cualquier lenguaje de programación, C# proporciona una serie de tipos predefinidos (int, byte, char, string, ...) y mecanismos para que el usuario cree sus propios tipos (class y struct).

La estructura de tipos de C# es una gran novedad ya que establece una relación jerárquica entre éstos, de manera que todos los tipos son clases y se construyen por herencia de la clase base Objet. Esta particularidad hace que la creación y gestión de tipos de datos en C# sea una de las principales novedades y potencialidades del lenguaje.

Tipos básicos

Los tipos de datos básicos son los tipos de datos más comúnmente utilizados en programación.

Los tipos predefinidos en C# están definidos en el espacio de nombres System, que es el espacio de nombres más numeroso (e importante) de la plataforma .NET. Por ejemplo, para representar números enteros de 32 bits con signo se utiliza el tipo de dato System.Int32 y a la hora de crear un objeto a de este tipo que represente el valor 2 se usa la siguiente sintaxis:

System.Int32 a = 2;

Page 41: Curso de C Sharp

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ímbolo

sbyte System.Sbyte entero, 1 byte con signo

byte System.Byte entero, 1 byte sin signo

short System.Short entero, 2 bytes con signo

ushort System.UShort entero, 2 bytes sin signo

int System.Int32 entero, 4 bytes con signo

uint System.UInt32 entero, 4 bytes sin signo U

long System.Int64 entero, 8 bytes con signo L

ulong System.ULong64 entero, 8 bytes sin signo UL

float System.Single real, IEEE 754, 32 bits F

double System.Double real, IEEE 754, 64 bits D

decimal System.Decimal real, 128 bits (28 dígitos significativos) M

bool System.Boolean (Verdad/Falso) 1 byte

char 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 Object

Page 42: Curso de C Sharp

En C# desaparecen las variables y funciones globales: todo el código y todos los datos de una aplicación forman parte de objetos que encapsulan datos y código (como ejemplo, recuerde cómo Main() es un método de una clase). Como en otros lenguajes orientados a objetos, en C# un tipo puede incluir datos y métodos. De hecho, hasta los tipos básicos predefinidos incluyen métodos, que, como veremos, heredan de la clase base object, a partir de la cual se construyen implícita o explícitamente todos los tipos de datos. Por ejemplo: 

int i = 10; string c = i.ToString();e incluso:  string c = 10.ToString();

Esta manera por la que podemos manipular los datos básicos refleja la íntima relación entre C# y la biblioteca de clase de .NET. De hecho, C# compila sus tipos básicos asociándolos a sus correspondientes en .NET; por ejemplo, hace corresponder al tipo string) con la claseSystem.String, al tipo int) con la clase System.Int32, etc. Así, se confirma que todo es un objeto en C# (por si aún había alguna duda).

Aunque no comentaremos todos los métodos disponibles para los tipos básicos, destacaremos algunos de ellos.

Todos los tipos tienen un método ToString() que devuelve una cadena (string) que representa su valor textual.

char tiene propiedades acerca de su contenido (IsLetter, IsNumber, etc.) además de métodos para realizar conversiones (ToUpper(), ToLower(), etc.)

El tipo básico string dispone, como puede suponerse, de muchos métodos específicos, aún más que la clase string de la biblioteca estándar de C++.

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 yNegativeInfinity, valores que son pueden ser devueltos al realizar ciertos cálculos.

Muchos tipos, incluyendo todos los tipos numéricos, proporcionan el método Parse() que permite la conversión desde un dato string(p.e. double d = double.Parse("20.5"))

Conversiones de tipos

Una conversión de tipo (casting) puede ser implícita o explícita.

Implícitas Explícitas

Page 43: Curso de C Sharp

Ocurren automáticamente Requieren un casting

Siempre tienen éxito Pueden fallar

No 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 object

Por el sistema unificado de tipos de C#, todo es un objeto. C# tiene predefinido un tipo referencia llamado object y cualquier tipo (valor o referencia, predefinido o definido por el usuario) es en última instancia, de tipo object (con otras palabras puede decirse que hereda todas las características de ese tipo).

El tipo object se basa en System.Object de .NET Framework. Las variables de tipo object pueden recibir valores de cualquier tipo. Todos los tipos de datos, predefinidos y definidos por el usuario, heredan de la clase System.Object. La clase Object es la superclase fundamental de todas las clases de .NET Framework; la raíz de la jerarquía de tipos.

Normalmente, los lenguajes no precisan una clase para declarar la herencia de Object porque está implícita.

Por ejemplo, dada la siguiente declaración: 

object o;

Page 44: Curso de C Sharp

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; // boolean

Dado que todas las clases de .NET Framework se derivan de Object, todos los métodos definidos en la clase Object están disponibles en todos los objetos del sistema. Las clases derivadas pueden reemplazar, y de hecho reemplazan, algunos de estos métodos, entre los que se incluyen los siguientes:

public void Object() Inicializa una nueva instancia de la clase Object. Los constructores llaman a este constructor en clases derivadas, pero éste también puede utilizarse para crear una instancia de la clase Object directamente.

public bool Equals(object obj) Determina si el objeto especificado es igual al objeto actual.

protected void Finalize() Realiza operaciones de limpieza antes de que un objeto sea reclamado automáticamente por el recolector de elementos no utilizados.

public int GetHashCode()  Sirve como función hash para un tipo concreto, apropiado para su utilización en algoritmos de hash y estructuras de datos como las tablas hash.

public string ToString() Devuelve un objeto string que representa al objeto actual. Se emplea para crear una cadena de texto legible para el usuario que describe una instancia de la clase. La implementación predeterminada devuelve el nombre completo del tipo del objeto Object.

En el último ejemplo, si cada vez que asignamos un valor a o ejecutamos Console.WriteLine (o.ToString()) o simplementeConsole.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

Page 45: Curso de C Sharp

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-

Boxing (y su operación inversa, unboxing) permiten tratar a los tipos valor como objetos (tipo referencia). Los tipos de valor, incluidos los structy 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));

Boxing

Boxing es una conversión implícita de un tipo valor al tipo object. Cuando se realiza boxing de un valor, se asigna una instancia de objeto y se copia el valor en el nuevo objeto.

Por ejemplo, considere la siguiente declaración de una variable de tipo de valor: 

int i = 123;La siguiente instrucción aplica implícitamente la operación de boxing sobre la variable i:  object o = i;El resultado de esta instrucción es crear un objeto o en la pila que hace referencia a un valor del tipo int alojado en el heap. Este valor es una copia del valor del tipo de valor

Page 46: Curso de C Sharp

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); Console.WriteLine("Valor (tipo object)= {0}", o); }}

El resultado de su ejecución es: Valor (tipo valor) = 456Valor (tipo object)= 123

Unboxing

Una vez que se ha hecho boxing sobre un dato y disponemos de un object no puede hacerse demasiado con él ya que no pueden emplearse métodos o propiedades del tipo original: el compilador no puede conocer el tipo original sobre el que se hizo boxing. 

Page 47: Curso de C Sharp

string s1 = "Hola"; object o = s1; // boxing ... if (o.Lenght > 0) // ERROR

Una vez que se ha hecho boxing puede deshacerse la conversión (unboxing) haciendo casting explícito al tipo de dato inicial. 

string s2 = (string) o; // unboxing

Afortunadamente es posible conocer el tipo, de manera que si la conversión no es posible se lanza una excepción. Pueden utilizarse los operadoresis 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 funciono

Conclusiones

Ventajas del sistema unificado de tipos: las colecciones funcionan sobre cualquier tipo.

Hashtable t = new Hashtable();

t.Add(0, "zero");t.Add(1, "one");t.Add(2, "two");

string s = string.Format("Your total was {0} on {1}", total, date);

Desventajas del sistema unificado de tipos: Eficiencia.

La necesidad de utilizar boxing disminuirá cuando el CLR permita genéricos (algo similar a los templates en C++).

Cadenas de caracteres

Una cadena de caracteres no es más que una secuencia de caracteres Unicode. En C# se representan mediante objetos del tipo string, que no es más que un alias del tipo System.String incluido en la BCL.

El tipo string es un tipo referencia. Representa una serie de caracteres inmutable. Se dice que una instancia de String es inmutable porque no se puede modificar su valor una vez creada. Los métodos que aparentemente modifican una cadena devuelven en realidad una cadena nueva que contiene la modificación.

Page 48: Curso de C Sharp

Recuerde la particularidad de este tipo sobre el operador de asignación: su funcionamiento es como si fuera un tipo valor. Este es, realmente, el funcionamiento obtenido con el método Copy(). Si no se desea obtener una copia (independiente) sino una copia en el sentido de un tipo valor emplee el método Clone().

Puede accederse a cada uno de los caracteres de la cadena mediante índice, como ocurre habitualmente en otros lenguajes, siendo la primera posición asociada al índice cero. Este acceso, no obstante, sólo está permitido para lectura. 

string s = "!!Hola, mundo!!";; for (int i=0; i < s.Length; i++) Console.Write (s[i]);Este ejemplo muestra todos los caracteres que componen la cadena, uno a uno. El ciclo realiza tantas iteraciones como número de caracteres forman la cadena (su longitud) que se consulta usando la propiedad Length.

Por definición, un objeto String, incluida la cadena vacía (""), es mayor que una referencia nula y dos referencias nulas son iguales entre sí. El carácter null se define como el hexadecimal 0x00. Puede consultarse si una cadena es vacía empleando la propiedad estática (sólo lectura)Empty: el valor de este campo es la cadena de longitud cero o cadena vacía, "". Una cadena vacía no es igual que una cadena cuyo valor sea null.

Los procedimientos de comparación y de búsqueda distinguen mayúsculas de minúsculas de forma predeterminada. Pueden emplearse los métodosCompare() 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 deObject. 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 == s5

Page 49: Curso de C Sharp

Un método particularmente útil es Split(), que devuelve un vector de cadenas resultante de "partir" la cadena sobre la que se aplica en palabras:

string linea; string [] palabras; ... palabras = linea.Split (null); // null indica dividir por espacios

En definitiva, la clase String incluye numerosos métodos, que pueden emplease para:

Realizar búsquedas en cadenas de caracteres: IndexOf(), LastIndexOf(),

StartsWith(), EndsWith() Eliminar e insertar espacios en blanco: Trim(), PadLeft(), PadRight() Manipular subcadenas: Insert(), Remove(), Replace(),

Substring(), Join() Modificar cadenas de caracteres: ToLower(), ToUpper(), Format() (al estilo del printf de C, pero seguro).

Vectores y matrices

Un vector (matriz) en C# es radicalmente diferente a un vector (matriz) en C++: más que una colección de variables que comparten un nombre y accesibles por índice, en C# se trata de una instancia de la clase System.Array, y en consecuencia se trata de una colección que se almacena en el heap y que está bajo el control del gestor de memoria.

Todas las tablas que definamos, sea cual sea el tipo de elementos que contengan, son objetos que derivan de System.Array. Ese espacio de nombres proporciona métodos para la creación, manipulación, búsqueda y ordenación de matrices, por lo tanto, sirve como clase base para todas las matrices de la CLR (Common Language Runtime).

En C# las tablas pueden ser multidimensionales, se accede a los elementos por índice, siendo el índice inicial de cada dimensión 0.

Siempre se comprueba que se esté accediendo dentro de los límites. Si se intenta acceder a un elemento de un vector (matriz) especificando un índice fuera del rango, se detecta en tiempo de ejecución y se lanza una excepción IndexOutOfBoundsException.

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

Page 50: Curso de C Sharp

// 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];

Page 51: Curso de C Sharp

for (int i=0; i<tam; i++) VecDin[i]=i;

Console.Write ("Contenido de VecDin = "); foreach (int i in VecDin) Console.Write(i + ", ");

Console.ReadLine();

produce este resultado: Num. casillas: 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 enSystem.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 = particion

Matrices

Las diferencias son importantes respecto a C++ ya que C# permite tanto matrices rectangulares (todas las filas tienen el mismo número de columnas) como matrices dentadas o a jirones.

Page 52: Curso de C Sharp

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

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

Page 53: Curso de C Sharp

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

Estructuras

Una estructura (struct) se emplea para definir nuevos tipos de datos. Los nuevos tipos así definidos son tipos valor (se almacenan en la pila).

La sintaxis para la definición de estructuras es similar a la empleada para las clases (se emplea la palabra reservada struct en lugar de class). No obstante,

La herencia y aspectos relacionados (p.e. métodos virtuales, métodos abstractos) no se admiten en los struct.

Page 54: Curso de C Sharp

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.

Page 55: Curso de C Sharp

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

Enumeraciones

Una enumeración o tipo enumerado es un tipo especial de estructura en la que los literales de los valores que pueden tomar sus objetos se indican explícitamente al definirla.

La sintaxis es muy parecida a la empleada en C++: 

enum State { Off, On };aunque el punto y coma final es opcional ya que una enumeración es una definición de un struct y ésta no requiere el punto y coma:  enum State { Off, On }

Al igual que en C++ y en C, C# numera a los elementos de la enumeración con valores enteros sucesivos, asignando el valor 0 al primero de la enumeración, a menos que se especifique explícitamente otra asignación: 

enum Tamanio { Pequeño = 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: 

Page 56: Curso de C Sharp

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()); // Black

Pueden convertirse explícitamente en su valor entero (como en C). Admiten determinados operadores aritméticos (+,-,++,--) y lógicos a nivel de bits (&,|,^,~). ejemplo: 

Color c2 = Color.White; Console.WriteLine((int) c2); // 7 Console.WriteLine(c2.ToString()); // White

Otro ejemplo más complejo: 

enum ModArchivo{ Lectura = 1, Escritura = 2, Oculto = 4, Sistema = 8} ... ModArchivo st = ModArchivo.Lectura | ModArchivo.Escritura; ... Console.WriteLine (st.ToString("F")); // Lectura, Escritura Console.WriteLine (Enum.Format(typeof(ModArchivo), st, "G")); // 3 Console.WriteLine (Enum.Format(typeof(ModArchivo), st, "X")); // 00000003 Console.WriteLine (Enum.Format(typeof(ModArchivo), st, "D")); // 3

Page 57: Curso de C Sharp

Curso de C#

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.

Datoso camposo constantes y campos de sólo lecturao propiedades

Métodoso métodos generaleso métodos constructoreso 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.

Page 58: Curso de C Sharp

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 { 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";

Page 59: Curso de C Sharp

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 llamadoconstructor 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: "); MiCoche.MuestraCoche(); // LLamada al método "MuestraCoche()"

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

Console.ReadLine ();

Page 60: Curso de C Sharp

} // 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 accesoUn miembro del tipo T definido en el assembly A

es accesible...

public desde cualquier sitio

private (por defecto) sólo desde dentro de T (por defecto)

protected desde T y los tipos derivados de T

internal desde los tipos incluidos en A

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

using System;

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

Page 61: Curso de C Sharp

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

Page 62: Curso de C Sharp

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

Page 63: Curso de C Sharp

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

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

// Método public void Acelerar(double c) {

Page 64: Curso de C Sharp

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

Page 65: Curso de C Sharp

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 refpermite 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 llamada. Además, la variable que se pase por referencia ha de estar inicializada previamente.

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

Page 66: Curso de C Sharp

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.

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

Page 67: Curso de C Sharp

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

Page 68: Curso de C Sharp

A diferencia de C++, la llamada al destructor no está garantizada por lo que tendremos que utilizar una orden using e implementar el interfazIDisposable 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{

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 {

Page 69: Curso de C Sharp

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

Page 70: Curso de C Sharp

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

Para asegurar la compatibilidad con otros lenguajes de .NET:

// Operadores aritméticos

public static Point operator + (Point p1, Point

Page 71: Curso de C Sharp

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

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

Page 72: Curso de C Sharp

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

Page 73: Curso de C Sharp

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

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

Page 74: Curso de C Sharp

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{ 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 Struct

Tipo referencia Tipo valor

Puede heredar de Para las estructuras no existe herencia: una

Page 75: Curso de C Sharp

otro tipo (que no esté "sellado")

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

Page 76: Curso de C Sharp

Curso de C#

Herencia

Concepto de herenciaClases abstractasClases selladasTipos anidados

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

C# sólo permite herencia simple.

Herencia de constructores

Page 77: Curso de C Sharp

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 }

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

Page 78: Curso de C Sharp

}

} // 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 claseTaxi. 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;

namespace DemoHerencia {

class CocheSimple {

Page 79: Curso de C Sharp

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

Console.Write ("Mi coche: "); MiCoche.MuestraCoche(); Console.Write ("El tuyo: ");

Page 80: Curso de C Sharp

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 ("Citröen", "C5", 250, "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:

CocheSimple MiCoche = new CocheSimple ("Citröen", "Xsara Picasso",

Page 81: Curso de C Sharp

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 82: Curso de C Sharp

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 baseCocheSencillo:

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 83: Curso de C Sharp

En la sección dedicada a la 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á:

Métodos virtuales

Page 84: Curso de C Sharp

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.

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

Page 85: Curso de C Sharp

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

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

Page 86: Curso de C Sharp

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

Page 87: Curso de C Sharp

Curso de C#

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 { // Fijar un elemento } } ...

Page 88: Curso de C Sharp

}...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; public Coche[] vectorCoches;

Page 89: Curso de C Sharp

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 get

Page 90: Curso de C Sharp

{ 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 91: Curso de C Sharp

// 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 92: Curso de C Sharp

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;

Page 93: Curso de C Sharp

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 { 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(); } }}

Page 94: Curso de C Sharp

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

Page 95: Curso de C Sharp

} } 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) { 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();

Page 96: Curso de C Sharp

}}

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.

La sobrecarga de los operadores relacionales puede hacerse implementando la interface IComparable, concretamente el método CompareTo. La interface IComparable define un método de comparación generalizado, implementado por un tipo de valor o clase para crear un método de comparación específico del tipo. Esta interfaz la implementan tipos cuyos valores se pueden ordenar, como por ejemplo, las clases numéricas o de tipo cadena (las clases enum y string implementan esta interface). Un tipo de valor o clase implementa el método CompareTo para crear un método de comparación específico.

El siguiente ejemplo amplía la especificación de la clase Point introducida anteriormente.

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

Page 97: Curso de C Sharp

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

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

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

Herencia de interfaces

using System;

Page 98: Curso de C Sharp

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

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

Page 99: Curso de C Sharp

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 100: Curso de C Sharp

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 y el tipo de retorno de un método.

Un delegado crea un nuevo tipo al que debe amoldarse un método para que pueda ser asignado al delegado. Su función es similar a la de lospunteros a funciones en lenguajes como C y C++ (C# no soporta punteros a funciones) sólo que los delegados son a prueba de tipos, orientados a objetos y seguros.

Page 101: Curso de C Sharp

Los delegados pueden pasarse a métodos y pueden usarse para llamar a los métodos de los que contienen referencias. 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(); }}

En el ejemplo anterior Sin, Cos e incremento son métodos con la misma signatura (todos reciben un único argumento de tipo double) y devuelven un valor de tipo double. Los objetos del1, del2 y del3 son objetos delegados del tipo Del (Sin, Cos e incremento delegan en éstos) que invocan a los métodos a los que representan.

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

Page 102: Curso de C Sharp

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

En este caso f referencia, por turno, a los métodos representados por f1, f2 y f3.

Finalmente, observe cómo el polimorfismo es más evidente en el siguiente código:

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<3; i++) Console.WriteLine (d[i](0)); // 0, 1, 1 }

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. Observe el siguiente ejemplo:

using System;using System.IO;

class DelegateTest{

public delegate void Print (string s);

public static void EnConsola (string str) { Console.WriteLine(str); }

public static void EnFichero (string str) { StreamWriter fo = new StreamWriter ("Resultado.txt"); fo.WriteLine(str); fo.Close();

Page 103: Curso de C Sharp

}

public static void Display (Print Metodo) { Metodo ("Este es el mensaje que se muestra"); }

static void Main() { Print dc = new Print (EnConsola); Print df = new Print (EnFichero); Display (dc); Display (df);

Console.ReadLine(); }}

En el ejemplo anterior EnFichero y EnConsola son métodos que reciben un argumento string y no devuelven nada. El tipo delegado Printrepresenta a métodos que reciben un argumento string y no devuelven nada, por lo que los métodos EnFichero y EnConsola pueden delegar en objetos de tipo Print. En este ejemplo, no obstante, el método Display recibe un argumento del tipo delegado Print, esto es, recibe la referencia a un método que recibe un argumento string y no devuelve nada (EnFichero y EnConsola son buenos candidatos).

Las instrucciones: 

Print dc = new Print (EnConsola); Print df = new Print (EnFichero);hacen que los métodos EnConsola y EnFichero deleguen en los objetos dc y df, respectivamente. Así, podemos usarlos como argumentos al método Display sin problemas:  Display (dc); Display (df);

Delegados multimiembro (Multicasting)

Un delegado multimiembro es un delegado individual compuesto de más delegados. En definitiva: permite almacenar referencias a uno o más métodos, de tal manera que a través del objeto delegado sea posible solicitar la ejecución en cadena de todos ellos.

Los delegados multimiembro tienen dos restricciones:

No pueden devolver ningún valor (deben ser void). No pueden tener parámetros de salida (out).

Se añaden delegados a un delegado multimiembro con el operador + y se eliminan delegados de un delegado multimiembro con el operador -. Cada delegado tiene una

Page 104: Curso de C Sharp

lista ordenada de métodos que se invocan secuencialmente (en el mismo orden en el que fueron añadidos al delegado).

"Multicasting"

using System;

class Delegate2App{ delegate void SomeEvent (void);

static void Func1(void) { Console.WriteLine(" Desde Func1"); }

static void Func2(void) { Console.WriteLine(" Desde Func2"); }

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

func += new SomeEvent(Func2);

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

func -= new SomeEvent(Func1);

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

Console.ReadLine(); }}

El siguiente ejemplo ilustra de una manera más compleja la adición y eliminación de delegados a un delegado multimiembro. Observe como interviene un objeto de la clase EjemploDelegado que encapsula dos delegados.

"Multicasting" (2)

Page 105: Curso de C Sharp

using System;

class Multicasting{ public delegate void Calculo (int n1, int n2, ref int res);

public class EjemploDelegado { public Calculo Calculo1; public Calculo Calculo2;

public void Suma (int n1, int n2, ref int res) { res = n1 + n2; Console.WriteLine("Suma ({0},{1}) = {2}", n1, n2, res); } public void Resta (int n1, int n2, ref int res) { res = n1 - n2; Console.WriteLine("Suma ({0},{1}) = {2}", n1, n2, res); }

}

static void Main(string[] args) { int resultado = 0; EjemploDelegado Del = new EjemploDelegado ();

Del.Calculo1 = new Calculo (Del.Suma); Del.Calculo2 = new Calculo (Del.Resta);

Del.Calculo1 (5, 9, ref resultado); Del.Calculo2 (5, 9, ref resultado); Console.WriteLine();

Calculo MultiCalc = Del.Calculo1; MultiCalc += Del.Calculo2; MultiCalc (5, 9, ref resultado); Console.WriteLine();

MultiCalc -= Del.Calculo1; MultiCalc (5, 9, ref resultado);

Console.ReadLine(); }}

Page 106: Curso de C Sharp

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

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

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

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.

Page 107: Curso de C Sharp

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 108: Curso de C Sharp

urso de 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...).

Page 109: Curso de C Sharp

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

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

Page 110: Curso de C Sharp

catch (Exception e){ Console.WriteLine("catch"); Console.WriteLine("Excepción detectada: " + e.Message);}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");

Page 111: Curso de C Sharp

Console.WriteLine("Excep. aritmética"); Console.WriteLine("ArithmeticException Handler: {0}", e.ToString()); } 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:

Page 112: Curso de C Sharp

Cuando se produce desbordamiento:

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

Page 113: Curso de C Sharp

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

Page 114: Curso de C Sharp

Console.ReadLine(); }}

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 115: Curso de C Sharp

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 116: Curso de C Sharp

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 117: Curso de C Sharp

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 118: Curso de C Sharp

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 métodos, los bloques set y get de sus propiedades e indexadores o los

Page 119: Curso de C Sharp

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 ID

Transaction 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 120: Curso de C Sharp

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 121: Curso de C Sharp

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 stringo System.Typeo enumeracioneso 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 122: Curso de C Sharp

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 claseSystem.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 esGetCustomAttributes, 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 123: Curso de C Sharp

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 124: Curso de C Sharp

[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()

Page 125: Curso de C Sharp

{ string st1 = " Revisor : " + Reviewer + "\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]); }

Page 126: Curso de C Sharp

}

[CodeReview("Pepe", "12-11-2002"), IsTested()]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

Page 127: Curso de C Sharp

ayuda:"); if (attribute is CodeReviewAttribute) Console.WriteLine (" Atributos de Revisión:"); if (attribute is IsTestedAttribute) 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(); }}

Page 128: Curso de C Sharp
Page 129: Curso de C Sharp

Programas en C#

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

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:

Page 130: Curso de C Sharp

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

Page 131: Curso de C Sharp

}}

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

Page 132: Curso de C Sharp

}

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

Page 133: Curso de C Sharp

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

class MainClass { Point2D p1; Point3D p2;

static void Main(string[] args) {

} }}

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

Page 134: Curso de C Sharp

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.

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

Page 135: Curso de C Sharp

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

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 llamaMiClase.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 Disposetodo aquello que haya de realizarse para liberar un objeto). El método Dispose siempre se invoca al terminar una sentencia using:

Page 136: Curso de C Sharp

public class MyResource : IDisposable{ public void MyResource() { // Acquire valuble resource }

public void Dispose() { // Release valuble resource }

public void DoSomething() { ... }

}

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

Page 137: Curso de C Sharp

{ // 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() { 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#.

Page 138: Curso de C Sharp

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, #endif Compilación condicional.

#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) {

Page 139: Curso de C Sharp

throw new AssertionException(s); } }

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

Documentación

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.

Page 140: Curso de C Sharp

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

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

Page 141: Curso de C Sharp

/// <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; }

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

Page 142: Curso de C Sharp

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

Page 143: Curso de C Sharp

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 | 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 144: Curso de C Sharp
Page 145: Curso de C Sharp
Page 146: Curso de C Sharp

Curso de C#

Conceptos básicos de programación orientada a objetos

Conceptos básicosReferencias

Conceptos básicos

Objetos, instancias y clases: Un objeto es una estructura de datos en tiempo de ejecución, formada por uno o más valores (campos) y que sirve como representación de un objeto abstracto. Todo objeto es instancia de una clase. Una clase es un tipo abstracto de datos implementado total o parcialmente, que encapsula datos y operaciones. Las clases sirven de módulos y de tipos (o patrones de tipos si son genéricas).

Módulo: Unidad lógica que permite descomponer el software. En programación orientada a objetos, las clases proporcionan la forma básica de módulo. Para facilitar el desarrollo de software y su posible reutilización, las dependencias entre módulos deberían reducirse al máximo para conseguir sistemas débilmente acoplados.

Tipo: Cada objeto tiene un tipo, que describe un conjunto de operaciones con las que están equipados todos los objetos de una misma clase.

Interfaz: Contrato perfectamente definido que especifica completamente las condiciones precisas que gobiernan las relaciones entre una clase proveedora y sus clientes (rutinas exportadas). Además, es deseable conocer las precondiciones, postcondiciones e invariantes que sean aplicables.

Identidad: Cada objeto (instancia de una clase) tiene una identidad única, independientemente de su contenido actual (los datos almacenados en sus campos).

Encapsulación (u ocultación de información): Capacidad de evitar que ciertos aspectos sean visibles desde el exterior. De esta forma, se ocultan detalles de implementación y el usuario puede emplear objetos sin tener que conocer su estructura interna.

Herencia: Los tipos se organizan de forma jerárquica (clases base y derivadas, superclases y subclases). La herencia proporciona un mecanismo simple mediante el cual se pueden definir unos tipos en función de otros, a los que añade sus características propias. Hay

Page 147: Curso de C Sharp

que distinguir entre herencia de interfaz y herencia de implementación (que aumenta el acoplamiento entre los módulos de un programa).

Polimorfismo: Capacidad de usar un objeto sin saber su tipo exacto. Formalmente, el polimorfismo es la capacidad de que un elemento de código pueda denotar, en tiempo de ejecución, objetos de dos o más tipos distintos.

Referencias

Bertrand Meyer: "Construcción de software orientado a objetos", [2ª ed.], Prentice Hall, 1999, ISBN 84-8322-040-7.

Page 148: Curso de C Sharp

Curso de C#

La biblioteca de clases de la plataforma .NET

SystemColeccionesEntrada / Salida

Esta sección describe algunas clases e interfaces de la plataforma .NET con el objetivo de que el lector sea capaz de utilizarlas al desarrollar sus propios programas.

Antes de que existiese la plataforma .NET, cada lenguaje de programación tenía su propia biblioteca de clases, lo que provocaba que no todos los lenguajes dispusiesen de la misma funcionalidad (p.ej. algunos APIs no estaban soportados en Visual Basic, por lo que había que recurrir a C/C++ para desarrollar determinados tipos de aplicaciones). Además, la funcionalidad proporcionada por distintas clases estaba repartida en componentes COM/COM+, controles ActiveX, DLLs del sistema... lo cual dificultaba su organización (además de hacer casi imposible la implementación de extensiones de las clases disponibles).

La plataforma .NET incluye una colección de clases bien organizada cuya parte independiente del sistema operativo ha sido propuesta para su estandarización (http://msdn.microsoft.com/net/ecma/).

La biblioteca de clases de la plataforma .NET integra todas las tecnologías Windows en un marco único para todos los lenguajes de programación (Windows Forms, GDI+, Web Forms, Web Services, impresión, redes...).

La biblioteca de clases .NET proporciona un modelo orientado a objetos que sustituye a los componentes COM.

System

El espacio de nombres System incluye la clase base para todos los objetos (System.Object), los tipos de datos primitivos y algunos no tan primitivos (cadenas, textos, fechas, horas y calendarios), además de un conjunto de interfaces estándar y soporte para la E/S a través de consola.

System.Object

Page 149: Curso de C Sharp

La clase System.Object es la clase base para todos los tipos de la plataforma .NET. Cualquier tipo hereda implícitamente de esta clase, de forma que se unifica el sistema de tipos. Esto facilita que las colecciones se puedan usar para almacenar cualquier cosa y es menos propenso a errores que el tipo VARIANT utilizado en COM (al ser un sistema fuertemente tipificado).

Métodos de System.Object

System.Object.Equals(Object o) comprueba si dos objetos son iguales.

System.Object.ReferenceEquals(Object o) comprueba si dos referencias apuntan al mismo objeto.

System.Object.Finalize() es invocado por el recolector de basura para liberar el objeto.

System.Object.GetHashCode() se emplea en la implementación colecciones de objetos (del tipoSystem.Collections.HashTable) y debe implementarse en las subclases de System.Object para crear buenas funciones hash (por defecto, se utiliza la identidad del objeto).

System.Object.GetType() devuelve el tipo de un objeto (punto de entrada para el sistema de reflexión de la plataforma .NET).

System.Object.MemberwiseClone() crea un clon del objeto utilizando la capacidad de reflexión de la plataforma .NET

System.Object.ToString() devuelve una representación textual del objeto. Por defecto, devuelve el nombre de la clase, por lo que deberemos implementarlo nosotros. No está diseñado para mostrar mensajes al usuario (para esa función se debería utilizar el interfazIFormattable)

public class Person{ String name;

public override string ToString() { return name; }}

...

Name n1 = new Name("Fred");Name n2 = new Name("Fred");Name n3 = n2; // n2 & n3 apuntan al mismo objeto

if (n1 == n2) ... // falseif (n2 == n3) ... // trueif (n1.Equals(n2)) ... // trueif (n2.Equals(n3)) ... // true

Tipos primitivos

Page 150: Curso de C Sharp

Se elimina la distinción entre los tipos primitivos del lenguaje y los demás objetos (al estilo de Smalltalk). Los tipos primitivos son comunes para toda la plataforma .NET, si bien se muestran en cada lenguaje según su sintaxis:

C#: bool, int, long, string, double, float... Visual Basic.NET: Boolean, Integer, String...

Algunos tipos primitivos de interés son: System.Byte, System.Char, System.Boolean (valores lógicos: true o false), System.Guid(identificador universal de 128 bits, que además incluye un generador System.Guid.NewGuid()) y los valores nulos (System.DBNull para valores NULL en bases de datos, System.Empty para representar el valor VT_EMPTY en COM y System.Missing para trabajar con parámetros opcionales).

Cadenas de caracteres

La clase System.String es la utilizada en todos los lenguajes de la plataforma .NET para representar cadenas de caracteres, que se almacenan utilizando UNICODE. El tipo System.String es inmutable, por lo que los métodos que parecen modificar una cadena en realidad lo que hacen es crear una nueva. La inmutabilidad de los objetos de este tipo hace recomendable el uso de String.Format o StringBuilder para manipular cadenas de caracteres (como en Java).

La clase String incluye métodos 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(), Split()

Modificar cadenas de caracteres: ToLower(), ToUpper(), Format() (al estilo del printf de C, pero seguro).

Comparación de cadenas

public static int Main(){ string s = "abc"; string s1 = s; // s1 y s hacen referencia al mismo objeto string s2 = "abc";

if (s1 == s2) Console.WriteLine("OK"); else Console.WriteLine("Esto no debería pasar");

return 0}

Page 151: Curso de C Sharp

Fechas

System.DateTime permite representar fechas (100 d.C. - 9999 d.C.) y realizar operaciones aritméticas con ellas, así como darles formato y leerlas de una cadena en función de la configuración local.

System.TimeSpan sirve para trabajar con duraciones de tiempo (que se pueden expersar en distintas unidades).

System.TimeZone permite trabajar con husos horarios.

Consola

La clase System.Console proporciona la funcionalidad básica de E/S (equivalente a stdin, stdout y stderr en C). Para escribir se puede utilizarWrite() o WriteLine() empleando la sintaxis de String.Format, mientras Read() lee un caracter y ReadLine() lee una línea completa.

Console.Write("Snow White and the {0} dwarfs", 7);

Clases útiles de System

Clase Función

System.URI Identificadores universales de recursos

System.Random Generador de números aleatorios

System.Convert Conversiones de tipos básicos

Interfaces de System

IFormattable

El interfaz IFormattable nos permite dar formato a la representación del valor de un objeto:

interface IFormattable{ String Format(String format, IServiceObjectProvider sop);}

El método Format da formato al valor de la instancia actual tal como se le especifique:

String.Format("Please order {0} widgets at {1} each.", i, f);String.Format("{0:U}", DateTime.Now);

Page 152: Curso de C Sharp

El parámetro IServiceProvider se puede emplear para obtener características especiales de la configuración local (p.ej. delimitadores para números y fechas).

Incluso se puede implementar el interfaz ICustomFormatter para redefinir el formato en el que se muestran los valores de los tipos predefinidos.

IDisposable

Este interfaz nos permite controlar explícitamente la liberación de recursos:

class ResourceWrapper : IDisposable{ private IntPrt handle; // Puntero a un recurso externp private OtherResource otherRes;

bool disposed = false;

private void free () { if (!disposed) {

CloseHandle (handle); dispose = true;}

}

public void Dispose { free();

OtherRes.Dispose();GC.Suppress.Finalization(this);

}

public void Finalize () { free();

Base.Finalize(); }}

Colecciones

Arrays

Los arrays, representados mediante la clase System.Array, constituyen la única colección de datos que queda fuera del espacio de nombresSystem.Collections. La clase System.Array corresponde a los arrays en cualquier lenguaje de programación de la plataforma .NET y sirven para almacenar objetos de forma polimórfica (esto es, sirven para almacenar instancias de cualquier clase que herede de System.Object [todas]).

Page 153: Curso de C Sharp

Los arrays pueden tener un número arbitrario de dimensiones y su tamaño se especifica al crearlos (CreateInstance). Una vez creado el array, su tamaño es fijo.

Entre sus cualidades más destacadas destaca el hecho de que los arrays pueden ordenarse (siempre y cuando se comparen objetos que implementen el interfaz IComparable o se indique un comparador IComparer). Además, si el array está ordenado, se pueden realizar búsquedas binarias en él.

public static void Main(){ // Creación e inicialización arrays int[] intArray = new int[5] { 1, 2, 3, 4, 5 }; Object[] objArray = new Object[5] { 26, 27, 28, 29, 30 };

// Copia de los dos primeros elementos de intArray en objArray Array.Copy( intArray, objArray, 2 );

// Volcado en consola de los valores actuales de los arrays Console.Write( "intArray: " ); PrintValues( intArray ); Console.Write( "objArray: " ); PrintValues( objArray );

// Copia de los dos últimos elementos de objArray en intArray Array.Copy( objArray, objArray.GetUpperBound(0) - 1, intArray, intArray.GetUpperBound(0) - 1, 2 );}

Interfaces

Aparte de los arrays, la plataforma .NET también suministra una serie de interfaces estándar para la implementación de colecciones de objetos de distintos tipos (similar a las colecciones de Java).

IEnumerable & IEnumerator

El interfaz System.Collections.IEnumerable permite iterar sobre una colección de datos, proporcionando un mecanismo estándar para todas las colecciones:

public interface IEnumerable{ IEnumerator GetEnumerator();}

Page 154: Curso de C Sharp

El método GetEnumerator() devuelve un iterador que implementa el interfaz System.Collections.IEnumerator:

public interface IEnumerator{ Boolean MoveNext(); Object Current { get; } void Reset();}

SetType st = new SetType(...);

// Se obtiene un iterador para enumerar los elementos de la colección

IEnumerator e = st.GetEnumerator();

// Se recorre la colección utilizando el iterador como cursor

while (e.MoveNext()) { // Se lee un elemento de la colección

ItemType it = (ItemType) e.Current;// Se emplea el elemento para hacer lo que

haga faltaConsole.WriteLine(it);

}

ICollection

El interfaz System.Collections.ICollection se deriva de IEnumerable y proporciona un interfaz básico para todas las colecciones:Count(), CopyTo(), IsSynchronized()

IList

El interfaz System.Collections.IList también deriva de ICollection y permite trabajar con listas. Proporciona un indexador Item para acceder a los elementos de la lista en función de su posición y una serie de métodos para trabajar con el contenido de la lista:Add(), Remove(), Contains(), Clear()

IDictionary

El interfaz System.Collections.IDictionary especializa el interfaz ICollection y se utiliza para implementar diccionarios (conjuntos de pares clave-valor, a modo de memoria asociativa). Este interfaz proporciona un indexador Item para consultar un valor dada su clave y los mismos métodos que IList para trabajar con el diccionario: Add(), Remove(), Contains(), Clear() .

Clases

Page 155: Curso de C Sharp

Aparte de los interfaces estándar que sirven como base para la implementación de colecciones, la biblioteca de clases de la plataforma .NET también incluye algunas colecciones ya implementadas:

ArrayList

La clase System.Collections.ArrayList implementa el interfaz IList para proporcionar arrays dinámicos cuyo tamaño puede variar dinámicamente (a diferencia de System.Array, que mantiene fijo su tamaño una vez que ha sido creado).

using System;using System.Collections;

public class SampleArrayList{ public static void Main() { // Creación ArrayList myAL = new ArrayList();

// Inicialización myAL.Add("Hello"); myAL.Add("World"); myAL.Add("!");

// Visualización Console.WriteLine( "myAL" ); Console.WriteLine( "\tCount: {0}", myAL.Count ); Console.WriteLine( "\tCapacity: {0}", myAL.Capacity );

Console.Write( "\tValues:" ); PrintValues( myAL ); }

public static void PrintValues( IEnumerable myList ) { IEnumerator myEnumerator = myList.GetEnumerator();

while ( myEnumerator.MoveNext() ) Console.Write("\t{0}", myEnumerator.Current );

Console.WriteLine(); }}

BitArray

Page 156: Curso de C Sharp

La clase System.Collections.BitArray proporciona una implementación eficiente y compacta de arrays de bits.

HashTable

La clase System.Collections.HashTable implementa un diccionario (IDictionary) mediante una tabla hash.

SortedList

La clase System.Collections.SortedList implementa una lista ordenada sin duplicados que puede indexada por enteros (posición) y por cadenas (contenido).

Stack

La clase System.Collections.Stack implementa una pila LIFO con sus métodos Push() y Pop(). Al implementar el interfaz IEnumerable, podemos enumerar sus elementos:

using System;using System.Collections;

public class SampleStack{ public static void Main() { Stack myStack = new Stack();

myStack.Push("Hello"); myStack.Push("World"); myStack.Push("!");

Console.WriteLine( "myStack" ); Console.WriteLine( "\tCount: {0}", myStack.Count ); Console.Write( "\tValues:" ); PrintValues( myStack );

Console.Write( "\tLIFO:" ); EmptyStack ( myStack ); }

public static void PrintValues( IEnumerable myCollection ) { IEnumerator myEnumerator = myCollection.GetEnumerator();

while ( myEnumerator.MoveNext() ) Console.Write( "\t{0}", myEnumerator.Current );

Console.WriteLine();

Page 157: Curso de C Sharp

}

public static void EmptyStack( Stack myStack ) { object value = myStack.Pop();

while (value!=null) { Console.Write ("\t{0}", value); value = myStack.Pop(); }

Console.WriteLine(); }}

Queue

La clase System.Collections.Queue implementa una cola FIFO con sus métodos Enqueue() y Dequeue(). Igual que sucede con las pilas, las colas también son enumerables:

using System;using System.Collections;

public class SampleQueue{ public static void Main() { Queue myQ = new Queue();

myQ.Enqueue("Hello"); myQ.Enqueue("World"); myQ.Enqueue("!");

Console.WriteLine( "myQ" ); Console.WriteLine( "\tCount: {0}", myQ.Count ); Console.Write( "\tValues:" ); PrintValues( myQ );

Console.Write( "\tFIFO:" ); EmptyQueue (myQ); }

public static void PrintValues ( Ienumerable myCollection ) { IEnumerator myEnumerator = myCollection.GetEnumerator();

while ( myEnumerator.MoveNext() ) Console.Write( "\t{0}", myEnumerator.Current );

Page 158: Curso de C Sharp

Console.WriteLine(); }

public static void EmptyQueue( Queue myQ ) { object value = myQ.Dequeue();

while (value!=null) { Console.Write ("\t{0}", value); value = myQ.Dequeue(); }

Console.WriteLine(); }}

Entrada / Salida

Ficheros y directorios

La biblioteca de clases de la plataforma .NET proporciona una serie de clases que nos permiten trabajar con el sistema de archivos:

System.IO.Directory y System.IO.File proporcionan métodos estáticos para manipular directorios y ficheros, respectivamente.

System.IO.DirectoryInfo Y System.IO.FileInfo incluyen métodos para manipular instancias de directorios y ficheros, respectivamente. System.IO.DirectoryInfo representa un directorio concreto, a partir del cual se pueden obtener sus subdirectorios (con GetDirectories([mask])) y los ficheros que incluye (con GetFiles([mask]). Por su parte, System.IO.FileInforepresenta un fichero concreto, que se puede obtener directamente especificando su path o a partir de la enumeración de ficheros de un directorio con GetFiles().

Una vez que tenemos un fichero con el que trabajar, utilizamos uno de sus métodos Open... para poder acceder y modificar su contenido:Open(), OpenRead(), OpenWrite(), OpenText(). Cualquiera de los métodos anteriores devuelve una instancia de System.IO.Stream.

Streams

La clase base de los streams en la plataforma .NET es la clase abstracta System.IO.Stream, que proporciona funciones de acceso síncrono (Read(), Write()) y asíncrono (BeginRead(), BeginWrite(), EndRead(), EndWrite()).

System.IO.FileStream se utiliza para acceder directamente al contenido de ficheros. De hecho es el tipo devuelto por una llamada aFile.Open().

System.IO.MemoryStream permite construir streams en memoria.

Page 159: Curso de C Sharp

Lectura

Los stream readers proporcionan acceso de lectura a un stream:

System.IO.BinaryReader permite leer datos en binario: ReadInt16(), ReadBoolean(), ReadDouble(), etc.

System.IO.TextReader es una clase abstracta utilizada como base común de las dos siguientes:

System.IO.StreamReader hereda de TextReader e implementa los métodos ReadLine() para leer una línea de texto yReadToEnd() para leer lo que quede de stream.

System.IO.StringReader también hereda de TextReader y se utiliza para simular streams a partir de cadenas de caracteres.

Lectura de un fichero de texto

static void Main(string[] args) { string filename = @"../../AssemblyInfo.cs"; string line;

Console.WriteLine ("*****************************");

StreamReader stream = new StreamReader(filename); // StreamReader stream = File.OpenText (filename);

do { line = stream.ReadLine(); Console.WriteLine (line); } while (line != null);

stream.Close();

Console.WriteLine ("*****************************");

Console.ReadLine(); }

Escritura

Los stream writers sirven para escribir en streams:

System.IO.BinaryWriter nos permite escribir datos en binario, mediante la utilización del método sobrecargado Write().

System.IO.TextWriter es la clase abstracta que sirve de base para StreamWriter y StringWriter.

System.IO.StreamWriter hereda de TextWriter y sirve para escribir cadenas de texto en streams.

Page 160: Curso de C Sharp

System.IO.StringWriter también hereda de TextWriter y simula streams en cadenas de caracteres.

public class MyWriter{ private Stream stream;

public MyWriter(Stream stream) { this.stream = stream; }

// Almacena la representación binaria de un double

public void WriteDouble(double myData) { byte[] b = myData.GetBytes(); stream.Write(b,0,b.Length); }

public void Close() { stream.Close(); }}

static void Main(string[] args) { string filenameI = @"../../AssemblyInfo.cs"; string filenameO = @"../../COPIA_AssemblyInfo.cs"; string line;

StreamReader streamI = new StreamReader(filenameI); StreamWriter streamO = new StreamWriter(filenameO);

while ((line = streamI.ReadLine()) != null) { streamO.WriteLine (line); }

streamI.Close(); streamO.Close(); }

System.Net

El espacio de nombres System.Net contiene todas las clases relativas al uso de protocolos de red para transmitir datos. Proporciona todo lo necesario para utilizar los protocolos de red IP (sockets) e IPX, así como otros protocolos de aplicación (p.ej. HTTP, incluidos sus mecanismos de autentificación [Basic, Digest, NTLM Challenge/Reponse] y el uso de cookies).

Page 161: Curso de C Sharp

HTTP

La clase abstracta System.Net.WebRequest es la clase base para el uso de protocolos de red como HTTP. Esta clase sirve de punto de acceso común para distintos protocolos. Además, permite registrar nuevos protocolos mediante el método RegisterPrefix().

La clase HttpWebRequest da soporte a los protocolos HTTP y HTTPS. El contenido de la solicitud HTTP[S] se rellena con un stream (WebRequest.GetRequestStream()) y la solicitud se ejecuta con GetResponse(). Los datos devueltos tras ejecutar GetResponse() se pueden leer mediante el método WebResponse.GetReponseStream().

HttpWebRequest HttpWReq = (HttpWebRequest) WebRequestFactory.Create("http://elvex.ugr.es");

HttpWebResponse HttpWResp = (HttpWebResponse)HttpWReq.GetResponse();

SMTP

MailMessage MyMessage = new MailMessage();MyMessage.To = "[email protected]";MyMessage.From = "MyApplication";MyMessage.Subject = "Unhandled Error!!!";MyMessage.BodyFormat = MailFormat.Html;MyMessage.Body = "<html><body><h1> ERROR </h1></body></html>";SmtpMail.Send(MyMessage);

Serialización

La serialización es el proceso de convertir un objeto, o un grafo conexo de objetos, en una secuencia de bytes.

using System;using System.IO;using System.Collections;using System.Serialization;using System.Serialization.Formatters.Binary;

class SerializeExample{ public static void Main(String[] args) { ArrayList l = new ArrayList();

for (int x=0; x < 100; x++)

Page 162: Curso de C Sharp

l.Add (x);

FileStream s = File.Create("foo.bin"); BinaryFormatter b = new BinaryFormatter();

b.Serialize(s, l); }}

using System;using System.IO;using System.Collections;using System.Serialization;using System.Serialization.Formatters.Binary;

class DeserializeExample{ public static void Main(String[] args) { FileStream s = File.Open("foo.bin"); BinaryFormatter b = new BinaryFormatter();

ArrayList p = (ArrayList) b.Deserialize(s); p.ToString(); }}

Un tipo no es serializable salvo que se marque específicamente con el atributo Serializable:

[Serializable] public class MyClass {}

[Serializable] public class MyClass{ [NotSerialized] int _size;}

El proceso de serialización puede personalizarse si implementamos el interfaz ISerializable, el interfaz IDeserializationEventListener o creamos nuestros propios "formateadores" (custom formatters).

ISerializable

Un único método: void GetObjectData (SerializationInfo info, StreamingContext context);y un constructor que puede ser privado: private <T> (SerializationInfo info, StreamingContext context)

IFormatter

Page 163: Curso de C Sharp

public interface IFormatter{ // Propiedades SerializationBinder Binder { get; set; } StreamingContext Context { get; set; } ISurrogateSelector SurrogateSelector { get; set; }

// Métodos object Deserialize(Stream serializationStream); void Serialize(Stream serializationStream, object graph);}

Page 164: Curso de C Sharp