Csharp

Page 1

Tutorial de C# version 0.5


Tutorial de C#: version 0.5

Un tutorial para aprender C# a nivel inicial utilizando mono (http://mono-project.com).

Copyright (c) 2004 MonoHispano. Se otorga permiso para copiar, distribuir y/o modificar este documento bajo los t茅rminos de la Licencia de Documentaci贸n Libre GNU, versi贸n 1.2 o cualquier versi贸n posterior publicada por la Free Software Foundation. No hay Secciones Invariantes ni Textos de Portada o Contraportada. Puedes consultar una copia de la licencia en http://www.gnu.org/copyleft/fdl.html.


Tabla de contenidos 1. Introducción ...........................................................................................................................................1 1.1. El lenguaje C# .............................................................................................................................1 1.1.1. Introducción....................................................................................................................1 1.1.2. Programación basada en componentes...........................................................................1 1.1.3. Orientación a objetos......................................................................................................2 1.1.4. Librería del lenguaje.......................................................................................................2 1.1.5. Estandarización...............................................................................................................2 1.1.6. C# frente a Java ..............................................................................................................2 1.1.7. C# frente a C++ ..............................................................................................................3 1.1.8. ¿Porqué C#?....................................................................................................................3 1.2. Primer ejemplo ............................................................................................................................4 2. Tipos........................................................................................................................................................6 2.1. Tipos............................................................................................................................................6 2.1.1. Importancia de los tipos de datos ...................................................................................6 2.1.2. Tipos en C#.....................................................................................................................6 2.2. Tipos por valor ............................................................................................................................6 2.2.1. Tipos por valor................................................................................................................6 2.2.2. Enteros............................................................................................................................7 2.2.3. Tipos de coma flotante....................................................................................................8 2.2.4. El tipo decimal................................................................................................................9 2.2.5. El tipo bool .....................................................................................................................9 2.2.6. Tipo arreglo ..................................................................................................................10 2.2.7. El tipo char ...................................................................................................................12 3. Estructuras de control .........................................................................................................................13 3.1. Estructuras de control................................................................................................................13 3.1.1. Instrucción if.................................................................................................................13 3.1.2. Instrucción switch.........................................................................................................15 3.1.3. Bucle for .......................................................................................................................17 3.1.4. Bucle while...................................................................................................................18 3.1.5. Bucle do-while..............................................................................................................18 3.1.6. Bucle foreach................................................................................................................19 3.2. Cambiando el rumbo .................................................................................................................20 3.2.1. Instrucción goto ............................................................................................................20 3.2.2. Sentencia return ............................................................................................................21 3.2.3. La instrucción continue ................................................................................................21 3.2.4. break para salir de bucles..............................................................................................22 3.2.5. throw y el manejo de excepciones ................................................................................23 4. Operadores ...........................................................................................................................................24 4.1. Operadores ................................................................................................................................24 5. Introducción a las clases......................................................................................................................27 5.1. Introducción a las clases en C# .................................................................................................27 5.1.1. Métodos ........................................................................................................................28 5.1.2. Modificadores public y static........................................................................................29 5.1.3. Constructores e instancias de una clase........................................................................30

iii


5.1.4. Sobrecarga de métodos.................................................................................................32 5.1.5. La palabra reservada this ..............................................................................................33 6. Variables y parámetros........................................................................................................................35 6.1. Variables y parámetros ..............................................................................................................35 6.1.1. Variables .......................................................................................................................35 6.1.2. Parámetros ....................................................................................................................36 6.1.3. Arreglo de parámetros ..................................................................................................38 7. Propiedades e indizadores...................................................................................................................40 7.1. Propiedades e indizadores .........................................................................................................40 7.1.1. Propiedades...................................................................................................................40 7.1.2. Indexadores...................................................................................................................41 8. Clases ....................................................................................................................................................43 8.1. Modificadores de acceso ...........................................................................................................43 8.2. Herencia ....................................................................................................................................43 8.2.1. La palabra reservada base.............................................................................................44 8.2.2. Clases Abstractas..........................................................................................................45 8.2.3. Miembros virtual ..........................................................................................................45 8.3. Interfaces ...................................................................................................................................46 8.4. Modificadores de Herencia .......................................................................................................46 8.5. Algunas clases notables ............................................................................................................46 8.5.1. Tipo arreglo ..................................................................................................................47 9. Delegados ..............................................................................................................................................48 9.1. Delegados..................................................................................................................................48 9.1.1. Llamadas a múltiples métodos .....................................................................................49 10. Eventos................................................................................................................................................52 10.1. Eventos....................................................................................................................................52 11. Programación multihilo ....................................................................................................................58 11.1. Threads -- Programación multihilo .........................................................................................58 11.1.1. Controlando el tiempo de vida del hilo ......................................................................60 12. Sobrecarga de operadores.................................................................................................................62 12.1. Sobrecarga de operadores .......................................................................................................62 12.1.1. ¿ Qué es la sobrecarga de operadores ? ......................................................................62 12.1.2. Sobrecargando operadores en la practica ...................................................................62 13. Tratamiento de ficheros.....................................................................................................................65 13.1. Tratamiento de ficheros...........................................................................................................65 13.1.1. La primera clase: System.IO.File ...............................................................................65 13.1.2. Obteniendo informacion sobre archivos: System.IO.FileInfo....................................68 14. Interoperabilidad con código nativo ................................................................................................69 14.1. Uso de Platform Invoke...........................................................................................................69 14.1.1. ¿ Que es Platform Invoke ?.........................................................................................69 14.1.2. Usando Platform Invoke .............................................................................................69 14.1.3. Ejemplo de uso ...........................................................................................................69 14.1.4. Conclusiones...............................................................................................................70

iv


15. Introspección ......................................................................................................................................71 15.1. Introspección: El ojo que todo lo ve .......................................................................................71 15.1.1. Metadatos ...................................................................................................................71 15.1.2. Atributos .....................................................................................................................71 15.1.3. Código gestionado: Integración..................................................................................72 15.1.4. Reflexión.....................................................................................................................72 15.1.5. Ejemplo práctico: Árboles de Mono...........................................................................72 16. Manipulación de archivos XML.......................................................................................................73 16.1. Algunos conceptos de XML ...................................................................................................73 16.2. Archivos XML ........................................................................................................................74 16.2.1. Escribir un archivo XML............................................................................................74 16.3. La clase XmlWriter .................................................................................................................74 17. Creación y uso de librerías................................................................................................................76 17.1. Compilando para una librería..................................................................................................76 17.2. Usando nuestra libreria ...........................................................................................................76 18. Autores ................................................................................................................................................78 Bibliografía...............................................................................................................................................79

v


Lista de tablas 2-1. Tipos por valor......................................................................................................................................7

Tabla de ejemplos 9-1. Ejemplo simple de uso de delegados..................................................................................................49 9-2. Ejemplo de uso de delegados .............................................................................................................50

vi


Capítulo 1. Introducción 1.1. El lenguaje C# 1.1.1. Introducción Los primeros rumores de que Microsoft estaba desarrollando un nuevo lenguaje de programación surgieron en 1998 , haciendo referencia a un lenguaje que entonces llamaban COOL y que decían era muy similar a Java. En junio de 2000, Microsoft despejó todas las dudas liberando la especificación de un nuevo lenguaje llamado C#. A esto le siguió rápidamente la primera versión de prueba del entorno de desarrollo estándar (SDK) .Net, que incluía un compilador de C#. El nuevo lenguaje estaba diseñado por Anders Hejlsberg ( creador de Turbo Pascal y arquitecto de Delphi ), Scott Wiltamuth y Peter Golde. Entonces describieron el lenguaje como "...simple, moderno, orientado a objetos, de tipado seguro y con una fuerte herencia de C/C++".

1.1.2. Programación basada en componentes En los últimos 10 años se han asentado diferentes técnicas de programación como lo son la orientación a objetos, la programación basada en interfaces o los componentes. A pesar de ésto, los lenguajes de programación siempre han ido un paso por detrás de las mejores prácticas de programación del momento. Como resultado, los programadores tienden a depender de código específico, usar convenciones, o simplemente a no usar las nuevas técnicas. Por ejemplo, C++ soporta orientación a objetos, pero no tiene el concepto formal de interfaces. Entonces los programadores recurren a una mezcla entre clases abstractas bases e interfaces para simular programación basada en interfaces, mientras que utilizan modelos externos de componentes, como COM y CORBA para obtener los beneficios de la programación orientada a componentes. Aunque Java está un paso adelante de C++ al proporcionar soporte a nivel de lenguaje para interfaces y paquetes (entre otras cosas), también tiene poco soporte para construir y mantener a la largo del tiempo completos sistemas basados en componentes (en los que uno necesita desarrollar, desplegar, interconectar y manejar distintas versiones de varias fuentes en un extenso periodo de tiempo). Esto no significa que la comunidad Java no haya construido tales sistemas, simplemente que las necesidades derivadas de la implementación de tales sistemas se han conseguido a través de convenciones de notación y código propio, no a través de una característica del lenguaje. Por otra parte, el lenguaje C# se ha construido suponiendo que los modernos sistemas de software se construyen usando componentes. Por lo tanto, C# proporciona soporte a nivel de lenguaje para los constructores básicos de los componentes, como puden ser propiedades, métodos y eventos. Esto no

1


Capítulo 1. Introducción significa que todo esto no se haya hecho antes, lenguajes como LISP o Smalltak hacían cosas parecidas, pero con un gran coste. C# tiene mecanismos para permitir al mismo tiempo un orientación a componentes y un gran rendimiento.

1.1.3. Orientación a objetos Además del profundo soporte para desarrollo de software basado en componentes, C# es un lenguaje completamente orientado a objetos, que implementa casi todo los conceptos y abstracciones presentes en C++ y Java. Como es de esperar en un lenguaje orientado a objetos, C# implementa conceptos como herencia, encapsulación, polimorfismo y programación basada en interfaces. Además soporta las construcciones típicas de C++ y Java, como clases, estructuras, interfaces y enumeraciones, así como algunas construcciones nuevas, como los delegados, que son parecidos a los punteros a funciones de C++, o los atributos, lo cual permite añadir metainformación sobre el código. C# consigue compaginar bien orientación a objetos y rendimiento. Algunos lenguajes, como Smalltalk, se basan en que "todo es un objetos". Esta aproximación tiene la ventaja de una completa orientación a objetos, pero tiene la desventaja de ser muy ineficiente. Para mejorar el rendimiento, otros lenguajes, como Java, separan el sistema de tipos en tipos primitivos y todo el resto, dando lugar a mejor rendimiento en los tipos primitivos, pero en una separación a veces molesta entre tipos primitivos y tipos definidos por el usuario. En C# se han aunado ambas aproximaciones presentando lo que se llama un sistema unificado de tipos, en el que todos los tipos, incluso los primitivos, derivan de un tipo objeto común, a la vez que permite el uso de optimizaciones para tipos primitivos y tipos sencillos.

1.1.4. Librería del lenguaje Contrariamente a la mayoría de lenguajes, C# no incluye una librería específica, sino que utiliza la librería de clases de la plataforma .NET para todas sus necesidades, desde utilización de la consola hasta la programación multiproceso o el cifrado de seguridad.

1.1.5. Estandarización Además de los méritos técnicos, uno de las razones del éxito de C# y la plataforma .NET ha sido por el proceso de estandarización que Micrsoft ha seguido (y que ha sorprendido a más de uno). Micrsoft, en lugar de reservarse todos los derechos sobre el lenguaje y la plataforma, ha publicado las especificaciones del lenguaje y de la plataforma, que han sido posteriormente revisadas y ratificadas por la Asociación Europea de Fabricantes de Computadoras (ECMA). Esta especifícación (que se puede descargar libremente de aquí (http://www.ecma-international.org/publications/standards/Ecma-334.htm)) permite la implementación del lenguaje C# y de la plataforma .NET por terceros, incluso en entornos distintos de Windows.

2


Capítulo 1. Introducción

1.1.6. C# frente a Java C# y Java son lenguajes muy similares, de sintaxis basada en C/C++, orientados a objetos y que incluyena las características más importantes de los lenguajes modernos, como gestión automática de memoria y compilación a código intermedio. Pero por supuesto, también hay diferencias. Una de las diferencias más importantes es que C# es mucho más cercano a C++ en cuanto a diseño se refiere. C# toma casi todos sus operadores, palabras reservadas y expresiones directamente de C++. También se han mantenido algunas características que en Java se han desestimado. Por ejemplo las enumeraciones. No hay enumeraciones en Java y sin embargo era un concepto muy usado en C/C++. En C# se han mantenido las enumeraciones, y se han adaptado al nuevo lenguaje, de forma que ahora las enumeraciones no son simplemente enteros, sino que son tipos de tipado seguro que derivan de System.Enum en la librería de clases base. Una enumeración de tipo "ej1" no se puede cambiar con una enumeración de tipo "ej2" sin una conversión. Creo que esa es una diferencia importante. Toda la estructura de los espacios de nombres es mucho más cercana a C++. Otra característica que no está presente en Java es la posibilidad de trabajar directamente con direcciones de memoria. Si bien tanto Java como .NET proporcionan recogida automática de basura, en C# es posible usar lo que se denomina "código no seguro". Cuando se usa código no seguro en C# es posible operar con punteros de forma muy similar a como se haría en C/C++, pero el código que utiliza punteros se queda marcado como no seguro y no se ejecuta en entornos en los que no tiene permisos.

1.1.7. C# frente a C++ Puesto que C# se ejecuta en una máquina virtual, ésta se hace cargo de la gestión de memoria y por lo tanto el uso de punteros es mucho menos importante en C# que en C++. C# también es mucho más orientado a objetos, hasta el punto de que todos los tipos usados derivan en última instancia el tipo ’object’. Además, muchos tipos se usan dde forma distinta. Por ejemplo, en C# se comprueban los límites de los vectores antes de usarlos, evitando así que se pueda escribir pasado el final del vector. Al igual que Java, C# renuncia a la idea de herencia múltiple de clases presente en C++. Sin embargo, referido a clases, C# implemente ’propiedades’ del tipo de las que existen en Visual Basic, y los métodos de las clases son accedidos mediante ’.’ en lugar de ’::’.

1.1.8. ¿Porqué C#? La plataforma .NET acepta varios lenguajes. Por ahora, C#, Visual Basic, C++ gestionado, Nemerle, FORTRAN, Java, Python, etc. , y con capacidad para aceptar prácticamente cualquier lenguaje. Entonces la pregunta es, ¿porqué se eligió C# en lugar de cualquier otro lenguaje?. La razón fundamental es que C# se diseñó para la plataforma .NET y es capaz de utilizar todo su potencial. También es cierto que es un lenguaje "limpio" en el sentido de que al no tener que proporcionar compatibilidad hacia detrás se ha tenido más libertad en el diseño y se ha puesto especial incapié en la simplicidad. Por ejemplo, en C# hay un tipo de clase y siempre se le aplica el recolector de

