Tutorial de C# (68 Páginas)
Tutorial de C# (68 Páginas)
Tutorial de C# (68 Páginas)
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.
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.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.
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 '::'.
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
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.
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.
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.
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.
using System;
class Enteros{
public static void Main()
{
int Minuto = 60; //segundos por minuto
int Hora = Minuto*60;
int Dia = Hora*24;
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 );
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);
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.
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.
class Booleano{
public static void Main()
{
bool b;
b = true;
if(b)
{
Console.WriteLine("esto saldrá");
}
b = false;
if(b)
{
Console.WriteLine("esto no saldrá");
}
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
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};
int[,] arr
en contraposición a C/C++, en el que se declararía como
int[][] arr
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}};
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 .
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;
...
}
class InstruccionIf{
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);
}
}
}
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;
}
class IfElseIf{
public static void Main()
{
string opcion;
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"
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.
using System;
class InstruccionSwitch{
public static void Main()
{
string s;
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.
class BucleFor{
public static void Main()
{
int i; //el contador
using System;
class BucleFor2{
public static void Main()
{
int i;
int 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á).
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.
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.
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}};
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
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
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.
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;
}
}
}
using System;
class UsoContinue{
public static void Main()
{
int i;
using System;
class UsoBreak{
public static void Main()
{
int 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.
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.
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:
o bien
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:
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 >>=
variable1 += variable2;
variable1 = variable1 + 1;
variable1 += 1;
variable1++;
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++;
b = (a>0) ? a : 0;
Ojo: No confundir con un "if". Este operador devuelve un valor, mientras que el
if es sólamente una instrucción.
A.metodo1 ();
(expresion) is nombreTipo
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;
public int a = 1;
private double b = 3;
public char c = 'a';
}
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;
public int a = 1;
public double b = 3;
public char c = 'a';
public void Descripcion()
{
Console.WriteLine("Hola, soy una clase");
}
}
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.
Tipo devuelto
Nombre del método
Parámetros (puede ser vacío)
Cuerpo del método
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;".
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) );
}
}
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) );
}
}
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) );
}
}
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;
A.X = 1;
A.Y = -1;
}
}
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;
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) .
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.
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;
}
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) );
}
}
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;
}
}
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
}
}
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.
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.
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;
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.
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
int a = 1, b = 2;
Console.WriteLine("a = {0}, b = {1}", a, b);
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;
}
}
}
Para probar esta clase, creamos otra clase con un punto de entrada (public static void
Main ()), que será donde hagamos las pruebas.
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
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
a = obj[3];
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.
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();
}
}
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()
}
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
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";
}
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".
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.
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,
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:
using System;
class Ejemplo {
delegate void MiDelegado (int a);
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:
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.
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.
using System;
class Ejemplo {
public static MiDelegado delegado;
class ClaseA {
public static void MetodoEstatico (string cad) {
Console.WriteLine ("ClaseA.MetodoEstatico ha sido llamado: {0}",
cad);
}
class ClaseB {
void MetodoPrivado (string cad) {
Console.WriteLine ("ClaseB.MetodoPrivado ha sido llamado: {0}",
cad);
}
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:
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.
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.
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:
ClaseQuePublicaElEvento.OnButtonClicked += new
OnButtonClickedEventHandler (callback);
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.
ClaseQuePublicaElEvento.OnButtonClicked -= new
OnButtonClickedEventHandler (callback2);
using System;
using System.Threading;
string msg;
int index;
}
using System;
using System.Threading;
string time;
}
using System;
using System.Collections;
Ya solo os queda tener los experimentos que hacer. Os propongo estos: Experiment1.cs
using System;
y este otro:
using System;
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
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:
Thread t = Thread.CurrentThread;
#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 */
void
funcion ()
{
/* Ponle lo que quieras hacer aqui */
}
Thread.Sleep (3000);
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
!=.
public ComplexNum() {
_img = 0;
_real = 0;
}
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:
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
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.
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á:
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
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.4. System.IO.File.CreateText()
StreamWriter System.IO.File.CreateText(string camino);
13.1.1.5. System.IO.File.Delete()
void System.IO.File.Delete(string camino);
13.1.1.6. System.IO.File.Exists()
bool System.IO.File.Exists(string camino);
13.1.1.7. System.IO.File.GetAttributes()
FileAttributes System.IO.File.GetAttributes(string 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);
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);
13.1.1.13. System.IO.File.OpenRead()
FileStream System.IO.File.OpenRead(string ruta);
13.1.1.14. System.IO.File.OpenText()
StreamReader System.IO.File.OpenText(string ruta);
13.1.1.15. System.IO.File.OpenWrite()
FileStream System.IO.File.OpenWrite(stream ruta);
13.1.1.17. System.IO.File.SetCreationTime()
void System.IO.File.SetCreationTime(string ruta, DateTime
cr_tm);
13.1.1.18. System.IO.File.SetLastAccessTime()
void System.IO.File.SetLastAccessTime(string ruta, DateTime
la_tm);
13.1.1.19. System.IO.File.SetLastWriteTime()
void System.IO.File.SetLastWriteTime(string ruta, DateTime
lw_tm);
[DllImport("glade-2.0")]
static extern IntPtr glade_xml_new(string fname, string root,
string 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
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+
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.
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.
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.
<?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.
Si quieres saber más sobre XML te sugiero que mires las siguientes páginas:
http://w3.org/XML/, http://xml.com
using System;
using System.Xml;
class EjemploXml{
writer.WriteEndElement();
writer.Flush();
writer.Close();
}
}
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;
}
}
}
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
Bibliografía
Libros
Herbert Schild, Mc Graw Hill, Manual de referencia C#.
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.