Вы находитесь на странице: 1из 68

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 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. Los programadores entonces recurren a clases abstractas 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), le sigue faltando 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 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 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 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 al código.

C# consigue aunar 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.

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 multihilo 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í) permite la implementación del lenguaje C# y de la
plataforma .NET por terceros, incluso en entornos distintos de Windows.

1.1.6. C# frente a Java


C# y Java son lenguajes similares, de sintaxis basada en C/C++, orientados a objetos, y
ambos incluyen las características más importantes de los lenguajes modernos, como
son la gestión automática de memoria y la compilación a código intermedio. Pero por
supuesto, también hay diferencias. Una de las diferencias más importantes es que C# es
mucho más cercano a C++ en cuanto a diseño se refiere. C# toma casi todos sus
operadores, palabras reservadas y expresiones directamente de C++. También se han
mantenido algunas características que en Java se han desestimado. Por ejemplo 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.

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
gestión automática de memoria, en C# es posible usar lo que se denomina "código no
seguro". Cuando se usa código no seguro en C# es posible operar con punteros de forma
muy similar a como se haría en C/C++, pero el código que utiliza punteros se queda
marcado como no seguro y no se ejecuta en entornos en los que no tiene permisos.

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
de forma distinta. Por ejemplo, en C# se comprueban los límites de los arrays antes de
usarlos, evitando así que se pueda escribir pasado el final del vector.

Al igual que Java, C# renuncia a la idea de herencia múltiple de clases presente en C++.
Sin embargo, referido a clases, C# 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 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

[1] 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.

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

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
memoria en el que se almacena 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 sinónimos para tipos predefinidos en la librería
base de la plataforma Mono/.NET . Así, el tipo entero int, no es más que un sinónimo de
System.Int32 .

Los tipos de C# están divididos en dos grandes categorías: tipos por valor y tipos por
referencia. Existe además una tercera categoría, los punteros (disponibles solo cuando
se usa código no seguro), 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 su valor. De esta forma, los tipos por valor actúan de forma muy
parecida a los tipos de datos de otros lenguajes de programación como C++. Los tipos
por valor se conocen también como tipos sencillos

Tabla 2-1. Tipos por valor

Nombre para la
Tipo Con Bytes
plataforma Rango
C# signo? utilizados
Mono/.NET
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
-2.147.483.648 hasta
int System.Int32 Si 4
2.147.483.647
uint System.Uint32 No 4 0 hasta 4.394.967.395
-9.223.372.036.854.775.808 hasta
long System.Int64 Si 8
9.223.372.036.854.775.807
ulong System.Uint64 No 8 0 hasta 18446744073709551615
Approximadamente ±1.5E-45
float System.Single Si 4 hasta ±3.4E38 con 7 cifras
significativas
Approximadamente ±5.0E-324
double System.Double Si 8 hasta ±1.7E308 con 7 cifras
significativas
Approximadamente ±1.0E-28
decimal System.Decimal Si 12 hasta ±7.9E28 con 28 ó 29 cifras
significativas
Cualquier carácter Unicode (16
char System.Char 2
bits)

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

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.

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

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

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

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

Console.WriteLine( "Elige hacer algo con los números 2 y


3");
Console.WriteLine( " + para sumarlos" );
Console.WriteLine( " - para restarlos" );
Console.WriteLine( " * para multiplicarlos" );
Console.WriteLine( " / para dividirlos (division
entera)" );

s = Console.ReadLine();
switch(s){
case "+":
Console.WriteLine("El resultado es {0}", 2+3);
break;
case "-":
Console.WriteLine("El resultado es {0}", 2-3);
break;
case "*":
Console.WriteLine("El resultado es {0}", 2*3);
break;
case "/":
Console.WriteLine("El resultado es {0}", 2/3);
break;
default:
Console.WriteLine("No te entiendo");
break;
}
}
}

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.

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

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

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

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

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

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)

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;

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

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

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

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

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)
{
this.X = X;
this.Y = Y;
}
}

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;
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: a = 1
p = 2
post: a = 1

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

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++) {
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("nº 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:
nº de arguments: 0
nº de argumentos: 1
args[0] = 1
nº de argumentos: 2
args[0] = 1
args[1] = 2
nº de argumentos: 3
args[0] = 1
args[1] = 2
args[2] = 3
nº 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 WriteLine(string s) {...}
public static void WriteLine(string s, object a) {...}
public static void WriteLine(string s, object a, object b) {...}
...
public static void WriteLine(string s, params object[] args)
{...}
}
}

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 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[indice]);
return tabla[indice];
}
set
{
Console.WriteLine ("Escrito el valor {0} en la posición {1}
de la tabla", value, indice);
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:


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.

Capítulo 8. Clases (II)


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
}

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

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

8.6. Nuevas características en C# 2.0


para las clases
8.6.1. Clases "parciales"
En C# 2.0, es posible que las clases estén repartidas en varios archivos. Solamente hay
que especificar la palabra partial en la definición de la clase.

8.6.2. Clases estáticas


TODO

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.

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.

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

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.

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.

En nuestro caso estamos de suerte porque el lenguaje en sí ¡soporta eventos!. 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 publicación/subscripción para manejar


eventos, así que lo que tendremos será una clase que publica un evento, al que se
suscriben aquellas clases que quieran ser informadas cuando se lanze el evento.

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,
OnButtonClickedEventArgs e);

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

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

public void Unsuscribe (Dude dude) {


dude.MessageFromDude -= new Dude.DudeSpeechEventHandler
(message_received);
}

private void message_received (object s, DudeSpeechEventArgs


args) {
Console.WriteLine ("&lt;dude&gt;: {0}", args.Message);
if (args.Index < 3) {
Thread.Sleep (4000);
Console.WriteLine ("&lt;you&gt;: {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) {
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 () {
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#.

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

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

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;

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

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

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.

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.

13.1.1.12. System.IO.File.Open()
FileStream System.IO.File.Open(string ruta, FileMode modo);
FileStream System.IO.File.Open(string ruta, FileMode modo,
FileAccess modo_a);
FileStream System.IO.File.Open(string ruta, FileMode modo,
FileAccess 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

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.

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)

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+

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.

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

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.

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

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

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

Capítulo 18. Autores


Para la versión 0.6 (octubre-2004):

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

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

Fabian Seoane, (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>

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.
This document was created with Win2PDF available at http://www.daneprairie.com.
The unregistered version of Win2PDF is for evaluation or non-commercial use only.

Вам также может понравиться