3


Capítulo 1. Introducción basura mientras que en C++ gestionado hay dos tipos de clases, una a las que se le aplica el recolector y otra a la que no.

1.2. Primer ejemplo Para empezar con C#, que mejor que con un ejemplo básico para entender la estructura básica de un programa en C# y empezar a conocer las características del lenguaje. El ejemplo sería el siguiente: //Declaración del espacio de nombres using System; //Clase de nuestro programa principal class PrimerEjemplo { public static void Main() { // Escribir a la consola Console.WriteLine ("Bienvenido al tutorial de C# de M-H"); } }

Todo programa que escribamos en C# va a tener una estructura similar a esta en la que declararemos uno/varios espacios de nombres a utilizar (System), una clase y el método "Main" de esa clase con las sentencias de nuestro programa. Por cierto, todos los archivos en C# llevan la extensión .cs (no hay ficheros de cabecera ni nada similar ). Una vez ya visto el primer ejemplo, para compilarlo habría que utilizar mcs (mono compiler suite), que es el compilador de mono de C#, implementado según las especificaciones del lenguaje según ECMA-334, que será el encargado de generar los ejecutables en código intermedio (CIL) que posteriormente tendrá que ser ejecutado por mono. Para ello se procedería de la siguiente forma: # mcs ejemplo.cs # mono ejemplo.exe

Dando el siguiente resultado: Bienvenido al tutorial de C# de M-H

Sobre todo si estás en entornos Windows tal vez querrás que la consola no se cierre automáticamente. Entonces tendrás que escribir Console.Read() detrás de la última sentencia para que el programa espere a que pulses una tecla para poder cerrarse. Esto se hace extensible a todos los ejemplos de este tutorial.

4


Capítulo 1. Introducción En nuestro ejemplo, System es un espacio de nombres y con él le estaremos diciendo que podamos usar todos las clases asociadas a ese espacio de nombres, en nuestro caso la clase Console con el método WriteLine que es el que se encarga de escribir por pantalla el mensaje que queramos mostrar. La clase PrimerEjemplo es la que va a contener la definición de datos y métodos que va a usar nuestro programa al ejecutarse. Además de clases veremos que se pueden definir otros tipos diferentes de elementos tales como estructuras e interfaces con los métodos asociados a estos tipos. Al utilizar el método Main le estaremos diciendo que nuestro programa empieza ahí y tiene los modificadores static (sólo se va a usar en esa clase) y void diciendole que nuestro método no va a devolver nada. Ya únicamente queda mostrar por pantalla el resultado, esto se hace utilizando la clase Console y el método asociado WriteLine que es el que muestra por pantalla el mensaje de bienvenido. Para hacer referencia a los métodos en WriteLine en C# se va a hacer con el operador ".", en diferencia a lo que puede ser en C++ el "::". Para los comentarios se utiliza tanto // como /* esto es un comentario */ al más puro estilo de C++ y que todas las sentencias tienen que acabar con ; y los delimitadores de bloque son { y }.

5


Capítulo 2. Tipos 2.1. Tipos 2.1.1. Importancia de los tipos de datos Los tipos son la base de cualquier programa. Un tipo no es más que un espacio en el que se almacena una información, ya sean números, palabras o tu fecha de nacimiento. Los tipos de datos son especialmente importantes en C# porque es un lenguaje con información de tipos. Esto significa que, en todas las operaciones, el compilador comprueba los tipos para ver su compatibilidad. Las operaciones no válidas no se compilan. De esta forma se evitan muchos errores y se consigue una mayor fiabilidad

2.1.2. Tipos en C# En C# los tipos básicos no son más que alias para tipos predefinidos en la librería base de la plataforma Mono/.NET . Así, el tipo entero int, no es más que una forma rápida de escribir System.Int32 . Los tipos del lenguaje C# son divididos en dos grandes categorías: tipos por valor y tipos por referencia. Existe una tercera categoría de tipos, disponible solo cuando se usa código no seguro: los punteros, que se discutirán más adelante. Los tipos por valor difieren de los tipos por referencia en que las variables de los tipos por valor contienen directamente su valor, mientras que las variables de los tipos por referencia almacenan referencias a objetos. Con los tipos por referencia, es posible que dos variables se refieran al mismo objeto, y por tanto es posible que las operaciones sobre una variable afecten al objeto al que hace referencia otra variable. Con los tipos por valor, cada variable tienen su propia copia de los datos, y las operaciones sobre una no afectará a la otra.

2.2. Tipos por valor 2.2.1. Tipos por valor Como hemos comentado, el término tipo por valor indica que las variables de esos tipos contienen directamente sus valores. De esta forma, los tipos por valor actúan de forma muy parecida a los tipos de

6


Capítulo 2. Tipos datos de otros lenguajes de programación como C++. Los tipos por valor también se conocen como tipos sencillos Tabla 2-1. Tipos por valor Tipo C#

Nombre para la plataforma Mono/.NET

Con signo?

Bytes utilizados Rango

bool

System.Boolean

No

1

verdadero o falso

byte

System.Byte

No

1

0 hasta 255

sbyte

System.SByte

Si

1

-128 hasta 127

short

System.Int16

Si

2

-32.768 hasta 32.767

ushort

System.Uint16

No

2

0 hasta 65535

int

System.Int32

Si

4

-2.147.483.648 hasta 2.147.483.647

uint

System.Uint32

No

4

0 hasta 4.394.967.395

long

System.Int64

Si

8

9.223.372.036.854.775.808 hasta 9.223.372.036.854.775.807

ulong

System.Uint64

No

8

0 hasta 18446744073709551615

float

System.Single

Si

4

Approximadamente ±1.5E-45 hasta ±3.4E38 con 7 cifras significativas

double

System.Double

Si

8

Approximadamente ±5.0E-324 hasta ±1.7E308 con 7 cifras significativas

decimal

System.Decimal

Si

12

Approximadamente ±1.0E-28 hasta ±7.9E28 con 28 ó 29 cifras significativas

char

System.Char

2

Cualquier carácter Unicode (16 bits)

7


Capítulo 2. Tipos

2.2.2. Enteros Los tipos que sirven para almacenar números enteros son: byte, sbyte. short, ushort, int, uint, long y ulong. Como se aprecia en la tabla, C# define versiones con y sin signo para tipos con los mismo bytes utilizados. La diferencia entre enteros con signo y sin signo radica en el modo de interpretar el bit de nivel superior del entero. Si se especifica un entero con signo, el compilador entenderá que el primer bit indica el signo: 0 si es positivo, 1 si es negativo. Sin embargo, los enteros sin signo, ese bit se puede utilizar para almacen el número y así se consigue que los enteros sin signo puedan almacenar números el doble de grandes que los enteros con signo. Probablemente el tipo más utilizado es el int, pués se utiliza para controlar matrices, inidizar arreglos además de las operaciones normales con enteros. Además, se trata de un entero de tamaño medio: más pequeño que long y ulong, pero más grande que byte, sbyte, short y ushort. El siguiente ejemplo muestra la declaración y uso de algunos tipos enteros calculando el número de segundos en una hora, dia y en un año. using System; class Enteros{ public static void Main() { int Minuto = 60; //segundos por minuto int Hora = Minuto*60; int Dia = Hora*24; long Anio = Dia*365; Console.WriteLine("Segundos en un dia: {0}", Dia); Console.WriteLine("Segundos en un año: {0}", Anio); } }

De nuevo hemos usado el método Console.WriteLine para imprimir los resultados por la consola. El identificador {0} dentro de la cadena de texto indica que se sustituye {0} por el primer argumento. si hubiera más de un argumento, se seguiría con {1}, y así sucesivamente. Por ejemplo, las dos líneas que utilizan Console.WriteLine se pueden simplificar así: Console.WriteLine("En un dia: {0}; en un año: {1}", Dia, Anio );

8


Capítulo 2. Tipos

2.2.3. Tipos de coma flotante Los tipos de coma flotante sirven para representar a números con parte fraccionaria. La representación por supuesto puede no ser exacta, bien por errores de la máquina, bien porque el número de decimales que se puede alojar es finito. Existen dos clases de tipos de punto flotante, float y double. De los dos, el más usado es double, pués es el valor que devuelven la mayoría de las funciones matemáticas de la librería base. El siguiente ejemplo calcula la raíz cuadrada y el logaritmo de dos: using System; class Flotante{ public static void Main() { int a = 2; double log2 = Math.Log(2); double raiz2 = Math.Sqrt(2); Console.WriteLine("El logaritmo de dos es {0}", log2 ); Console.WriteLine("La raiz de dos es {0}", raiz2 ); } }

y la salida será la siguiente: El logaritmo de dos es 0.693147180559945 La raiz de dos es 1.4142135623731

si intentamos cambiar el tipo de log2 a otro de menos precisión, como float o int, el compilador protestará. Esto se debe, como hemos dicho a que el valor devuelto por Math.Log() es de tipo double y si se quiere convertir a float, pués se perderán datos. Lo mismo ocurre con la mayoría de los miembros de la clase Math, como Math.Sin(), Math.Tan(), etc.

2.2.4. El tipo decimal El tipo decimal es un tipo "nuevo" en el sentido de que no tiene equivalente en C/C++. Es muy parecido a los tipo de coma flotante float y double. En la aritmética de los tipos de coma flotante ordinarios, se pueden producir leves errores de redondeo. El tipo decimal elimina estos errores y puede representar correctamente hasta 28 lugares decimales. Esta capacidad para representar valores decimales sin errores de redondeo lo hace especialmente eficaz para cálculos monetarios.

9


Capítulo 2. Tipos

2.2.5. El tipo bool El tipo bool sirve para expresar los valores verdadero/falso, que en C# se muestran con las palabras reservadas true y false. En C#, por ejemplo, una instrucción if solo puede estar gobernada por un valor bool, no como en C/C++, que lo puede estar también por un entero. De esta forma se ayuda a eliminar el error tan frecuente en programadores de C/C++ cuando usa "=" en lugar de "==". En definitiva, la inclusión del tipo bool en el lenguaje ayuda a la claridad del código y evita algunos errores muy comunes. El siguiente ejemplo, muestra algunos usos del tipo bool: using System; class Booleano{ public static void Main() { bool b; b = true; Console.WriteLine("b es {0}", b); if(b) { Console.WriteLine("esto saldrá"); } b = false; if(b) { Console.WriteLine("esto no saldrá"); } Console.WriteLine("2==2 es {0}", 2==2); } }

En la última línea se muesta que el operador "==" también devuele un valor booleano. El resultado debería ser el siguiente: b es True esto saldrá 2==2 es True

10


Capítulo 2. Tipos

2.2.6. Tipo arreglo En C# se pueden construir arreglos de prácticamente cualquier tipo de dato. Los arreglos, también llamados vectores o arrays, no son más que una sucesión de datos. Por ejemplo, el concepto matemático de vector es una sucesión de números y por lo tanto es un arreglo unidimensional. Así, podemos construir arreglos de objetos, de cadenas de texto, y, por supuesto, arreglos de enteros: using System; class Arreglo{ public static void Main() { int[] arr = new int[3]; arr[0] = 1; arr[1] = 2; arr[2] = 3; Console.WriteLine( arr[1] ); } }

En este ejemplo se crea un arreglo arr unidimensional con capacidad para 3 enteros, y luego se le asigna a cada valor un entero distinto (nótese que se comienza a contar a partir de 0 ). Existe una forma más corta para declarar el arreglo y asignarle las variables: int[] arr = {1,2,3};

También se pueden crear arreglos bidimensionales ( de la misma forma para más dimensiones). En ese caso la sintaxis para declarar un arreglo bidimensional de enterios será int[,] arr

en contraposición a C/C++, en el que se declararía como int[][] arr

De esta forma, un arreglo bidimensional se declararía y utilizaría de la siguiente forma: using System; class Arreglo2{ public static void Main() { int[,] arr = new int[2,2]; arr[0,0] = 1;

11


Capítulo 2. Tipos arr[1,0] = 2; arr[0,1] = 3; arr[1,1] = 4; Console.WriteLine( arr[1,1] ); } }

que, igual que el ejemplo anterior, podíamos hacer declarado todo el arreglo de la siguiente forma: int[,] arr = {{1,2},{3,4}};

Se hablará con más detalle sobre arreglos en la sección

2.2.7. El tipo char El tipo char permite almacenar un carácter en formato unicode de 16 bits, lo que nos garantiza que los acentos se ven de forma adecuada y además permite la representación de otros alfabetos, como el griego, cirílico, etc. Para introducir un carácter se utilizan comillas simples, de forma de declarar un carácter sigue la estructura char letra = ’a’

De igual forma que hemos hecho con los enteros, es posible declarar un arreglo de char char[] cadena = {’a’, ’b’, ’c’ };

aunque para almacenar algunas cadenas de caracteres, como las palabras, es más indicado usar el tipo string .

12


Capítulo 3. Estructuras de control 3.1. Estructuras de control En este capítulo se describen algunas sentencias que sirven para controlar la ejecución de un programa. Algunas son muy similares a las existentes en otros lenguajes, como las sentencias if, for, while, etc. y otras, como foreach, throw o continue, son algo más específicas.

3.1.1. Instrucción if Esta sentencia sirve para ejecutar unas instrucciones en caso de que se cumpla determinada condición. La forma completa de la instrucción if es if( condición ) { instrucción1; instrucción2; ... } else { instrucción1; instrucción2; ... }

donde la cláusula else es opcional. Si la condición es verdadera, se ejecutarán las instrucciones dentro del bloque if, mientras que si es falsa, se ejecutará el bloque else. El valor que controla la sentencia if debe ser de tipo bool. El siguiente ejemplo //programa que determina si un valor es positivo o negativo using System; class InstruccionIf{ public static void Main() { double d; Console.WriteLine("Introduce un numero"); d = Double.Parse( Console.ReadLine() ); if( d>0 ) { Console.WriteLine("El numero {0} es positivo", d); } else { Console.WriteLine("El numero {0} es negativo", d);

13


Capítulo 3. Estructuras de control } } }

te pide que introduzcas un número y dependiendo de si se cumple que dicho número es mayor que cero (condición), se ejecuta un bloque u otro. La sentencia d = Double.Parse( Console.ReadLine() ); tal vez requiera algo de explicación adicional. En realidad, con Console.ReadLine() estamos leyendo lo que el usuario introduce por pantalla, que es una cadena de caractéres, y con Double.Parse lo que hacemos es interpretar esa cadena de caractéres y conventirna en un tipo numérico double, de forma que d tendrá el valor del número que introduzcamos por la consola. Las intrucciones if se pueden anidar, y existe también una extensión de la sentencia if, la sentencia if-else-if. Su formato es el siguiente: if( condicion1 ) { instrucciones; } else if( condicion2 ) { instrucciones; } ... else { instrucciones; }

Las instrucciones condicionales se evalúan de arriba a abajo. Tan pronto como se encuentra una condición true, se ejecuta la instrucción asociada con ella, y el resto de la escalera se omite. Si ninguna de las condiciones es true, se ejecutará la última instrucción else. La última instrucción else actúa como condición predeterminada, es decir, si no funciona ninguna de las otras pruebas condicionaes, se realiza esta última instrucción. Si no existe esta instrucción else final y el resto de de las condiciones son falsas, entonces no se realizará ninguna acción. El siguiente ejemplo using System; class IfElseIf{ public static void Main() { string opcion; Console.WriteLine("Elija una opción (si/no)"); opcion = Console.ReadLine(); if( opcion=="si" )

14


Capítulo 3. Estructuras de control { Console.WriteLine( "Muy bien, ha elegido si" ); } else if( opcion=="no" ) { Console.WriteLine( "Ha elegido no" ); } else{ Console.WriteLine("No entiendo lo que ha escrito"); } } }

le pide al usuario que elija una opción si/no y la procesa usando una estructura if-else-if. Si la opción no es ni "si" ni "no", entonces se ejecuta la sentencia else por defecto, que imprime por pantalla el mensaje "No entiendo lo que ha escrito"

3.1.2. Instrucción switch La instrucción switch es muy parecida a la estructura if-else-if, sólo que permite seleccionar entre varias alternativas de una manera más cómoda. Funciona de la siguiente manera: el valor de una expresión se prueba sucesivamente con una lista de constantes. Cuando se encuentra una coincidencia, se ejecuta la secuencia de instrucciones asociada con esa coincidencia. La forma general de la instrucción switch es la siguiente: switch( expresión ){ case constante1: instrucciones; break; case constante2: instrucciones; break; ... default: instrucciones; break; }

La sentencia default se ejecutará sólo si ninguna constante de las que siguen a case coincide con expresión. Es algo similar al else final de la instrucción if-ese-if. Sin más, vamos a por un ejemplo using System; class InstruccionSwitch{ public static void Main() {

15


Capítulo 3. Estructuras de control string s; Console.WriteLine( Console.WriteLine( Console.WriteLine( Console.WriteLine( Console.WriteLine(

"Elige " + " " * " /

hacer algo con los números 2 y 3"); para sumarlos" ); para restarlos" ); para multiplicarlos" ); para dividirlos (division entera)" );

s = Console.ReadLine(); switch(s){ case "+": Console.WriteLine("El break; case "-": Console.WriteLine("El break; case "*": Console.WriteLine("El break; case "/": Console.WriteLine("El break; default: Console.WriteLine("No break; }

resultado es {0}", 2+3);

resultado es {0}", 2-3);

resultado es {0}", 2*3);

resultado es {0}", 2/3);

te entiendo");

} }

El cual solicita al usuario que inserte uno de los símbolos +-*/ , y con un switch compara los resultados para hacer diferentes acciones dependiendo del valor de s, que es la cadena de caracteres que almacena la elección del usuario. El resultado debería de ser algo parecido a esto: Elige hacer algo con los números 2 y 3 + para sumarlos - para restarlos * para multiplicarlos / para dividirlos (division entera) * El resultado es 6

Como habrá notado, al final de todo case siempre hay una sentencia break. Esto no es obligatorio, puede haber en su lugar otra sentencia de salto, como un goto, pero siempre tiene que haber una sentencia de salto, incluso en el default final, a no ser que la sentencia case esté vacía. En caso contrario se obtiene un error en tiempo de compilación. Otros lenguajes, como C/C++ o Java no tienen esta restricción. La razón de adoptarla en C# es doble: por un lado, elimina muchos errores comunes y en segundo lugar permite al compilador reorganizar las sentencias de los case, y así permitir su optimización.

16


Capítulo 3. Estructuras de control

3.1.3. Bucle for El bucle for de C# es idéntico al encontrado en los lenguajes C/C++ y Java. El formato general es for( inicialización; condición; iteración ) { instrucciones; }

Las sentencias de inicialización se ejecutan una vez al principio y sirven principalmente para asignar valores a las variables que servirán de contador. Las sentencias de condición, por su parte, se ejecutan cada vez que el bucle vuelve al principio y sirven para controlar el bucle: éste seguirá realizándose siempre y cuando estas codiciones sea true. Las sentencias de iteración se ejecutan también cada vez que se realiza una nuevo ciclo en el bucle, y sirven para cambiar el estado de las variables que gobiernan las sentencias de condición. Pero todo esto se entiende mejor con un ejemplo using System; class BucleFor{ public static void Main() { int i; //el contador for( i = 0; i < 10; i++) { Console.WriteLine( i ); } } }

Este ejemplo imprime por pantalla los 10 primero enteros positivos. Es un caso muy simple del bucle for. Por cierto, el operador ++ lo que hace es que añade una unidad a la variable a la que acompaña, de forma que, por ejemplo, 9++ es 10. De esta forma, la variable i se incrementa a cada vuelta. En el ejemplo anterior, las sentencias de inicialización y de iteración eran únicas, pero esto no tiene porqué ser así, de hecho se pueden utilizar varias sentencias separadas por comas. Por ejemplo, se pueden usar dos variables para controlar el bucle using System; class BucleFor2{ public static void Main() { int i; int j; for( i=0, j=10; i<j; i++, j--) { Console.WriteLine("( {0} , {1} )", i, j); }

17


Capítulo 3. Estructuras de control } }

Por su parte, la expresión condicionar del bucle for puede ser cualquier expresión que genere un valor booleano. En este caso se ha usado "i>j", pero también hubiera sido válida "i==5", "true" (el bucle se realizará indefinidamente) o "false" (el bucle no se realizará).

3.1.4. Bucle while El bucle while es un bucle que se realiza hasta que se cumpla determinada condición. Tiene la forma while( condición ) { instrucciones; }

Donde la condicióntiene que ser un valor booleano. Tiene una estructura muy sencilla, así que vamos a ver directamente un ejemplo. using System; class BucleWhile{ public static void Main() { int i = 0; while( i<10) { Console.WriteLine( i ); i = i+1; } } }

En el que se realiza lo mismo que en el ejemplo anterior, sólo que ahora con un bucle while.

3.1.5. Bucle do-while Se trata de una ligera variante del bucle anterior, con la diferencia de que ahora primero se ejecutan las instrucciones y luego se evalúa la condición, de forma que tiene tiene una estructura: do{ instrucciones; }

18


Capítulo 3. Estructuras de control while( condición );

El siguiente ejemplo class BucleDoWhile{ public static void Main() { string s = ""; do { Console.WriteLine( "Introduce si para salir del bucle" ); s = Console.ReadLine(); } while( s != "si" ); } }

muestra un programa que ejecuta un bucle hasta que el usuario introduce "si". Por cierto, != es lo contrario de ==, es decir, != devuelve true cuando los valores comparados son distintos.

3.1.6. Bucle foreach El bucle foreach se utiliza para hacer iteraciones sobre elementos de una colección, como pueden ser los enteros dentro de un arreglo de enteros. La sintaxis sigue la siguiente estructura: foreach( tipo in coleccion ) { instrucciones; }

Como hemos comentado, el uso más inmediato es iterar sobre un arreglo de números: using System; class BucleForeach{ public static void Main() { int[,] arr = {{1,2},{2,3}}; foreach( int elem in arr ) { Console.WriteLine( elem ); } } }

19


Capítulo 3. Estructuras de control Este ejemplo sólo imprime los valores de una matriz, pero como se puede comprobar mejora mucho la claridad del código comparándolo con una implementación con bucles for como esta using System; class BucleForeach{ public static void Main() { int i, j; //seran los indexadores de la matriz int[,] arr =

{{1,2},{2,3}};

for(i = 0; i<2; i++ ) { for( j = 0; j<2; j++ ) { Console.WriteLine( arr[i,j] ); } } } }

Además, es posible utilizar el bucle foreach con cualquier tipo que sea una colección, no solo con arreglos, como veremos más adelante.

3.2. Cambiando el rumbo Existen varias formas de cambiar el rumbo de los programas, mediante sentencias de salto incondicional, como goto, return, así como formas de cambiar el rumbo de bucles, como continue o break

3.2.1. Instrucción goto La instrucción goto sirve para realizar saltos incondicionales a otras partes del programa. Los programadores siempre advierten de los peligros de usar demasiado esta sentencia (es propensa a escribir código ilegible), aunque el contadas ocasiones puede ser de gran utilidad. Como su propio nombre indica, esta sentencia nos lleva a alguna parte del programa, de forma que habrá que marcar con una etiqueta la parte del programa a la que deseemos que nos envíe. Estas etiquetas se declaran simplemente con el nombre de la etiqueta y dos puntos. Lo vemos en el siguiente ejemplo using System; class Goto{ public static void Main() { goto dos;

20


Capítulo 3. Estructuras de control uno: Console.WriteLine("Esto no se ejecutará"); dos: Console.WriteLine("Hemos saltado directamente aquí!"); } }

Es un ejemplo muy simple con dos etiquetas: uno y dos. En la ejecución normal de un programa, primero se ejecutaría uno y después dos. Sin embargo, la instrucción goto hace que saltemos directamten a la instrucción dos. Seguro que se te ocurren multitud de situaciones en las que es útil la instrucción goto, así que no insistiremos más sobre esto.

3.2.2. Sentencia return Colocar return en alguna parte del programa provoca que el método termine y devuelva el valor especificado. Si estamos en el método Main() , que por ahora es el único caso que conocemos, return provocará el final del programa. En el ejemplo using System; class SentenciaReturn{ public static void Main() { double a; principio: Console.WriteLine("Introduce un numero:"); a = Double.Parse( Console.ReadLine() ); if( a > 10 ) { return; } else { goto principio; } } }

utilizamos también la instrucción return para implementar un programa en el que se le pide al usuario que introduzca un número y solo termina si el número es mayor que 10. Este es un mal ejemplo de uso de goto y return, pués se podría haber conseguido lo mismo con un bucle while, pero por ahora sirve. La importancia de la sentencia return se verá con más claridad cuando veamos métodos.

21


Capítulo 3. Estructuras de control

3.2.3. La instrucción continue Es posible realizar la repetición temprana de un bucle omitiendo el resto del código que contenga el bucle mediante continue. Su comportamiento es muy sencillo:

using System; class UsoContinue{ public static void Main() { int i; for(i = 0; i<10; i++ ) { continue; Console.WriteLine("Esto nunca se ejecutará!"); } Console.WriteLine("Adios!"); } }

Al compilar el siguiente ejemplo, es posible que el compilador emita una advertencia de que ha detecatado código inalcanzable -esto es, que nunca llegará a ejecutarse. Esto no es problema, pués es justamente lo que queremos. Por lo demás, el ejemplo es muy sencillo, dentro del bucle for lo primero que se hace es ejecutar la instrucción continue, de forma que el bucle se repite sin llegar nunca al siguiente Console.WriteLine().

3.2.4. break para salir de bucles La instrucción break es el complemento lógico de continue, de forma que si continue repite el bucle, break, lo termina. Así, cuando se encuentra una sentencia break dentro de un bucle, éste inmediatamente finaliza y se sigue la ejecución del programa. using System; class UsoBreak{ public static void Main() { int i; for( i = 0; i<20; i++) { if( i> 10 ) { break; } Console.WriteLine( i ); }

22


Capítulo 3. Estructuras de control } }

Este ejemplo muestra el uso de break para imprimir los enteros de 0 a 10. Si no estuviera la sentencia break mostraría los enteros hasta 20, pero al llegar a 11 se ejcuta la instrucción if que desemboca en un break que hace que la ejecución salga del bucle.

3.2.5. throw y el manejo de excepciones La sentencia throw sirve para lanzar excepciones y así modificar el flujo del programa, de mondo que pasa a otra instrucción si se captura la excepción, o se termina el programa si la excepción no es capturada. Esta sentencia se verá en el capítulo sobre manejo de excepciones

23


Capítulo 4. Operadores 4.1. Operadores Los operadores son símbolos que permiten realizar operaciones con uno o más datos, para dar un resultado. El ejemplo clásico y obvio de operador es el símbolo de la suma (+), aunque hay otros muchos. Vamos a hacer un repaso a los tipos de operadores que tenemos en C#: •

Operadores Aritméticos: Son la suma (+), resta (-), producto (*), división (/) y módulo (%). Éstos son bastante inmediatos pués funcionan de la misma manera que en álgebra. Solo hay que tener en cuenta que la división entre enteros devuele un entero, de forma que 3/2 devuelve 1 ( el resultado se trunca). Es interesante saber que a las operaciones aritméticas les podemos añadir otros dos operadores, checked y unchecked, para controlar los desbordamientos en las operaciones. La sintaxis es esta: variable1 = checked (34+4);

o bien variable1 = unchecked (34+4);

Si en una operación regida por checked se produce un desbordamiento, dará un error en tiempo de compilación si los operadores son constantes, o lanzará una excepción System.OverflowException si son variables. En cambio, si la operación es unchecked, cuando se produce un desboramiento nos devolverá el resultado truncado, para que "quepa" en el resultado esperado. •

Operadores Lógicos: Son "and" (&& y &), "or" (|| y |), "not" (!) y "xor" (^). La diferencia entre && y &, y entre || y | es que && y || hacen lo que se llama "evaluación perezosa": si evaluando sólo la primera parte de la operacion se puede deducir el resultado, la parte derecha no se evaluará. Es decir, si tenemos por ejemplo: false && (otra cosa)

El resultado de esta operación siempre será false, y (otra cosa) ni siquiera se evalúa. De igual forma, si tenemos true || (otra cosa)

24


Capítulo 4. Operadores el resultado será true, y la parte derecha nunca se evaluará. •

Operadores relacionales: igualdad (==), desigualdad (!=), mayor que (>), menor que (<), mayor o igual que (>=) y menor o igual que (<=)

Operadores de Manipulación de Bits: Tenemos las siguientes operaciones: and (&), or (|), not (~), xor (^), desplazamiento a la izquierda (<<), y desplazamiento a la derecha (>>). El desplazamiento a la izquierda rellena con ceros. El desplazamiento a la derecha, si se trata de un dato con signo, mantiene el signo. Si no, rellena con ceros.

Operadores de Asignación: El operador básico de asignación es =. Además, tenemos las clásicas abreviaturas +=, -=, *=, /=, &=, |=, ^=, <<= y >>= Estas abreviaturas se usan para evitar tecleo en operaciones como esta: variable1 = variable1 + variable2;

Se puede escribir de esta forma abreviada: variable1 += variable2;

También tenemos operadores de incremento (++) y decremento (--), que incrementan en una unidad el valor de la variable sobre la que se aplican. Por tanto, estas tres líneas de código son casi iguales: variable1 = variable1 + 1; variable1 += 1; variable1++;

El "casi iguales" lo ponemos porque en muchas máquinas, el operador ++ es más rápido que la operación "+ 1", ya que el compilador lo traduce a una única instrucción máquina. Hay que tener en cuenta que no es lo mismo poner variable++ que ++variable. Ambas formas son correctas, pero no significan lo mismo. Lo vemos con un ejemplo: variable1 = ++variable2; variable1 = variable2++;

En el primer caso, primero se incrementa variable2 y luego se hace la asignación. En el segundo caso primero se hace la asignación, y luego el incremento. •

Operador Condicional: Es el único operador de C# que tiene tres operandos. Su sintaxis es esta: <condición> ? <expresión1> : <expresión2>

Quiere decir que si la condición es true, se evalúa expresión1, y si es falsa, se evalúa expresión2. Se ve más claro con un ejemplo: b = (a>0) ? a : 0;

25


Capítulo 4. Operadores

Esto quiere decir que si a es mayor que 0, la expresión será b = a, y si a no es mayor que 0, la expresión será b = 0; Ojo: No confundir con un "if". Este operador devuelve un valor, mientras que el if es sólamente una instrucción. •

Operadores de Delegados: Para añadir métodos a un delegado se hace con + y +=, y para quitarselos, con - y -=.

Operadores de Acceso a Objetos: El operador para acceder a los miembros de un objeto es el punto. Así esta expresión: A.metodo1 ();

nos permite acceder al método "metodo1" del objeto A. •

Operadores de Punteros: Tenemos varios operadores. Para acceder a la dirección de memoria a la que apunta el puntero, lo hacemos con &puntero. Para acceder al contenido de la dirección de memoria, tenemos *puntero. Si lo que referencia el puntero es un objeto, podemos acceder a sus miembros con puntero->miembro.

Operadores de Obtención de Información sobre Tipos: Para averiguar el tipo de una variable, usamos el operador sizeof (variable). Nos devolverá un objeto de tipo System.Type. Si queremos hacer una comparación, usamos algo como esto: (expresion) is nombreTipo

que, como es lógico, nos devolverá un true o un false. •

Operadores de Conversión: Para convertir el tipo de un objeto en otro, precedemos el objeto que queremos cambiar con el tipo al que queremos convertir, entre paréntesis, de esta forma: variable1 = (int) variable2;

De esta forma, variable2 será tratada como si fuera un dato de tipo int, aunque no lo sea.

26


Capítulo 5. Introducción a las clases 5.1. Introducción a las clases en C# Como hemos dicho, C# es un lenguaje orientado objetos. A diferencia de lenguajes como C++ o Python en los que la orientación a objetos es opcional, en C# es imposible programar sin utilizar esta técnica. Una prueba de ello es que en C# cualquier método o variable está contenida dentro de un objeto. Por ahora puede asumirse que un objeto y una clase son la misma cosa. Una clase es como una plantilla que describe cómo deben ser las instancias de dicha clase, de forma que cuando creamos una intancia, ésta tendrá exactamente los mimos métodos y variables que los que tiene la clase.Los datos y métodos contenidos en una clase se llaman miembros de la clase y se accede a ellos siempre mediante el operador "." . En el siguiente ejemplo, se definirá una clase, Clase1 y en el método Main se creará una instancia de Clase1 llamada MiClase. Una buena idea es jugar un poco con el código para ver que la instancia de la clase efectivamente tiene los mismos miembros que la clase Clase1 (que sería la plantilla de la que hablábamos antes) using System; //definimos nuestra clase class Clase1{ public int a = 1; private double b = 3; public char c = ’a’; }

//usamos la clase que hemos creado class UsoClase{ public static void Main() { Clase1 MiClase = new Clase1(); // asi creamos una instancia de Clase1 Console.WriteLine( MiClase.c ); //podemos llamar a los tipos que hay dentro de C } }

los identificadores public delante de los tipos que hay dentro de Clase1 son necesarios para luego poder ser llamados desde otra clase, como en este caso, que estamos llamando a los miembros de una instancia de Clase1 desde UsoClase. Pero en las clases no solo hay variables, también podemos incluir métodos. using System; //definimos nuestra clase class Clase1{ public int a = 1; public double b = 3;

27


Capítulo 5. Introducción a las clases public char c = ’a’; public void Descripcion() { Console.WriteLine("Hola, soy una clase"); } }

//usamos la clase que hemos creado class UsoClase{ public static void Main() { Clase1 MiClase = new Clase1(); // asi creamos una instancia de Clase1 Console.WriteLine( MiClase.c ); //podemos usar todos los tipos que hay dentro de MiClase.Descripcion(); } }

Podemos hacer más cosas con las clases, como heredar otras clases o implementar interfaces, pero en este capítulo nos centraremos en el uso de métodos y variables.

5.1.1. Métodos Los métodos, también llamados funciones, son trozos de código que reciben unos datos, hacen algo con esos datos, y a veces devuelven algún valor. En C#, todos los métodos se encuentran contenidas dentro de un objeto. La estructura mínima de un método tiene las siguientes partes: •

Tipo devuelto

Nombre del método

Parámetros (puede ser vacío)

Cuerpo del método

de forma que el siguiente método: double Divide( double a, double b ) { return a/b; }

devuelve un tipo double, tiene por nombre Divide, los parámetos son dos tipo double, y el cuertpo del método es simplemente "return a/2;".

28


Capítulo 5. Introducción a las clases Cuando queramos llamar a un método, debemos simplemente poner el nombre del método y sus argumentos dentro de un paréntesis separados por comas. Para llamar al método Dive declarado antes, simplemente debemos escribir Divide(8, 2);

Según lo que hemos visto, el ejemplo del método Divide() completo neceista tener tener una clase donde definirse y un método Main() donde ejecutarse. using System; class Metodo{ double Divide( double a, double b ) { return a/b; } } class Principal{ public static void Main() { Metodo m = new Metodo(); Console.WriteLine( m.Divide(8, 2) ); } }

5.1.2. Modificadores public y static El modificador public lo hemos utilizado anteriormente. Se puede utilizar en la declaración de cualquier método o variable, y como es de esperar, produce el efecto de que el campo afectado se vuelve &público&, esto es, se puede utilizar desde otras clases using System; class Metodo{ public double Divide( double a, double b ) { return a/b; } } class Principal{ public static void Main() {

29


Capítulo 5. Introducción a las clases Metodo m = new Metodo(); Console.WriteLine( m.Divide(8, 2) ); } }

Si por ejemplo intentamos declarar el método Divide sin el modificador public, obtendremos un error en tiempo de compilación. El modificadro complementario de public es private, que provoca que el método o dato solo sea accesible desde la clase en la que está declarado. Si no se especifica nada, se toma por defecto el modificador private De esta forma podríamos separar las clases Metodo y Principal en dos archivos separados, llamados por ejemplo metodo.cs y principal.cs . Para compilar esto, bastará compilar ambos archivos al mismo tiempo, de forma similar a esto: mcs principal.cs metodo.cs Además, tampoco es necesario crear una instancia de la clase sólo para acceder a un método declarado en ella. Para eso debemos anteponer a la declaración del método el modificador static. Los métodos estáticos se caracterizan por no necesitar una instancia de la clase para cumplir su función, pero como contrapartida, no pueden acceder a datos propios de la clase. using System; class Metodo{ public static double Divide( double a, double b ) { return a/b; } } class Principal{ public static void Main() { Console.WriteLine( Metodo.Divide(8, 2) ); } }

Los métodos estáticos se utilizan en multitud de situaciones. Por ejemplo, el método Console.WriteLine() o las funciones de la librería matemática estándar no son más que métodos estáticos de sus respectivas clases

5.1.3. Constructores e instancias de una clase Como hemos visto, las instancias de una clase se crean con la sintaxis nombreclase objeto = new nombreclase( argumentos );

30


Capítulo 5. Introducción a las clases

donde nombreclase es el nombre que le hemos dado a la definición de la clase, argumentos es una lista de argumentos posiblemente vacía y objeto es el nombre que queremos darle a la instancia de la clase. Una vez creada una clase, sus miembros se inicializan a sus valores predeterminados ( cero para valores numéricos, cadena vacía para el tipo string, etc. ). La siguiente clase representa un punto sobre el plano, de forma que tiene dos valores públicos X e Y, y un método que calcula la distancia al origen del punto (módulo) using System; class Punto{ public double X; public double Y; public double Modulo() { double d; d = Math.Sqrt(X*X + Y*Y); //Sqrt = raiz cuadrada return d; } }

class Principal{ public static void Main() { Punto A = new Punto(); A.X = 1; A.Y = -1; Console.WriteLine("El modulo del punto (1,1) es: {0}", A.Modulo() ); } }

Ahora bien, la forma en la que se crea la instancia, es decir, inicializando los datos a cero (ejercicio: comprobar esto), se puede personalizar, de forma que podemos construir nuestro propio constructor que le diga a la clase los valores por defecto que debe tomar. Esto se realiza simplemente escribiendo dentro de la clase un método que tenga el mismo nombre que la clase y en el que no se especifica el valor devuelto. La clase Par con un constructor sería así: using System; class Punto{ public double X;

31


Capítulo 5. Introducción a las clases public double Y; public Punto() //constructor { X = 1; Y = 1; } public double Modulo() { double d; d = Math.Sqrt(X*X + Y*Y); //Sqrt = raiz cuadrada return d; } }

de forma que ahora al crear una instancia de la clase se crea el punto (1,1) en lugar del (0,0), que era el que se creaba por defecto. De esta forma, al crear la instancia, par ya contendrá los valores (1,1) . En la práctica se utilizan mucho constructores con parámetos, de forma que al crear la instancia se le asignan valores según los parámetros. La siguiente implementación de Par contiene un constructor que acepta un par de valores, que servirán para inicializar los valores A y B class Punto{ public Punto( double val1, double val2) { X = val1; Y = val2; } ... }

También tenemos la posibilidad de clarar una clase con varios constructores (cada uno con diferenctes parámetros) Lo que hará el compilador de C# es buscar el constructor que se adecúe a los parámetros que le llegan, y ejecutarlo como si fuera un método más. Dependiendo de la llamada que se haga en el "new", usaremos un constructor u otro.

5.1.4. Sobrecarga de métodos En C#, al igual que en C++ y en Java es posible definir varios métodos con el mismo nombre pero con distintos parámetros, de forma que el compilador decide a cuál se llama dependiedo de los parámetros que le lleguen.

32


Capítulo 5. Introducción a las clases Esto es muy práctico, pués no tienes que renombrar cada función según el tipo de valor que acepta. El siguiente ejemplo implementa un par de métodos que elevan al cuadrado el valor que reciben, y se implementan para tipos double y para int. En C, que es un lenguaje que no soporta sobrecarga de métodos, se tendría que haber llamado distinto a ambos métodos, por ejemplo alcuadrado_double y alcuadrado_int using System; class Eleva{ public static double AlCuadrado( int a ) { return a*a; } public static double AlCuadrado( double a ) { return a*a; } } class Principal{ public static void Main() { Console.WriteLine("4 al cuadrado es {0}", Eleva.AlCuadrado(4) ); Console.WriteLine("3.2 al cuadrado es {0}", Eleva.AlCuadrado(3.2) ); } }

5.1.5. La palabra reservada this La palabra reservada this sirve para hacer referencia a miembros de la clase en caso de que se quiera especificar, ya sea por motivos de colisión de nombres o por la claridad del código. Su sitaxis es this.campo

donde campo es la variable de la clase a la que queremos hacer referencia. En el siguiente ejemplo, declaramos un constructor para la clase Punto, que toma dos argumentos X e Y. Entonces es obligado el uso de this para distinguir entre el X de la clase y el X tomado como parámetro class Complejo{ double X; double Y; Complejo(double X, double Y)

33


Cap铆tulo 5. Introducci贸n a las clases { this.X = X; this.Y = Y; } }

34


Capítulo 6. Variables y parámetros 6.1. Variables y parámetros 6.1.1. Variables Las variables representan un espacio donde alojar información. Toda variable tiene un tipo que determina qué valor puede ser almacenado en la variabe. Las variables locales son variables que son declaradas dentro de métodos, propiedades o indexadores. Una variable local queda definida especificando su tipo y una declaración que especifica el nombre de la variable y un valor inicial opcional, como en: int a; int b = 1;

pero también es posible declarar varias variables locales del mismo tipo de la siguente forma: int a, b = 1;

La variable debe tener asignado un valor antes de que pueda ser utilizada. El ejemplo: class Test { static void Main() { int a; int b = 1; int c = a + b; // error, a no tiene valor asignado } }

da como resultado un error en tiempo de compilación porque intenta usar la variable antes de que ésta haya tomado ningún valor. Un campo es una variable asociada a una clase o estructura. Un campo declarado con el modificador static define una variable estática, esto es, que no necesita que se haya creado una instancia de la clase en la que está contenida, mientras que un campo declarado sin este modificador define una variable instancia. Un campo estático está asociado a un tipo, mientras que una variable instancia está asociada con una instancia. El ejemplo

class Empleado { private static string nombre;

35


Capítulo 6. Variables y parámetros public int DNI; public decimal Salario; }

muestra la clase Empleado que tiene una variable estática privada y dos variables instancia públicas.

6.1.2. Parámetros La declaración formal de parámetros también define variables. Hay cuatro tipos de parámetros: parámetros por valor, por referencia, parámetros de salida, y arreglos de parámetros. 6.1.2.1. Paso por valor El paso de parámetros por valor es usado por defecto para pasar parámetros a métodos. Cuando se pasa un parámetro por valor a una función realmente se está pasando una copia de dicho parámetro, por lo que las modificaciones que le hagamos al parámetro dentro del método no afectarán al parámetro original. El ejemplo using System; class Test { static void F(int p) { p++; Console.WriteLine("p = {0}", p); } static void Main() { int a = 1; Console.WriteLine("pre: a = {0}", a); F(a); Console.WriteLine("post: a = {0}", a); } }

muestra un método F que tiene un parámetro por valor llamado p. El ejemplo produce la salida: pre: p = 2 post: a = 1

a = 1

aunque el valor del parámetro p haya sido modificado dentro del método.

36


Capítulo 6. Variables y parámetros 6.1.2.2. Paso por referencia El paso de parámetros por referencia es la contraposición lógica al paso por valor. En el paso por referencia no se realiza ninguna copia del objeto, sino que lo que se le pasa a la función es una referencia del objeto, de forma que el parámetro pasa directamente a la función y cualquier modificación sobre el parámetro dentro de la función afectará al parámetro original using System; class Test { static void Swap(ref int a, ref int b) { // intercambia los dos valores int t = a; a = b; b = t; } static void Main() { int x = 1; int y = 2; Console.WriteLine("pre: x = {0}, y = {1}", x, y); Swap(ref x, ref y); Console.WriteLine("post: x = {0}, y = {1}", x, y); } }

muestra un método swap que tiene dos parámetros por referencia. La salida producida es: pre: x = 1, y = 2 post: x = 2, y = 1

La palabra clave ref debe de ser usada tanto en la declaración formal de la función como en los usos que se hace de ésta. El parámetro de salida es similar al parámetro por referencia, salvo que el valor inicial de dicho argumento carece de importancia. Un argumento de salida se declara con el modificador out. El ejemplo using System; class Test { static void Divide(int a, int b, out int result, out int remainder) { result = a / b; remainder = a % b; } static void Main() { for (int i = 1; i < 10; i++) for (int j = 1; j < 10; j++) {

37


Capítulo 6. Variables y parámetros int ans, r; Divide(i, j, out ans, out r); Console.WriteLine("{0} / {1} = {2}r{3}", i, j, ans, r); } } }

muestra un método Divide que incluye dos parámetros de salida. Uno para el resultado de la división y otro para el resto.

6.1.3. Arreglo de parámetros Para los parámetros descritos anteriormente hay una correspondencia unívoca entre los argumentos que puede tomar la función y los parámetros que los representan. Un arreglo de parámetros permite guardar una relación de varios a uno: varios argumentos pueden ser representados por un único arreglo de parámetros. En otras palabras, los arreglos de parámetros permiten listas de argumentos de tamaño variable. Un arreglo de parámetros se declara con el modificador params. Sólo puede haber un arreglo de parámetros en cada método, y siempre debe ser el último método especificado. El tipo del arreglo de parámetros siempre es un tipo arreglo unidimensional. Al llamar a la función se puede pasar un único argumento de su tipo o bien cualquier número de argumentos de tipo del tipo del arreglo. El ejemplo using System; class Test { static void F(params int[] args) { Console.WriteLine("no de argumentos: {0}", args.Length); for (int i = 0; i < args.Length; i++) Console.WriteLine("args[{0}] = {1}", i, args[i]); } static void Main() { F(); F(1); F(1, 2); F(1, 2, 3); F(new int[] {1, 2, 3, 4}); } }

muestra un método F que toma un número variable de argumentos int, y varias llamadas a este método. La salida es: no de arguments: 0 no de argumentos: 1 args[0] = 1

38


Capítulo 6. Variables y parámetros no de argumentos: 2 args[0] = 1 args[1] = 2 no de argumentos: 3 args[0] = 1 args[1] = 2 args[2] = 3 no de argumentos: 4 args[0] = 1 args[1] = 2 args[2] = 3 args[3] = 4

La mayoría de los ejemplos presentes en esta introducción utilizan el método WriteLine de la clase Console. El comportamiento para las sustituciones, como muestra el ejemplo int a = 1, b = 2; Console.WriteLine("a = {0}, b = {1}", a, b);

se consigue usando un arreglo de parámetros. El método WriteLine proporciona varios métodos sobrecargados para el caso común en el que se pasan un pequeño números de argumentos, y un método que usa un arreglo de parámetros. namespace System { public class Console { public static void public static void public static void ... public static void } }

WriteLine(string s) {...} WriteLine(string s, object a) {...} WriteLine(string s, object a, object b) {...} WriteLine(string s, params object[] args) {...}

39


Capítulo 7. Propiedades e indizadores 7.1. Propiedades e indizadores 7.1.1. Propiedades Las propiedades son una característica de C# que permiten aparentemente el acceso a un miembro de la clase mientras mantiene el control asociado al acceso mediante métodos. Para los programadores de Java hay que decir que esto no es más que la formalización del patrón de asignación (setter) y método de lectura (getter) Las propiedades son como métodos que se declaran dentro de un bloque asociado a una variable mediante las palabras reservadas get (se encarga de devolver algo cuando se llama al tipo que lo contiene ) y set (que hace algo cuando se le asigna un valor a la variable que lo contiene. Este valor viene especificado en la variable value ) using System; class TestProperties { private static string clave; public string Clave { get { Console.WriteLine ("Acceso a la propiedad clave"); return clave; } set { Console.WriteLine ("Cambio del valor de clave"); clave = value; } } } class Test { public static void Main () { TestProperties tp = new TestProperties(); string c = "ClaveClave"; tp.Clave = c; Console.WriteLine (tp.Clave); } }

En realidad, lo que se hace es declarar una variable privada de forma que no se puede acceder de forma directa, y se crean dos métodos ( o uno si solo se requiere acceso de lectura) que permiten acceder al

40


Capítulo 7. Propiedades e indizadores contenido de la variable y tal vez modificarla. Si no queremos que se pueda moficiar la variable, no incluímos el método "set" y ya tendríamos propiedades de sólo lectura.

7.1.2. Indexadores Hemos visto, en el apartado en el que tratamos las propiedades, que podemos acceder a una variable privada de una clase a través de eventos que nos permiten controlar la forma en la que accedemos a dicha variable. Los indexadores nos van a permitir hacer algo parecido. Nos van a permitir acceder a una clase como si se tratara de un arreglo. Lo vemos de forma más sencilla con un ejemplo: using System; class PruebaIndexadores { private int[] tabla = {1, 2, 3, 4};

public int this [int indice] { get { Console.WriteLine ("La posicion {0} de la tabla tiene el valor {1}", indice, tabla[ return tabla[indice]; } set { Console.WriteLine ("Escrito el valor {0} en la posición {1} de la tabla", value, in tabla[indice] = value; } } }

Tenemos una clase PruebaIndexadores en la que hay un array llamado "tabla", declarado como privado, por lo que no podremos acceder a él desde fuera de nuestra clase. Pero hemos declarado también un indexador (public int this [int indice]), que nos permitirá acceder a él de forma más controlada. Para probar esta clase, creamos otra clase con un punto de entrada (public static void Main ()), que será donde hagamos las pruebas. Primero creamos un objeto de la clase PruebaIndexadores: PruebaIndexadores obj = new PruebaIndexadores ();

Luego accedemos a una posición del indexador:

41


Capítulo 7. Propiedades e indizadores int a = obj[3];

Esta línea lo que hace es llamar al indexador, pasándole como parámetro el índice, en este caso 3. Al ser una consulta de lectura, se ejecuta el código que haya en la parte "get" del indexador. Una vez ejecutado, lo que nos aparece por pantalla es esto: La posicion 3 de la tabla tiene el valor 4

Vamos ahora a hacer un cambio en la tabla: obj[3] = 6;

Lo que se ejecuta ahora es la parte "set" del indexador. Lo que aparecerá en pantalla una vez ejecutado esto será: Escrito el valor 6 en la posición 3 de la tabla

Nótese que tenemos que hacer explícitamente el acceso al array (tabla[indice]=value) en el set, ya que el indexador no tiene forma de saber qué variable se supone que tiene que manejar. Si no pusiéramos esa línea, en realidad el indexador no cambiaría el valor del array. Para comprobar que realmente se ha hecho el cambio, volvemos a acceder al indexador: a = obj[3];

Y esta vez nos aparecerá esto: La posicion 3 de la tabla tiene el valor 4.

42


Capítulo 8. Clases Como hemos dicho, las clases definen nuevos tipos por referencia. Una clase puede heredar otra clase y puede implementar (varias) interfaces (conceptos que se verán en este capítulo).

8.1. Modificadores de acceso Los miembros de una la clase pueden ser constantes, campos, métodos, propiedades, contructores, destructores, y tipos anidados. Como en la moyoría de lenguajes orientados a objetos, cada miembro tiene una accesibilidad asociada, que controla a qué código se le permite el acceso a nuestros miembros. Hay cinco posibles formas de accesibilidad: public, protected, internal y protected internal. Sus características son las siguientes: •

public: Acceso no limitado. Cualquier código puede acceder a un elemento public

protected: Acceso limitado desde la propia clase o desde tipos derivados de la propia clase

internal: Acceso limitado al propio programa

protected internal: Acceso limitado al propio programa o a tipos derivados de la propia clase

private: Acceso limitado a la propia clase

Puesto que uno de los pilares principales de la programación orientada a objetos es la ocultación de datos, y para mantener una API lo más simple posible, se recomienda siempre el uso de private salvo cuando esté justificado el uso de un modificador distinto. Si no se especifica ningún modificador, la accesibilidad por defecto es la private

8.2. Herencia La herencia es un concepto fundamentar de la programación orientada a objetos. Cuando se dice que una cierta clase A hereda otra clase B significa que la clase A contiene todos los miembros de la clase B más algunos que opcionalmente puede implementar ella misma Las clases en C# soportan herencia simple, de forma que una clase puede derivar de otra, pero no de varias (como si era posible en C++). De hecho, en C# todas las clases derivan implícitamente de la clase object. La sintaxis que se utiliza es la siguiente: class MiClaseDerivada : MiClaseBase { //miembros

43


Capítulo 8. Clases }

En el siguiente ejemplo definimos una clase A con un método F(). Posteriormente definimos una clase B que hereda A y además define un método G(). Finalemente creamos una clase con un método Main() que llamará a los dos métodos de B, al implementado por B y al heredado using System;

class A{ public void F() { Console.WriteLine("Soy F() de A"); } }

class B : A{ public void G() { Console.WriteLine("Soy G() de B"); } }

class Principal{ public static void Main() { B clase_heredada = new B(); clase_heredada.F(); clase_heredada.G(); } }

8.2.1. La palabra reservada base La palabra reservada base sirve para acceder a miembros de la clase heredada de la misma forma que this sirve para acceder a miembros de la propia clase. Su sintaxis es idéntica a la de this, esto es: base.nombre_del_miembro

En el siguiente ejemplo declaramos una clase B que hereda A y que utiliza el método F() de A. class B : A{ public void H()

44


Capítulo 8. Clases { base.F(); Console.WriteLine("soy H() de B"); } }

8.2.2. Clases Abstractas Las clases abstractas son clases que contienen algún método incompleto, esto es, que está definido pero no implementado. Por lo tanto, no se pueden instanciar y su único propósito es servir de clase base de las que se derivarán otras clases. Las clases que heredan una clase abstracta deben implementar los métodos incompletos. Las clases abstractas se declaran con la palabra reservada abstract using System; abstract class A{ public void F(); //metodo no implementado } class B : A{ //error en tiempo de compilación, B tiene que definir un método F() }

8.2.3. Miembros virtual Métodos, propiedades e indexadores pueden ser virtual, lo que significa que su implementación puede ser sobreescrita en clases derivadas. El ejemplo using System; class A { public virtual void F() { Console.WriteLine("A.F"); } } class B: A { public override void F() {

45


Capítulo 8. Clases base.F(); Console.WriteLine("B.F"); } } class Test { public static void Main() { B b = new B(); b.F(); A a = b; a.F(); } }

muestra una clase A con un método virtual F, y una clase B que sobreescribe F. El método sobreescrito en B contiene una llamada, base.F(), el cual llama al método sobreescrito en A.

8.3. Interfaces TODO

8.4. Modificadores de Herencia A continuación se lista un breve resumen de los modificadores de herencia de C#

abstract: se aplica a clases o a miembros de la clase. Sección 8.2.2

new: confirma explícitamente la intención de ocultar un miembro heredado con el mismo nombre

override: especifica que el miembro está redefiniendo un método heredado con la misma firma

sealed: si se aplica a una clase se indica que esta clase no puede heredarse. Si se aplica a un método heredado junto a override se asegura que el método no es redefinido en clases heredadas

virtual: se aplica a métodos. Permite que las clases derivadas redefinan los métodos en lugar de ocultarlos

46


Capítulo 8. Clases

8.5. Algunas clases notables 8.5.1. Tipo arreglo Los arreglos (de cualquier tipo) no son más que instancias que derivan de la clase System.Array. Por lo tanto, son clases y se comportan como tales. Esto implica, por ejemplo, que los arreglos son tipos por referencia, y por lo tanto, al ser pasados a un método, no se pasa una copia del objeto, sino una copia de la referencia al objeto. El efecto, por lo tanto, es que el objeto pasa "por referencia". El siguiente ejemplo

using System; class Arreglo{ public static void cambiastring (string[] s) { s[0] = "cambiado"; } public static void Main() { string[] s = new string[] {"sin cambiar"}; cambiastring(s); Console.WriteLine(s[0]); } }

muestra cómo al pasar un arreglo a un método y modificar el arreglo dentro del método, el arreglo queda afectado. Como habrás supuesto, la salida del programa es "cambiado" y no "sin cambiar".

47


Capítulo 9. Delegados Los delegados son objetos que encapsulan métodos. Son muy similares a los punteros a funciones de C/C++

9.1. Delegados Los delegados son un tipo especial de clases que derivan de System.Delegate. Los delegados tienen como objetivo almacenar referencias a métodos de otras clases, de tal forma que, al ser invocados, ejecutan todos estos métodos almacenados de forma secuencial. Los delegados pueden almacenar métodos estáticos o instanciados, indiferentemente. Además los tipos de estos métodos son comprobados para evitar errores de programación. Es decir, un delegado solo almacenará referencias a métodos compatibles, esto es, que devuelvan el mismo tipo y tome los mismos argumentos. Aparte de eso, a los delegados no les influye cómo sea el método que están manejando. Veamos esto con un ejemplo. Aquí podemos ver la declaración de un delegado: delegate void DelegadoSimple( int i );

Este delegado, por ejemplo, sólo podrá guardar referencias a métodos que no devuelvan nada (void) y que reciban como parámetros un entero. Para almacenar métodos en un delegado, usamos la siguiente sintaxis, DelegadoSimple d = new DelegadoSimple( F );

, donde F es el método que queremos que almacenen (debemos de haberlo declarado antes). Como ya hemos dicho, los métodos que almacena un delegado deben de ser compatibles con éste, y cualquier intento de guardar métodos no compatibles provocará una excepción. Una vez que tenemos una instancia de nuestro delegado, para que se ejecute la función F() simplemente debemos llamar a nuestro delegado, pués encapsula dicha función, de forma que d()

ejecuta la función F(). Expuesto así, los delegados parecen una forma absurda de complicar más las cosas (si queremso llamar a una función, pués la llamamos y punto, no hace falta encapsularla en un delegado). Sin embargo, los delegados son fundamentales, pués son la forma de pasar funciones como parámetros a métodos.

48


Capítulo 9. Delegados Una vez tenemos declarado nuestro delegado, ya podemos crear instancias del mismo. Veamos este sencillo ejemplo: Ejemplo 9-1. Ejemplo simple de uso de delegados using System; class Ejemplo { delegate void MiDelegado (int a); void Imprime (int entero) { Console.WriteLine ("Llamada a Ejemplo.Imprime({0})", entero); } public static void Main() { Ejemplo ejemplo = new Ejemplo (); MiDelegado foo; foo = new MiDelegado (ejemplo.Imprime); foo (10); } }

Tras crear una instancia de nuestra clase Ejemplo, creamos otra de nuestro delegado. Posteriormente insertamos en el delegado una referencia al método Imprime, que pertenece a la instancia de nuestra clase. Finalmente convocamos al delegado a que ejecute los métodos que contenga. Si lo compiláramos obtendríamos esto: bash$ mcs ejemplo.cs Compilation succeeded bash$ mono ejemplo.exe Llamada a Ejemplo.Imprime(10)

Otra cuestión a tener en cuenta cuando programemos con delegados, es que éstos no tienen en cuenta la visibilidad de los métodos. Esto permite llamar a métodos privados desde otros si ambos tienen acceso al delegado. Es decir, imaginemos que una clase guarda en un delegado referencia a uno de sus métodos privados. Si desde otra clase que tenga acceso al delegado (pero no al método privado) se convoca a éste, se ejecutará ese método. En verdad no se está violando la privacidad del método, porque no es la clase quien lo convoca, sino el delegado, que sí tiene acceso al mismo.

9.1.1. Llamadas a múltiples métodos Hasta el momento hemos visto como hacer que un delegado guarde referencia de un sólo método. Sin embargo, existe una clase, System.MulticastDelegate, que deriva de System.Delegate, que se diferencia de esta última en que puede tener múltiples métodos en su lista de invocaciones.

49


Capítulo 9. Delegados Para poder hacer esto usaremos los operadores sobrecargados ’+=’ y ’-=’ que, respectivamente, añaden o eliminan a un método de la lista de invocaciones de un delegado. Para intentar asimilar esto mejor, veámoslo con un ejemplo más completo. Ejemplo 9-2. Ejemplo de uso de delegados using System; delegate void MiDelegado (string cadena); class Ejemplo { public static MiDelegado delegado; public static void Main () { delegado = new MiDelegado (ClaseA.MetodoEstatico); ClaseA A = new ClaseA(); delegado += new MiDelegado (A.MetodoPublico); ClaseB B = new ClaseB(); // El constructor inserta MetodoPrivado // delegado += new MiDelegado (B.MetodoNoValido); // Excepción delegado ("Hola mundo"); } } class ClaseA { public static void MetodoEstatico (string cad) { Console.WriteLine ("ClaseA.MetodoEstatico ha sido llamado: {0}", cad); } public void MetodoPublico (string cad) { Console.WriteLine ("ClaseA.MetodoPublico ha sido llamado: {0}", cad); } } class ClaseB { void MetodoPrivado (string cad) { Console.WriteLine ("ClaseB.MetodoPrivado ha sido llamado: {0}", cad); } public void MetodoNoValido (int entero) { Console.WriteLine ("ClaseB.MetodoNoValido ha sido llamado: {0}", entero); } public ClaseB () { Ejemplo.delegado += new MiDelegado (MetodoPrivado); } }

50


Capítulo 9. Delegados Podemos ver que, en este caso, nuestro delegado sólo manipula métodos que no devuelvan nada y que reciban como único parámetro una cadena. Si observamos los métodos que componen ClaseA y ClaseB, el denominado MetodoNoValido no concuerda con el delegado, ya que recibe un entero y no una cadena. Eso implica que no vamos a poder llamarlo desde ninguna instancia del delegado que hemos declarado. Sin embargo, con las otras no tendremos ningún problema. Bien, observemos paso a paso lo que hace el programa. Fijemos nuestra atención en el método principal (Main). Primero insertamos un método estático. Como sabemos, para llamar a un método de este tipo, se hace a partir del nombre de la clase y no de una instancia. Bien, hasta aquí nada que no hayamos visto ya, pero ahora insertemos un segundo módulo en el delegado. Como hemos dicho, hemos usado el operador ’+=’ para incluir otro método más en nuestro delegado, en este caso MetodoPublico. Si usaramos de nuevo el operador ’=’, borraríamos la antigua lista de invocaciones y crearíamos una nueva con sólo una función referenciada. Ahora tenemos dos métodos en la lista de invocaciones de nuestro delegado. Por último, creamos una instancia de ClaseB, la cual en su constructor incluye una referencia más al delegado, en este caso a MetodoPrivado. Si ahora compilamos y ejecutamos este código, obtendremos esto: bash$ mcs ejemplo.cs Compilation succeeded bash$ mono ejemplo.exe ClaseA.MetodoEstatico ha sido llamado: Hola mundo ClaseA.MetodoPublico ha sido llamado: Hola mundo ClaseB.MetodoPrivado ha sido llamado: Hola mundo

Como vemos, aunque hemos convocado al delegado desde la clase Ejemplo, que no tiene acceso a MetodoPrivado, éste ha sido ejecutado. Como explicamos, esto es así porque realmente quien lo está haciendo es el delegado y no el método Main. Por último, una cuestión más. Hasta el momento hemos visto a delegados que gestionan miembros que no devuelven ningún valor. Pero, ¿qué ocurre cuando los devuelven? En este caso, la ejecución del delegado no devuelve todos esos valores, sólo el que retorne el último método de su lista de invocación.

51


Capítulo 10. Eventos 10.1. Eventos Mientras que antiguamente la interacción entre un programa y el usuario se limitaba a que éste introdujese datos en determinados momentos de la ejecución del primero en los últimos años este modelo está siendo relegado por un crecimiento exponencial de las aplicaciones GUI (con Interfaz Gráfica de Usuario). En esta nueva aproximación conocida como Programación Orientada a Eventos el programa queda a la espera de que el usuario vaya haciendo tal o cual cosa sin tener casi ningún control sobre el orden en el que se producirán los sucesos o más aún, ni cuales son los que darán. Más aún este tipo de programación proporciona una asincronía muy útil gracias al uso de las devoluciones de llamada, tal y como se vió con los delegados. En nuestro caso estamos de suerte porque el lenguaje en sí ¡soporta eventos!. Sin entrar en detalles sobre qué son exactamente, veremos como usarlos. Para quienes no se queden tranquilos con eso podemos decir que los eventos son una especie de mensajes que lanza el entorno de ejecución de manera que un objeto pueda avisar de cuando ha sucedido algo de lo que quiera avisar a otros objetos. En .NET se usa un mecanismo conocido como de publicación/subscripción. Así que lo que tendremos será una clase que publica un evento al que, aquellas clases que quieran estar al tanto de cuando ha sido lanzado ese evento se subscriben. De hecho, es práctica corriente no lanzar el evento si no hay ninguna clase subscrita. Ya hemos dicho todo lo que hay que decir de momento. Como al principio es un poco lioso trabajar con delegados y eventos (normalmente se llega a esta sección sin haber practicado lo suficiente con los delegados), haremos una receta del procedimiento general a seguir. •

Crear la clase que contendrá el evento, la que lo publica (la que lo puede lanzar). Supongamos que el evento que queremos lanzar se llama OnButtonClicked. Si queréis podéis poner un nombre genérico como MyEvent. Consecuentemente cambiad todos los OnButtonClicked por MyEvent si hacéis eso.

Definimos el manejador del evento. Es el código que se asocia con el evento en tiempo de ejecución y que se invoca cuando la clase que se suscribe recibe la notificación del evento. Es un delegado de tipo void y que recibe dos parámetros: el primero un objeto, el segundo una instancia de la clase que guarda la información del evento. Esta clase tiene que derivar de EventArgs. [modificadores de acceso] delegate void OnButtonClickedEventHandler (object source,

Declaramos el evento con el tipo del delegado antes definido. [modificadores de acceso] event OnButtonClickedEventHandler OnButtonCliked;

Definimos el método que puede lanzar el evento y establecemos los valores para el objeto que guarda la información del evento. Esto es código en la clase que publica el evento y es el código que se encarga de lanzar el evento con la información adecuada en el momento adecuado.

52


Capítulo 10. Eventos •

Creamos la clase que guarda la información del evento derivándola de EventArgs: class OnButtonClickedEventArgs : EventArgs { // código // al menos un constructor y conveniente que tenga propiedades para acceder // a las variables miembro. }

Creamos la clase que se suscribe al evento.

Suscribimos la clase con:

ClaseQuePublicaElEvento.OnButtonClicked += new OnButtonClickedEventHandler (callback

Donde callback es la función de devolución de llamada (retrollamada o callback) que queráis que se llame cuando se recibe la notificación del evento. Esa función debe estar en la clase que se subscribe al evento. Como el operador que se usa para las subscripciones es del tipo += podemos subscribir varios manejadores de evento a un mismo evento. ClaseQuePublicaElEvento.OnButtonClicked += new OnButtonClickedEventHandler (callback2);

Y así añadir tantos callbacks como queramos ya que no se sobreescriben los unos a los otros (lo que pasaría si se hubiese usado el operador =) si no que se añaden. También podemos desuscribirnos de un evento mediante el operador -=. Para ello no hay más que escribir lo mismo pero con un - en vez de un +: ClaseQuePublicaElEvento.OnButtonClicked -= new OnButtonClickedEventHandler (callback2);

Tenéis un ejemplo muy simple en el C# Reference Manual de Anders Hejlsberg y Scott Wiltamuth. Pero también he hecho un par de ejemplos a medida un poco más elaborados. Clase que publica. Guardadla en Dude.cs // Un tipo tratando de ligarse a una tipa using System; using System.Threading; public class Dude { // Declaramos el delegado que ’envolvera’ a las posibles retrollamadas conectadas // al evento SignalEmitted. public delegate void DudeSpeechEventHandler (object s, DudeSpeechEventArgs args); // El evento que emitiremos. public event DudeSpeechEventHandler MessageFromDude;

53


Capítulo 10. Eventos

public void StartBrainWashing () { for (int i=0; i < 5; i++) { if (MessageFromDude != null) MessageFromDude (this, new DudeSpeechEventArgs (msgs[i], i)); Thread.Sleep (3000); } } string[] msgs = {"Hey!. Garl!", "What’s up, honey?", "Your nick is making me real horny ;)", "Do you have a webcam setup? 8)~", "Uh?", "FUU..."}; } public class DudeSpeechEventArgs : EventArgs { public DudeSpeechEventArgs (string msg, int index) { this.msg = msg; this.index = index; } public string Message { get { return msg; } set { msg = value; } } public int Index { get { return index; } } string msg; int index; }

Clase que se subscribe, guardadla en Chic.cs // La clase a la que le toca aguantar a los dudes. using System; using System.Threading; public class Chic { public Chic () { } public void Suscribe (Dude dude) { this.dude = dude; dude.MessageFromDude += new Dude.DudeSpeechEventHandler (message_received); }

54


Capítulo 10. Eventos

public void Unsuscribe (Dude dude) { dude.MessageFromDude -= new Dude.DudeSpeechEventHandler (message_received); } private void message_received (object s, DudeSpeechEventArgs args) { Console.WriteLine ("<dude>: {0}", args.Message); if (args.Index < 3) { Thread.Sleep (4000); Console.WriteLine ("<you>: {0}", msgs[args.Index]); } else { Unsuscribe (dude); Console.WriteLine ("Connection reset by peer..."); } } private string[] msgs = {"hi.", "nothing. Just talking with a friend.", "really?"}; private Dude dude; }

Otro ejemplo más. Esta clase guardadla como Emisor.cs. // Una clase simple que cada 3 segundos envia una senal using System; using System.Threading; public class Emisor { // Declaramos el delegado que ’envolvera’ a las posibles retrollamadas conectadas // al evento SignalEmitted. public delegate void SignalEmittedEventHandler (object s, SignalEmittedEventArgs args); // El evento que emitiremos. public event SignalEmittedEventHandler SignalEmitted; public void Emit () { for (int i=0; i < 4; i++) { Thread.Sleep (3000); string time = DateTime.Now.Ticks.ToString (); if (SignalEmitted != null) SignalEmitted (this, new SignalEmittedEventArgs (time)); } } } public class SignalEmittedEventArgs : EventArgs { public SignalEmittedEventArgs (string time) {

55


CapĂ­tulo 10. Eventos this.time = time; } public string Time { get { return time; } set { time = value; } } string time; }

Mientras que esta la tenĂŠis que meter en Satellite.cs // Una clase que recibe las senales de instancias // de tipo Emisor. using System; using System.Collections; public class Satellite { public Satellite (Emisor emisor) { emisors.Add (emisor); foreach (Emisor e in emisors) e.SignalEmitted += new Emisor.SignalEmittedEventHandler (signal_emitted); } private void signal_emitted (object s, SignalEmittedEventArgs args) { Console.WriteLine ("Signal emitted at: {0}", args.Time); } public void AdEmisor (Emisor emisor) { emisors.Add (emisor); } private ArrayList emisors = new ArrayList (); }

Ya solo os queda tener los experimentos que hacer. Os propongo estos: Experiment1.cs // Un experimento simple que pone a prueba nuestras clases // Emisor and Satellite. using System; public class Experiment { static void Main () {

56


Capítulo 10. Eventos Emisor emisor = new Emisor (); Satellite satellite = new Satellite (emisor); emisor.Emit (); } }

y este otro: // Una tipica charla del IRC using System; public class Experiment { static void Main () { Dude dude = new Dude (); Chic chic = new Chic (); chic.Suscribe (dude); dude.StartBrainWashing (); } }

Estos ejemplos los podéis compilar con este makefile: all: experiment1 experiment2 ############### Very first example of event-driven programming. experiment1: mcs -o experiment1.exe Experiment1.cs Satellite.cs Emisor.cs chmod +x experiment1.exe ############### Another example of event-driven programming. experiment2: mcs -o experiment2.exe Experiment2.cs Dude.cs Chic.cs chmod +x experiment2.exe clean: rm -rf *.exe rm -rf *~

Y eso es todo cuanto necesitáis saber de los eventos para poder usarlos con soltura en vuestros programas y en particular para los que queráis dar el salto y empezar a hacer aplicaciones GUI en Gtk#, GNOME# o Glade#.

57


Capítulo 11. Programación multihilo 11.1. Threads -- Programación multihilo La programación con hilos, también conocida como multiproceso, tiene la ventaja de poder trabajar de manera asíncrona. Esto permite que aquellos procesos que pueden requerir un tiempo más o menos largo en llevarse a cabo se pongan a trabajar ’paralelamente’ al proceso principal, de manera que la aplicación pueda retomar el control y así el usuario seguir trabajando con ella. Nota1: la programación multihilo no es siempre la solución correcta para todas las aplicaciones e incluso en algunos casos puede relentizar la aplicación aunque parezca que no es así o cosas aún peores como pérdida de datos, etc. Nota2: la implementación que hace Mono de las clases del espacio de nombres System.Threading está basada en los pthreads (los hilos POSIX que tan bien implementados están en Linux) y es fácil comprobar que el paso de trabajar con unos a trabajar con otros es casi inmediato. Una buena referencia es el libro "Programación Linux al descubierto" de Kurt Wall, así como la propia pthreads.h. Igualmente el recolector de basura que se usa hasta ahora en Mono (el GC de Bohem puede ser y debe ser compilado pasándole la opción --enable-threads=pthreads Veamos los conceptos generales: •

Hilo: un hilo de ejecución es una unidad de procesamiento.

Multitarea: la ejecución simultánea de varios hilos.

La multitarea puede ser de dos tipos, uno de los cuales está bastante obsoleto y además queda fuera de .NET de manera que no podremos usar los hilos de la máquina virtual en sistemas operativos que usen este clase primitiva de multitarea conocida como ’preferente’. El que vamos a usar nosotros es el tipo ’cooperativo’. La diferencia fundamental radica en que en el caso de la multitarea preferente el programador tiene que encargarse de liberar los hilos, etc, mientras que en la multitarea preferente el procesador asigna fracciones de tiempo de procesado a cada hilo y salta de hilo en hilo cada tiempo. Nota: los hilos y la forma de trabajar con ellos tal y como lo vamos a hacer aquí no es algo propio de C# sino que es extensible a todo el Framework de .NET (i.e. a cualquier lenguaje en .NET). Si en algún momento se está tratando alguna característica exclusiva de C#, se comentará explicítamente. Así, quién ya posea conocimientos de Threads en .NET puede saltarse el resto de este tema. Nuestra receta para crear un hilo simple es como sigue: • •

Metemos el espacio de nombres System.Theading en nuestra clase. Asignamos qué método ejecutará el hilo, para ello usamos un delegado de tipo ThreadStart que encapsule el método.

58


Capítulo 11. Programación multihilo •

Creamos el hilo pasándole al constructor el delegado anteriormente creado.

Ponemos el hilo en ejecución.

Ya tenemos el procedimiento a seguir para crear una aplicación multitarea elemental. Veamos el código: // Incluid esto en un método cualquiera que queráis que lance el nuevo // hilo. Por ejemplo en un Main. ¡De hecho podemos hacer que Main solo // haga eso!. // metodo es un método que queremos poner en un hilo aparte. ThreadStart delegadoQueGuardaElMetodo = new ThreadStart (metodo); // creamos el hilo pasándole el delegado al constructor. Thread nuevoHilo = new Thread (delegadoQueGuardaElMetodo); // Empezamos a ejecutar el hilo. nuevoHilo.Start ();

Otra forma de instanciar un hilo es consiguiendo una referencia al hilo actual de ejecución, esto se hace sin más que llamar a la propiedad estática Thread.CurrentThread. Thread t = Thread.CurrentThread;

Dejadme un segundo que muestre el equivalente en pthreads de este ejemplo sencillo para que comprobéis por vosotros mismos los que he comentado antes de la similitud Threads/pthreads: #include <stdio.h> #include <stdlib.h>; #include <pthread.h>; void funcion(); int main (int argc, char *argv[]) { pthread_t pthrd1; /* declaramos el hilo */ int ret; /* el valor de retorno para gestionar el /* estado de lacreación del hilo */ ret = pthread_create (&pthrd1, NULL, (void *) funcion, NULL); if (ret) { perror ("No se pudo crear el primer hilo"); exit (EXIT_FAILURE);

59


Capítulo 11. Programación multihilo } pthread_join (pthrd1, NULL); exit (EXIT_SUCCESS); } void funcion () { /* Ponle lo que quieras hacer aqui */ }

11.1.1. Controlando el tiempo de vida del hilo Los métodos fundamentales que se deben conocer si se quiere tener un control absoluto de los hilos en .NET son cinco: Thread.Sleep, Thread.Suspend, Thread.Resume, Thread.Interrupt y Thread.Abort. Es más, para casi todas las aplicaciones que vayáis a desarrollar, os bastará con conocer Thread.Sleep!. Antes de ver cómo funcionan, presentémoslos: •

Thread.Sleep (int time) -- Para el hilo durante ’time’ milisegudos.

Thread.Interrupt -- Interrumpe el hilo parada para que vuelva a la ejecución antes de que se acabe ’time’.

Thread.Suspend -- El hilo se queda suspendido hasta que otro hilo lo llame con Thread.Resume.

Thread.Resume -- Recupera un hilo suspendido.

Thread.Abort -- Destruye un hilo.

La utilización de esos métodos es muy sencilla. Thread.Sleep acepta como parámetro el tiempo que se quiere que la hebra (o hilo, es lo mismo) permanezca dormida. Si le decimos Thread.Sleep (5000) se detendrá durante cinco segundos o lo que es lo mismo, 5000ms. // más codigo por aquí... // Llamada a Thread.Sleep para parar la hebra durante 3 segundos Thread.Sleep (3000); // seguimos poniendo código... // Para ver ejemplos completos mirar la sección de Eventos de este mismo tutorial.

Si el valor pasado es 0 la hebra devolverá devolverá el resto del timeslice que le quedaba. Si por el contratio se le pasa Timeout.Infinite, el hilo se nos para indefinidamente hasta que alguna otra hebra llame al metodo Interrupt de la hebra suspendida. La diferencia fundamental entre Thread.Sleep y la otra

60


Capítulo 11. Programación multihilo manera de detener una hebra, llamando a Thread.Suspend, es que este último puede ser invocado desde la hebra actual o desde otra. Además, en caso de detener una hebra con Suspend, no podremos volver a ponerla en ejecución hasta que no se haga desde otra con el método Thread.Resume. (Nota: cuando se escribía este párrafo los métodos Thread.Suspend y Thread.Resume estaban parcialmentemente implementados en Mono y cabe la posibilidad de que lo sigan estando cuando leáis esto. Si véis que código que los usa no funciona como debiera,i.e. los hilos no se suspenden cuando debieran, comprobad que no se lanzó una excepción del tipo NotImplementedException o ningún WARNING. Hace poco esos métodos no hacían nada pero después de intentar probar estos ejemplos añadimos esos avisos a la clase Thread). Por último nos queda ver Thread.Abort. Thread.Abort es un método un tanto particular. En caso de ser llamado, el CLI (o CLR), aborta el hilo lanzando una excepción ThreadAbortException que no puede ser recogida. El CLI no permite recoger esa excepción y lo más que podremos hacer, si queremos hacer un cleanup, será llevar a cabo las medidas oportunas dentro de un bloque finally. Hay que tener en cuenta que mono no detendrá la ejecución del hilo inmediatamente. Se esperará alcanzar un punto seguro para hacer esto y ese punto lo escogerá mono. Si queréis que el hilo deje de ejecutarse inmediatamente, podéis hacer una llamada a Thread.Join que siendo una llamada síncrona dentendrá el hilo hasta que se finalize la ejecución.

61


Capítulo 12. Sobrecarga de operadores 12.1. Sobrecarga de operadores 12.1.1. ¿ Qué es la sobrecarga de operadores ? La sobrecarga de operadores es la capacidad para transformar los operadores de un lenguaje como por ejemplo el +, -, etc, cuando se dice transformar se refiere a que los operandos que entran en juego no tienen que ser los que admite el lenguaje por defecto. Mediante esta tecnica podemos sumar dos objetos creados por nosotros o un objeto y un entero, en vez de limitarnos a sumar numeros enteros o reales, por ejemplo. La sobrecarga de operadores ya era posible en c++ y en otros lenguajes, pero sorprendentemente java no lo incorpora asi que podemos decir que esta caracteristica es una ventaja de c# respesto a java, aunque mucha gente esta posibilidad no lo considera una ventaja por que complica el codigo. A la hora de hablar de operadores vamos a distinguir entre dos tipos, los unarios y los binarios. Los unarios son aquellos en que solo se requiere un operando, por ejemplo a++, en este caso el operando es ’a’ y el operador ’++’. Los operadores binarios son aquellos que necesitan dos operadores, por ejemplo a+c , ahora el operador es ’+’ y los operandos ’a’ y ’c’. Es importante esta distincion ya que la programacion se hara de forma diferente Los operadores que podemos sobrecargar son los unarios, +, -, !, ~, ++, --; y los binarios +, -, *, /, %, &, |, ^, <<, >>. Es importante decir que los operadores de comparacion, ==, !=, <, >, <=, >=, se pueden sobrecargar pero con la condicion que siempre se sobrecargue el complementario, es decir si sobrecargamos el == debemos sobrecargar el !=.

12.1.2. Sobrecargando operadores en la practica Para mostrar la sobrecarga vamos a usar el repetido ejemplo de los numeros complejos, ( aunque tambien valdria el de las coordenadas cartesianas ). Como se sabe los numeros complejos tienen dos partes, la real y la imaginaria, cuando se suma dos numeros complejos su resultado es la suma de las dos partes, para ello se va a crear una clase llamada ComplexNum que contendra ambas partes. Sin esta técnica no se podria sumar dos objetos de este tipo con este practico método, ya que esta clase no es válida como operando de los operadores de c#. Empecemos con el código de la clase de numeros imaginarios. public class ComplexNum{ private float _img; private float _real;

62


Capítulo 12. Sobrecarga de operadores public ComplexNum(float real, float img) { _img = img; _real = real; } public ComplexNum() { _img = 0; _real = 0; } public float getReal(){ return this._real; } public float getImg() { return this._img; } public String toString() { if (_img >= 0 ) return _real + "+" + _img +"i"; else return _real + "" + _img + "i"; } }

En el ejemplo hemos puesto la clase, con un par de constructores , dos getters para obtener los datos privados de la clase y un metodo que nos transfoma el numero complejo a cadena para que se pueda visualizarlo facilmente, a esta clase la iremos añadiendo métodos para que tenga capacidad de usar operadores sobrecargados. 12.1.2.1. Operadores binarios Para empezar vamos a sobrecargar el operador suma(’+’) para que al sumar dos objetos de la clase ComplexNum, es decir dos numeros complejos obtengamos otro numero complejo que sera la suma de ambas partes. Cabe destacar que los prototipos para sobrecargar operadores seran: public static Operando operator+(Operando a, Operando b)

Este es el prototipo para el operador +, el resto de operadores binarios van a seguir el mismo patron. Por tanto el código del método de sobrecarga será: public static ComplexNum operator+(ComplexNum a, ComplexNum b) { return new ComplexNum(a.getReal() + b.getReal(), a.getImg() + b.getImg()); }

63


Capítulo 12. Sobrecarga de operadores Este método sobrecarga el operador suma para que podamos sumar dos numeros complejos. Un dato a tener en cuenta es que los métodos que sobrecargan operadores deben ser static. Como se ve en el código los operandos son ’a’ y ’b’, que se reciben como parametro y el resultado de la operacion es otro numero complejo que es el que retorna el método. Por tanto se limita a crear un nuevo numero complejo con ambas partes operadas. De la misma forma podemos crear la sobrecarga del operador resta(’-’) para que lleve a cabo la misma función public static ComplexNum operator-(ComplexNum a, ComplexNum b) { return new ComplexNum(a.getReal() - b.getReal(), a.getImg() - b.getImg()); }

Como vemos el metodo es identico solo q sustituyendo los + por -. En este caso el trabajo que hacemos dentro del metodo es trivial pero podria ser tan complejo como se quiera. 12.1.2.2. Operadores Unarios En esta sección se vera como sobrecargar los operadores unarios, es decir aquellos que toman un solo operando, como por ejemplo a++. El prototipo de los métodos que van a sobrecargar operadores unarios será: public static Operando operator++(Operando a)

Como antes sustituyendo el ++ por cualquier operador unario. El ejemplo dentro de nuestra clase de numeros complejos sería: public static ComplexNum operator++(ComplexNum a) { float auximg = a.getImg(); float auxreal = a.getReal(); return new ComplexNum(++auxreal, ++auximg); }

A primera vista puede quedar la duda si estamos sobrecargando la operacion ++a o a++. Este aspecto se encarga el compilador de resolverlo, es decir, se sobrecarga la operacion ++ y el compilador se encarga de sumar y asignar o asignar y sumar. Este problema no ocurria en C++, cosa que teniamos que manejar nosotros Como hemos dicho antes la operacion que hagamos dentro del metodo que sobrecarga el operador es totalmente libre, se puede poder el ejemplo de multiplicar dos matrices lo que es mas complejo que sumar dos numeros complejos

64


Capítulo 13. Tratamiento de ficheros 13.1. Tratamiento de ficheros 13.1.1. La primera clase: System.IO.File

Esta clase estatica nos provee de las operaciones basicas a realizar con ficheros a nivel externo. Es una clase sellada, por lo que ninguna clase puede derivar de ella. Es una clase que hace los tests de permisos y seguridad en cada invocacion.

13.1.1.1. Metodo System.IO.File.AppendText() StreamReader

System.IO.File.AppendText(string camino)

Este metodo devuelve un StreamWriter (Explicado mas adelante) que nos permite añadir texto en formato UTF-8 al fichero especificado en la cadena"camino" 13.1.1.2. Metodo System.IO.File.Copy() void System.IO.File.Copy(string org, string dest); void System.IO.File.Copy(string org, string dest, bool sobreescribe);

Este metodo nos permite copiar un fichero a otro lugar, el fichero org se copia en dest, el tercer parametro es si se debe sobreescribir o no el fichero destino 13.1.1.3. Metodo System.IO.File.Create() TextWriter System.IO.File.Create(string camino); TextWriter System.IO.File.Create(string camino; int buffer);

Este metodo devuelve un TextWriter, crea o sobreescribe el fichero identificado por camino, el segundo parametro especifica el tamaño del buffer a usar en el TextWriter creado 13.1.1.4. System.IO.File.CreateText() StreamWriter System.IO.File.CreateText(string camino);

Este metodo devuelve un StreamWriter usando la codificacion UTF-8.

65


CapĂ­tulo 13. Tratamiento de ficheros 13.1.1.5. System.IO.File.Delete() void System.IO.File.Delete(string camino);

Este metodo borra el fichero especificado en camino. 13.1.1.6. System.IO.File.Exists() bool

System.IO.File.Exists(string camino);

Este metodo borra el fichero especificado en camino. 13.1.1.7. System.IO.File.GetAttributes() FileAttributes System.IO.File.GetAttributes(string ruta);

Este metodo devuelve la enumeracion FileAttributes y -1 si no existe la ruta. 13.1.1.8. System.IO.File.GetCreationTime() DateTime System.IO.File.GetCreationTime(string ruta)

Devuelve un tipo DateTime que contiene la ficha del fichero asociado a esa ruta 13.1.1.9. System.IO.File.GetLastAccessTime() DateTime System.IO.File.GetLastAccessTime(string ruta);

Devuelve un DateTime que contiene la fecha del ultimo acceso al fichero asociado a la ruta 13.1.1.10. System.IO.File.GetLastWriteTime() DateTime System.IO.File.GetLastAccessTime(string ruta);

Devuelve un DateTime que contiene la fecha del ultimo acceso al fichero asociado a la ruta 13.1.1.11. System.IO.File.Move() void System.IO.File.Move(string origen, string destino);

Mueve el fichero identificado por la cadena origen a destino.

66


CapĂ­tulo 13. Tratamiento de ficheros 13.1.1.12. System.IO.File.Open() FileStream FileStream FileAccess FileStream FileAccess

System.IO.File.Open(string ruta, FileMode modo); System.IO.File.Open(string ruta, FileMode modo, modo_a); System.IO.File.Open(string ruta, FileMode modo, modo_a, FileShare modo_s);

Abre el fichero especificado por ruta, devuelve un FileStream asociado a el en los modos especificados 13.1.1.13. System.IO.File.OpenRead() FileStream System.IO.File.OpenRead(string ruta);

Abre el fichero especificado para lectura y devuelve un FileStream asociado a el 13.1.1.14. System.IO.File.OpenText() StreamReader System.IO.File.OpenText(string ruta);

Abre el fichero especificado para lectura y devuelve un StreamReader asociado a el. 13.1.1.15. System.IO.File.OpenWrite() FileStream System.IO.File.OpenWrite(stream ruta);

Abre un fichero para escritura y devuelve un FileStream asociado a el. 13.1.1.16. System.IO.File.SetAttributes() void System.IO.File.SetAttributes(string ruta,FileAttributes atribs);

Pone los atributos especificados en atribs al fichero especificado por la ruta. 13.1.1.17. System.IO.File.SetCreationTime() void System.IO.File.SetCreationTime(string ruta, DateTime cr_tm);

Pone la fecha de creacion del archivo especificado en ruta a lo especificado en cr_tm

67


CapĂ­tulo 13. Tratamiento de ficheros 13.1.1.18. System.IO.File.SetLastAccessTime() void System.IO.File.SetLastAccessTime(string ruta, DateTime la_tm);

Pone la fecha de ultimo acceso especificada en la_tm al fichero asociado a ruta. 13.1.1.19. System.IO.File.SetLastWriteTime() void System.IO.File.SetLastWriteTime(string ruta, DateTime lw_tm);

Pone la fecha de la ultima escritura del archivo identificado en ruta, al valor especificado en lw_tm 13.1.1.20. Ejemplo de uso de la clase System.IO.File TODO

13.1.2. Obteniendo informacion sobre archivos: System.IO.FileInfo Una de las primeras cosas que interesa conseguir de un archivo es la informacion que el sistema operativo puede proveernos, tamaĂąo, localizacion, fechas de creacion, modificacion, acceso...; para ello la BCL nos provee de una clase (System.IO.FileInfo) que nos permite obtener informacion sobre un archivo.

68


CapĂ­tulo 14. Interoperabilidad con cĂłdigo nativo 14.1. Uso de Platform Invoke 14.1.1. Âż Que es Platform Invoke ? Platform Invoke o su abreviatura PInvoke es un metodo que tenemos en C# para poder llamar a funciones de otras librerias desde nuestros programas, como por ejemplo gtk+ en el caso de gtk# o a la propia api de windows desde un programa en C# para windows. El metodo que usa PInvoke es importar la funcion de la libreria donde reside declarandola dentro de nuestro programa y usando nuestros parametros de C#

14.1.2. Usando Platform Invoke Para usar PInvoke primeramente como hemos dicho debemos importar la funcion, para ello usaremos la siguente sintaxis [DllImport("libreria desde la que importamos la funcion")] -- Declaracion de la funcion --;

La forma de uso es sencilla y se ve que mediante la llamada DllImport indicamos la libreria donde se encuentra la funcion q pasamos a declarar para su siguiente uso

14.1.3. Ejemplo de uso Para ver como funciona lo mas facil es ver un ejemplo [DllImport("glade-2.0")] static extern IntPtr glade_xml_new(string fname, string root, string domain);

En el ejemplo lo que se hace es importar de la libreria glade-2.0 la funcion glade_xml_new. Como hemos dicho antes declaramos la funcion para nuestro uso, mientras que el prototipo original de la funcion dentro de libglade es GladeXML* glade_xml_new (const char *fname, const char *root, const char *domain)

69


Cap铆tulo 14. Interoperabilidad con c贸digo nativo Con PInvoke lo declaramos para su uso en C# asi const char se convierte en string y el puntero a GladeXML en un puntero general en c#. Tambien es importante notar que la funcion la definimos como static y extern, denotando que puede ser llamada sin definir una clase y que esta definida externamente al codigo del programa Finalmente vamos a ver un ejemplo de importacion y llamada [DllImport("gtk-x11-2.0")] static extern IntPtr gtk_button_new_with_mnemonic(string label); public Button(string label) { Raw = gtk_button_new_with_mnemonic(label); }

El ejemplo esta tomado de la clase Button de la libreria Gtk#. Como vemos el metodo que se define es un constructor al que le pasamos el nombre que llevara el propio boton, dentro de gtk+ usamos la funcion gtk_button_new_with_mnemonic, por lo tanto la importamos desde la libreria gtk para llamarlo posteriormente. Al igual que antes los parametros cambian y el const gchar * que recibe la funcion en gtk+ lo cambiamos por el tipo string Una vez que tenemos importada la funcion y declarada en nuestro entorno el siguiente paso natural es usarla. Para ello en el ejemplo dentro del constructor llamamos a la funcion importada con el parametro que nos pasan.

14.1.4. Conclusiones Para terminar es importante resaltar la gran utilidad que tiene este prodecimiento dentro de C# ya que podemos llamar a funciones dentro de otras librerias que estan escritas en otro lenguaje de programacion ( realmente no importa cual ). Esto es de una gran utilidad dentro de librerias que hacen de wrappers como es el caso de gtk# y gtk+

70


Capítulo 15. Introspección 15.1. Introspección: El ojo que todo lo ve Código gestionado (managed code), la nueva palabra de moda dentro del mundo de la programación, hace referencia a un nuevo tipo de código que se ejecuta bajo el escrutinio de una máquina virtual, al cual nos permite conocer muchas cosas sobre ese código, saber que va a hacer, de que cosas depende y muchas otras cosas. En realidad todos estos conceptos ya los teníamos en Java, por lo que se podría decir que el código Java también es código gestionado. Vamos a ver en esta sección del tutorial de C# como se nos abren grandes posibilidades con este tipo de código, que vamos a poder relacionarlo de forma sencilla con otras herramientas o que incluso vamos a poder modificar el código que se ejecuta en función del contexto de ejecución en el que se encuentre el programa. Lo primero que vamos a realizar es la presentación de algunos conceptos que vamos a utilizar dentro de la sección, para luego ir buceando en todos ellos.

15.1.1. Metadatos El primer concepto con el que vamos a jugar es con el de metadatos, que se refiere al conjunto de descripciones que se hacen sobre los datos, lo que muchas veces se llama "los datos sobre los datos". En nuestro caso, los datos que vamos a describir con metadatos son el código de nuestra aplicación. Vamos a ir añadiendo al código características que enriquezcan la interpretación posterior y lo que se puede hacer con ese código posteriormente. Los detalles sobre los datos, metadatos, también pueden ser detallados con otras descripciones, con lo que tenemos otro nivel de metadatos, es decir, "meta"-"metadatos". Como vemos el concepto de metadatos es algo abstracto pero vamos a comenzar a ver ejemplos reales que nos permitan tocar el suelo, y ver su gran utilidad.

15.1.2. Atributos Los atributos son el pilar de los metadatos en C#. Con ellos vamos a poder especificar características de diferentes partes del código C#. Según donde situemos el atributo, se aplicará a una zona u otra (clase, método, ensamblado ...). Tenemos atributos que ya están predefinidos dentro de C#, otros dentro de .NET, otros dentro de Mono y otros que nos podemos crear nosotros mismos.

71


Capítulo 15. Introspección Los atributos no dejan de ser objetos de C#.

15.1.3. Código gestionado: Integración

15.1.4. Reflexión

15.1.5. Ejemplo práctico: Árboles de Mono

72


Capítulo 16. Manipulación de archivos XML Cuando Micrsoft lanzó las primeras versiones de su plataforma .NET, nos sorprendió a todos por su apuesta por los estándares, lo cual constrastaba claramente con prácticas anteriores. El estándar más importante adoptado en la plataforma .NET es sin duda XML. XML es una tecnología tan integrada en .NET, que la propia plataforma utiliza XML internamente para sus archivos de configuración y para su propia documentación. XML es, por lo tanto, una tecnología con una importancia fundamental en .NET.

16.1. Algunos conceptos de XML XML es un lenguaje para estructurar datos. Sirve, por ejemplo, para almacenar en un archivo de texto una hoja de cálculo, una libreta de direcciones o un dibujo vectorial. . XML hace mucho más fácil al ordenador el proceso de generar datos, leer los datos y asegurarse de que la estructura de los datos no es ambigua. A su vez, contiene características que aseguran su validez durante mucho tiempo: es ampliable, independiente de plataforma, no pertenece a ninguna firma concreta de software y soporta internacionalización. El aspecto de un archivo XML es muy similar al de un archivo HTML, con texto encerrado entre etiquetas. Las etiquetas no son más que palabras rodeadas por < y >. El siguiente podría ser un ejemplo de archivo XML que describe a la persona que escribe éste tutorial. <?xml version="1.0"?> <persona> <nombre>Fabian</nombre> <apellido>Seoane</apellido> <organizacion>Mono Hispano</organizacion> <pagina>http://fseoane.net</pagina> </persona>

La diferencia fundamental con HTML es que XML no tiene etiquetas predefinidas, sino que éstas dependen de la implementación. Por ejemplo, en el archivo que acabamos de ver, las etiquetas disponibles podrían ser persona, nombre, appellido, ..., mientras que en un archivo XML que describa una librería, las etiquetas podrían ser ensamblado, clase, metodo, etc. Todo ésto podría parecer un lío increible si no se dispusiera de un mecanismo para traducir entre los diversos formatos XML, por ejemplo, entre el archivo persona y un archivo HTML que muestra la información sobre la persona. Por fortuna, esto existe, se llaman hojas XSL y la librería de clases dispone de métodos para transformar entre XML a partir de hojas XSL.

73


Capítulo 16. Manipulación de archivos XML Si quieres saber más sobre XML te sugiero que mires las siguientes páginas: http://w3.org/XML/, http://xml.com

16.2. Archivos XML 16.2.1. Escribir un archivo XML Comenzaremos por escribir un sencillo archivo XML. En éste caso será el ejemplo que acabamos de utilizar. El código siguiente crea un documento llamado ejemplo.xml con un documento xml como el del ejemplo anterior. using System; using System.Xml;

class EjemploXml{ public static void Main() { XmlTextWriter writer = new XmlTextWriter("ejemplo.xml", System.Text.Encoding.UTF8); //Usa indentación por legibilidad writer.Formatting = Formatting.Indented; //Escribe la declaración del XML writer.WriteStartDocument(); //Escribe el elemento raiz writer.WriteStartElement("persona"); //Escribe los elementos dentro de sus etiquetas writer.WriteElementString("nombre", "Fabian"); writer.WriteElementString("apellido", "Seoane"); writer.WriteElementString("organizacion", "Mono Hispano"); writer.WriteElementString("pagina", "http://fseoane.net"); writer.WriteEndElement(); writer.Flush(); writer.Close(); } }

74


Cap铆tulo 16. Manipulaci贸n de archivos XML

16.3. La clase XmlWriter TODO

75


Capítulo 17. Creación y uso de librerías Usando las opciones correctas en la compilación, C# genera librerías con sufijo dll que se pueden acceder directamente desde una aplicación cliente.

17.1. Compilando para una librería Esto es muy sencillo, simplemente especifícale al compilador la opción -targe:library y te generará un archivo dll. Supongamos que nuestra librería es un archivo MisMetodos.cs con el siguiente código: namespace MisMetodos{ public class Mates { public static int Factorial( int n ) { int fact = n; for( int i = n-1; i> 0; i-- ) { fact = fact*i; } return fact; } } }

que simplemente contiene una función para calcular el factorial de un entero. Lo compilamos ahora con mcs MisMetodos.cs -target:library

y obtendremos nuestra librería MisMetodos.cs

17.2. Usando nuestra libreria Ahora nos falta construir una aplicación cliente que utilize los métodos definidos en nuestra librería. El código de esta aplicación podría ser la seguiente (supongamos que está en un archivos llamado MiApliCliente.cs): using System; using MisMetodos; class MiApliCliente { public static void Main() { int n = Mates.Factorial(5); Console.WriteLine("5! = {0}", n );

76


Capítulo 17. Creación y uso de librerías } }

Ahora para compilar éste archivo debemos de referenciar la librería que acabamos de compilar. Referenciar una libreía siempre se consigue dándole al compilador la opción -r:ruta_a_la_libreria.dll. El nuestro caso sería algo así: mcs MiApliCliente.cs -r:MisMetodos.dll

y cuando lo ejecutemos (mono MiAplicliente o ./MiApliCliente ) debería de producir la siguiente salida 5! = 120

77


Capítulo 18. Autores Para la versión 0.6 (octubre-2004): •

Fabian Seoane (http://fseoane.net), (fseoane), <fabian@fseoane.net>

Para la versión 0.5 (agosto-2004): •

Fabian Seoane (http://fseoane.net), (fseoane), <fabian@fseoane.net>

En versiones anteriores han colaborado las siguientes personas •

Alejando Sánchez, <raciel@x0und.net>

Alvaro del Castillo, <acs@barrapunto.com>

Eduardo García Cebollero, <kiwnix@yahoo.es>

César García Tapia, <tapia@eitig.com>

Sergio Gómez Bachiller, <Sergio.Gomez@consejo-eps.uco.es>

Roberto Pérez Cubero, <hylian@jazzfree.com>

Jaime Anguiano Olarra, <jaime@gnome.org>

78


Bibliografía Libros Herbert Schild, Mc Graw Hill, Manual de referencia C#. Ecma-International, Estándar Ecma-334.

Artículos Dare Obsanjo, MSDN, Understanding XML (http://msdn.microsoft.com/XML/Understanding/default.aspx?pull=/library/enus/dnxml/html/understxml.asp).

79


Turn static files into dynamic content formats.

Create a flipbook
Issuu converts static files into: digital portfolios, online yearbooks, online catalogs, digital photo albums and more. Sign up and create your flipbook.