Skip to content

3. Conceptos básicos del lenguaje C#

3.1. Introduction

En primer lugar, trataremos C# como un lenguaje de programación clásico. Abordaremos las clases más adelante. En un programa hay dos elementos:

  • datos
  • las instrucciones que los manipulan

Por lo general, se intenta separar los datos de las instrucciones:

3.2. Los datos en C#

C# utiliza los siguientes tipos de datos:

  1. los números enteros
  2. los números reales
  3. números decimales
  4. caracteres y cadenas de caracteres
  5. los booleanos
  6. los objetos

3.2.1. Los tipos de datos predefinidos

Tipo C#
Tipo .NET
Dato representado
Sufijo
de los valores literales
Codificación
Rango de valores
char
Carácter(es)
carácter
 
2 bytes
carácter Unicode (UTF-16)
string
Cadena (C)
cadena de caracteres
  
referencia a una secuencia de caracteres Unicode
int
Int32 (S)
número entero
 
4 bytes
[-231, 231-1] [–2147483648, 2147483647]
uint
UInt32 (S)
..
U
4 bytes
[0, 232-1] [0, 4294967295]
long
Int64 (S)
..
L
8 bytes
[-263, 263 -1] [–9223372036854775808, 9223372036854775807]
ulong
UInt64 (S)
..
UL
8 bytes
[0, 264 -1] [0, 18446744073709551615]
sbyte
 
..
 
1 byte
[-27 , 27 -1] [-128,+127]
byte
Byte(s)
..
 
1 byte
[0 , 28 -1] [0,255]
short
Int16 (S)
..
 
2 bytes
[-215, 215-1] [-32768, 32767]
ushort
UInt16 (S)
..
 
2 bytes
[0, 216-1] [0,65535]
float
Único (S)
número real
F
4 bytes
[1.5 10-45, 3.4 10+38] en valor absoluto
double
Doble (S)
..
D
8 bytes
[-1.7 10+308, 1.7 10+308] en valor absoluto
decimal
Decimal (S)
número decimal
M
16 bytes
[1.0 10-28,7.9 10+28] en valor absoluto con 28 cifras significativas
bool
Booleano (S)
..
 
1 byte
verdadero, falso
object
Objeto (C)
referencia de objeto
  
referencia de objeto

En la tabla anterior se han comparado los tipos de C# con su tipo equivalente .NET, con el comentario (S) si dicho tipo es una estructura y (C) si es una clase. Se observa que hay dos tipos posibles para un entero de 32 bits: int y Int32. El tipo int es un tipo de C#. Int32 es una estructura que pertenece al espacio de nombres System. Por lo tanto, su nombre completo es System.Int32. El tipo int es un alias de C# que hace referencia a la estructura .NET System.Int32. Del mismo modo, el tipo de C# string es un alias para el tipo .NET System.String. System.String es una clase y no una estructura. Ambos conceptos son similares, pero presentan la siguiente diferencia fundamental:

  • una variable de tipo Structure se manipula a través de su valor
  • una variable de tipo Classe se maneja a través de su dirección (referencia en lenguaje orientado a objetos).

Tanto un structure como un classe son tipos complejos que tienen atributos y métodos. Así, se puede escribir:

string nomDuType=3.GetType().FullName;

En el ejemplo anterior, el literal 3 es, por defecto, de tipo C# int, por lo que es de tipo .NET System.Int32. Esta estructura tiene un método GetType() que devuelve un objeto que encapsula las características del tipo de datos System.Int32. Entre ellas, la propiedad FullName devuelve el nombre completo del tipo. Así pues, vemos que el literal 3 es un objeto más complejo de lo que parece a primera vista.

A continuación se muestra un programa que ilustra estos diferentes puntos:


using System;

namespace Chap1 {
    class P00 {
        static void Main(string[] args) {
            // ejemplo 1
            int ent = 2;
            float fl = 10.5F;
            double d = -4.6;
            string s = "essai";
            uint ui = 5;
            long l = 1000;
            ulong ul = 1001;
            byte octet = 5;
            short sh = -4;
            ushort ush = 10;
            decimal dec = 10.67M;
            bool b = true;
            Console.WriteLine("Type de ent[{1}] : [{0},{2}]", ent.GetType().FullName, ent,sizeof(int));
            Console.WriteLine("Type de fl[{1}]: [{0},{2}]", fl.GetType().FullName, fl, sizeof(float));
            Console.WriteLine("Type de d[{1}] : [{0},{2}]", d.GetType().FullName, d, sizeof(double));
            Console.WriteLine("Type de s[{1}] : [{0}]", s.GetType().FullName, s);
            Console.WriteLine("Type de ui[{1}] : [{0},{2}]", ui.GetType().FullName, ui, sizeof(uint));
            Console.WriteLine("Type de l[{1}] : [{0},{2}]", l.GetType().FullName, l, sizeof(long));
            Console.WriteLine("Type de ul[{1}] : [{0},{2}]", ul.GetType().FullName, ul, sizeof(ulong));
            Console.WriteLine("Type de b[{1}] : [{0},{2}]", octet.GetType().FullName, octet, sizeof(byte));
            Console.WriteLine("Type de sh[{1}] : [{0},{2}]", sh.GetType().FullName, sh, sizeof(short));
            Console.WriteLine("Type de ush[{1}] : [{0},{2}]", ush.GetType().FullName, ush, sizeof(ushort));
            Console.WriteLine("Type de dec[{1}] : [{0},{2}]", dec.GetType().FullName, dec, sizeof(decimal));
            Console.WriteLine("Type de b[{1}] : [{0},{2}]", b.GetType().FullName, b, sizeof(bool));
        }
    }
}
  • línea 7: declaración de un entero ent
  • línea 19: el tipo de una variable v se puede obtener mediante v.GetType().FullName. El tamaño de una estructura S se puede obtener mediante sizeof(S). La instrucción Console.WriteLine("... {0} ... {1} ...", param0, param1, ...) muestra en pantalla el texto que constituye su primer parámetro, sustituyendo cada notación {i} por el valor de la expresión parami.
  • línea 22: dado que el tipo string designa una clase y no una estructura, no se puede utilizar el operador sizeof.

Este es el resultado de la ejecución:

Type de ent[2] : [System.Int32,4]
Type de fl[10,5]: [System.Single,4]
Type de d[-4,6] : [System.Double,8]
Type de s[essai] : [System.String]
Type de ui[5] : [System.UInt32,4]
Type de l[1000] : [System.Int64,8]
Type de ul[1001] : [System.UInt64,8]
Type de b[5] : [System.Byte,1]
Type de sh[-4] : [System.Int16,2]
Type de ush[10] : [System.UInt16,2]
Type de dec[10,67] : [System.Decimal,16]
Type de b[True] : [System.Boolean,1]

La visualización muestra los tipos .NET y no los alias de C#.

3.2.2. Notación de los datos literales

entier int (32 bits)
145, -7, 0xFF (hexadecimal)
entier long (64 bits) - suffixe L
100000L
réel double
134,789, -45E-18 (-45 × 10⁻¹⁸)
réel float (suffixe F)
134.789F, -45E-18F (-45 10-18)
réel decimal (suffixe M)
100000M
caractère char
«A», «b»
chaîne de caractères string
«hoy» «c:\\cap1\\párrafo3» @«c:\cap1\párrafo3»
booléen bool
verdadero, falso
date
new DateTime(1954,10,13) (año, mes, día) para el 13/10/1954

Cabe destacar las dos cadenas literales: «c:\\chap1\\paragraph3» y @"c:\chap1\paragraph3". En las cadenas literales, el carácter \ se interpreta. Así, «\n» representa el carácter de fin de línea y no la sucesión de los dos caracteres \ y n. Si se quisiera esa sucesión, habría que escribir «\\n», donde la secuencia \\ se interpreta como un único carácter \. También se podría escribir @"\n" para obtener el mismo resultado. La sintaxis @"texto" exige que el texto se tome exactamente tal y como está escrito. A veces se denomina a esto una cadena literal.

3.2.3. Declaración de datos

3.2.3.1. Función de las declaraciones

Un programa maneja datos caracterizados por un nombre y un tipo. Estos datos se almacenan en memoria. En el momento de la compilación del programa, el compilador asigna a cada dato una ubicación en memoria caracterizada por una dirección y un tamaño. Lo hace basándose en las declaraciones realizadas por el programador.

Además, estas permiten al compilador detectar errores de programación. Así, la operación

x = x * 2;

se considerará errónea si x es, por ejemplo, una cadena de caracteres.

3.2.3.2. Declaración de constantes

La sintaxis para declarar una constante es la siguiente:

    const type nom=valeur;          //define una constante nombre=valor

Por ejemplo:

const float myPI=3.141592F;    

¿Por qué declarar constantes?

  1. La lectura del programa resultará más sencilla si se le da a la constante un nombre significativo:
    const  float taux_tva=0.186F;
  1. Modificar el programa resultará más sencillo si la «constante» llega a cambiar. Así, en el caso anterior, si el tipo del IVA pasa al 33 %, la única modificación que habrá que realizar será cambiar la instrucción que define su valor:
    const float taux_tva=0.33F;

Si se hubiera utilizado 0,186 explícitamente en el programa, habría que modificar numerosas instrucciones.

3.2.3.3. Declaración de variables

Una variable se identifica mediante un nombre y se asocia a un tipo de datos. C# distingue entre mayúsculas y minúsculas. Por lo tanto, las variables FIN y fin son diferentes.

Las variables se pueden inicializar al declararlas. La sintaxis para declarar una o varias variables es:

 Identificateur_de_type variable1[=valeur1],variable2=[valeur2],...;

donde Identificateur_de_type es un tipo predefinido o bien un tipo definido por el programador. De forma opcional, una variable puede inicializarse al mismo tiempo que se declara.

También es posible no especificar el tipo exacto de una variable utilizando la palabra clave «var» en lugar de «Identificateur_de_type»:

 var variable1=valeur1,variable2=valeur2,...;

La palabra clave «var» no significa que las variables no tengan un tipo concreto. La variable variablei tiene el tipo de los datos valeuri que se le asignan. En este caso, la inicialización es obligatoria para que el compilador pueda deducir el tipo de la variable.

He aquí un ejemplo:


using System;

namespace Chap1 {
    class P00 {
        static void Main(string[] args) {
            int i=2;
            Console.WriteLine("Type de int i=2 : {0},{1}",i.GetType().Name,i.GetType().FullName);
            var j = 3;
            Console.WriteLine("Type de var j=3 : {0},{1}", j.GetType().Name, j.GetType().FullName);
            var aujourdhui = DateTime.Now;
            Console.WriteLine("Type de var aujourdhui : {0},{1}", aujourdhui.GetType().Name, aujourdhui.GetType().FullName);
        }
    }
}
  • línea 6: un dato tipado explícitamente
  • línea 7: (dato).GetType().Name es el nombre corto de (dato), (dato).GetType().FullName es el nombre completo de (dato)
  • línea 8: un dato tipado implícitamente. Como 3 es de tipo int, j será de tipo int.
  • línea 10: un dato tipado implícitamente. Como DateTime.Now es de tipo DateTime, aujourdhui será de tipo DateTime.

Al ejecutarlo, se obtiene el siguiente resultado:

1
2
3
Type de int i=2 : Int32,System.Int32
Type de var j=3 : Int32,System.Int32
Type de var aujourdhui : DateTime,System.DateTime

Una variable cuyo tipo se define implícitamente mediante la palabra clave var no puede cambiar de tipo posteriormente. Por lo tanto, no se podría escribir, después de la línea 10 del código, la línea:


            var aujourdhui = "aujourd'hui";

Más adelante veremos que es posible declarar un tipo «sobre la marcha» en una expresión. En ese caso, se trata de un tipo anónimo, es decir, un tipo al que el usuario no ha dado nombre. Es el compilador el que asignará un nombre a este nuevo tipo. Si hay que asignar un dato de tipo anónimo a una variable, la única forma de declarar esta última es utilizando la palabra clave var.

3.2.4. Las conversiones entre números y cadenas de caracteres

nombre -> chaîne
nombre.ToString()
chaine -> int
int.Parse(cadena) o System.Int32.Parse
chaîne -> long
long.Parse(cadena) o System.Int64.Parse
chaîne -> double
double.Parse (cadena) o System.Double.Parse (cadena)
chaîne -> float
float.Parse (cadena) o System.Float.Parse (cadena)

La conversión de una cadena a un número puede fallar si la cadena no representa un número válido. En ese caso, se genera un error grave denominado «excepción». Este error se puede gestionar mediante la siguiente cláusula try/catch:


try{
    appel de la fonction susceptible de générer l'exception
} catch (Exception e){
    traiter l'exception e
}
instruction suivante

Si la función no genera ninguna excepción, se pasa a la siguiente instrucción; en caso contrario, se pasa al cuerpo de la cláusula catch y, a continuación, a la siguiente instrucción. Más adelante volveremos sobre la gestión de excepciones. A continuación se muestra un programa que presenta algunas técnicas de conversión entre números y cadenas de caracteres. En este ejemplo, la función affiche muestra en pantalla el valor de su parámetro. Así, affiche(S) muestra en pantalla el valor de S, donde S es de tipo string.


using System;

namespace Chap1 {
    class P01 {
        static void Main(string[] args) {

            // datos
            const int i = 10;
            const long l = 100000;
            const float f = 45.78F;
            double d = -14.98;

            // número --> cadena
            affiche(i.ToString());
            affiche(l.ToString());
            affiche(f.ToString());
            affiche(d.ToString());

            //booleano --> cadena
            const bool b = false;
            affiche(b.ToString());

            // cadena --> entero
            int i1;
            i1 = int.Parse("10");
            affiche(i1.ToString());
            try {
                i1 = int.Parse("10.67");
                affiche(i1.ToString());
            } catch (Exception e) {
                affiche("Erreur : " + e.Message);
            }

            // cadena --> entero largo
            long l1;
            l1 = long.Parse("100");
            affiche(l1.ToString());
            try {
                l1 = long.Parse("10.675");
                affiche(l1.ToString());
            } catch (Exception e) {
                affiche("Erreur : " + e.Message);
            }

            // cadena --> doble
            double d1;
            d1 = double.Parse("100,87");
            affiche(d1.ToString());
            try {
                d1 = double.Parse("abcd");
                affiche(d1.ToString());
            } catch (Exception e) {
                affiche("Erreur : " + e.Message);
            }

            // cadena --> float
            float f1;
            f1 = float.Parse("100,87");
            affiche(f1.ToString());
            try {
                d1 = float.Parse("abcd");
                affiche(f1.ToString());
            } catch (Exception e) {
                affiche("Erreur : " + e.Message);
            }

        }// fin main

        public static void affiche(string S) {
            Console.Out.WriteLine("S={0}",S);
        }
    }// fin de clase
}

En las líneas 30-32 se gestiona la posible excepción que pueda producirse. e.Message es el mensaje de error asociado a la excepción e.

Los resultados obtenidos son los siguientes:

S=10
S=100000
S=45,78
S=-14,98
S=False
S=10
S=Erreur : Input string was not in a correct format.
S=100
S=Erreur : Input string was not in a correct format.
S=100,87
S=Erreur : Input string was not in a correct format.
S=100,87
S=Erreur : Input string was not in a correct format.

Cabe señalar que los números reales en forma de cadena de caracteres deben utilizar la coma y no el punto decimal. Por lo tanto, se escribirá

double d1=10.7; 

pero

double d2=int.Parse("10,7");

3.2.5. Las matrices de datos

Una matriz en C# es un objeto que permite agrupar datos del mismo tipo bajo un mismo identificador. Su declaración es la siguiente:

Type[] tableau[]=new Type[n]

n es el número de datos que puede contener la matriz. La sintaxis Matriz[i] designa el dato n.º i, donde i pertenece al intervalo [0,n-1]. Cualquier referencia al dato «Tabla[i]» en la que «i» no pertenezca al intervalo «[0,n-1]» provocará una excepción. Una tabla puede inicializarse al mismo tiempo que se declara:

    int[] entiers=new int[] {0,10,20,30};

o, más sencillamente:

    int[] entiers={0,10,20,30};

Los arrays tienen una propiedad «Length» que indica el número de elementos del array.

Una matriz bidimensional se puede declarar de la siguiente manera:

Type[,] matriz = new Type[n,m];

donde n es el número de filas y m el número de columnas. La sintaxis Tableau[i,j] hace referencia al elemento j de la fila i de la matriz. La matriz bidimensional también puede inicializarse al mismo tiempo que se declara:

    double[,] réels=new double[,] { {0.5, 1.7}, {8.4, -6}};

o, más sencillamente:

    double[,] réels={ {0.5, 1.7}, {8.4, -6}};

El número de elementos en cada una de las dimensiones se puede obtener mediante el método GetLength(i), donde i=0 representa la dimensión correspondiente al primer índice, i=1 la dimensión correspondiente al segundo índice, …

El número total de dimensiones se obtiene con la propiedad Rank, y el número total de elementos con la propiedad Length.

Una matriz de matrices se declara de la siguiente manera:

Type[][] matriz = new Type[n][];

La declaración anterior crea una matriz de n filas. Cada elemento matriz[i] es una referencia de matriz unidimensional. Estas referencias matriz[i] no se inicializan en la declaración anterior. Su valor es la referencia nula.

El siguiente ejemplo ilustra la creación de una matriz de matrices:


            // una matriz de matrices
            string[][] noms = new string[3][];
            for (int i = 0; i < noms.Length; i++) {
                noms[i] = new string[i + 1];
            }//«for»
            // inicialización
            for (int i = 0; i < noms.Length; i++) {
                for (int j = 0; j < noms[i].Length; j++) {
                    noms[i][j] = "nom" + i + j;
                }//«for j»
            }//«for» i
  • línea 2: un array noms de 3 elementos de tipo string[][]. Cada elemento es un puntero a un array (una referencia de objeto) cuyos elementos son de tipo string[].
  • líneas 3-5: se inicializan los tres elementos del array noms. Cada uno de ellos «apunta» ahora a un array de elementos de tipo string[]. noms[i][j] es el elemento j de la matriz de tipo string [] a la que hace referencia noms[i].
  • Línea 9: inicialización del elemento «noms[i][j]» dentro de un bucle doble. En este caso, «noms[i]» es una matriz de i+1 elementos. Como «noms[i]» es una matriz, «noms[i].Length» es su número de elementos.

A continuación se muestra un ejemplo que reúne los tres tipos de matrices que acabamos de presentar:


using System;

namespace Chap1 {
    // matrices

    using System;

    // clase de prueba
    public class P02 {
        public static void Main() {
            // una matriz unidimensional inicializada
            int[] entiers = new int[] { 0, 10, 20, 30 };
            for (int i = 0; i < entiers.Length; i++) {
                Console.Out.WriteLine("entiers[{0}]={1}", i, entiers[i]);
            }//para

            // una matriz bidimensional inicializada
            double[,] réels = new double[,] { { 0.5, 1.7 }, { 8.4, -6 } };
            for (int i = 0; i < réels.GetLength(0); i++) {
                for (int j = 0; j < réels.GetLength(1); j++) {
                    Console.Out.WriteLine("réels[{0},{1}]={2}", i, j, réels[i, j]);
                }//«for j»
            }//for i

            // una matriz de matrices
            string[][] noms = new string[3][];
            for (int i = 0; i < noms.Length; i++) {
                noms[i] = new string[i + 1];
            }//para
            // inicialización
            for (int i = 0; i < noms.Length; i++) {
                for (int j = 0; j < noms[i].Length; j++) {
                    noms[i][j] = "nom" + i + j;
                }//para j
            }//para i
            // visualización
            for (int i = 0; i < noms.Length; i++) {
                for (int j = 0; j < noms[i].Length; j++) {
                    Console.Out.WriteLine("noms[{0}][{1}]={2}", i, j, noms[i][j]);
                }//para j
            }//para i
        }//Principal
    }//clase
}//espacio de nombres

Al ejecutarlo, obtenemos los siguientes resultados:

entiers[0]=0
entiers[1]=10
entiers[2]=20
entiers[3]=30
réels[0,0]=0,5
réels[0,1]=1,7
réels[1,0]=8,4
réels[1,1]=-6
noms[0][0]=nom00
noms[1][0]=nom10
noms[1][1]=nom11
noms[2][0]=nom20
noms[2][1]=nom21
noms[2][2]=nom22

3.3. Las instrucciones básicas de C#

Se distinguen

1 las instrucciones básicas ejecutadas por el ordenador.

2 las instrucciones que controlan el desarrollo del programa.

Las instrucciones elementales se aprecian claramente al analizar la estructura de un microordenador y sus periféricos.

  1. Lectura de información procedente del teclado

  2. procesamiento de información

  3. Escritura de información en la pantalla

3.3.1. Escritura en pantalla

Existen diferentes instrucciones para escribir en pantalla:

Console.Out.WriteLine(expression)
Console.WriteLine(expression)
Console.Error.WriteLine (expression)

donde expression es cualquier tipo de dato que pueda convertirse en una cadena de caracteres para mostrarse en pantalla. Todos los objetos de C# o .NET disponen de un método ToString() que se utiliza para realizar esta conversión.

La clase System.Console permite acceder a las operaciones de escritura en pantalla (Write, WriteLine). La clase Console tiene dos propiedades, Out y Error, que son flujos de escritura de tipo TextWriter:

  • Console.WriteLine() es equivalente a Console.Out.WriteLine() y escribe en el flujo Out asociado habitualmente a la pantalla.
  • Console.Error.WriteLine() escribe en el flujo Error, que normalmente también está asociado a la pantalla.

Los flujos Out y Error pueden redirigirse a archivos de texto durante la ejecución del programa, como veremos a continuación.

3.3.2. Lectura de datos introducidos mediante el teclado

El flujo de datos procedente del teclado viene designado por el objeto Console.In, de tipo TextReader. Este tipo de objetos permite leer una línea de texto mediante el método ReadLine:

    string ligne=Console.In.ReadLine();

La clase Console ofrece un método ReadLine asociado por defecto al flujo In. Por lo tanto, se puede escribir:

    string ligne=Console.ReadLine();

La línea introducida mediante el teclado se almacena en la variable ligne y, a continuación, el programa puede utilizarla. El flujo «In» se puede redirigir a un archivo, al igual que los flujos «Out» y «Error».

3.3.3. Ejemplo de entradas y salidas

A continuación se muestra un breve programa que ilustra las operaciones de entrada y salida del teclado y la pantalla:


using System;

namespace Chap1 {
    // clase de prueba
    public class P03 {
        public static void Main() {

            // escritura en el flujo Out
            object obj = new object();
            Console.Out.WriteLine(obj);

            // escritura en el flujo Error
            int i = 10;
            Console.Error.WriteLine("i=" + i);

            // lectura de una línea introducida mediante el teclado
            Console.Write("Tapez une ligne : ");
            string ligne = Console.ReadLine();
            Console.WriteLine("ligne={0}", ligne);
        }//fin de main
    }//fin de clase
}
  • línea 9: obj es una referencia de objeto
  • línea 10: obj se escribe en pantalla. Por defecto, se llama al método obj.ToString().
  • línea 14: también se puede escribir:

            Console.Error.WriteLine("i={0}",i);

El primer parámetro «i={0}» es el formato de visualización; los demás parámetros, las expresiones que se van a mostrar. Los elementos {n} son parámetros «posicionales». Durante la ejecución, el parámetro {n} se sustituye por el valor de la expresión n.º n.

El resultado de la ejecución es el siguiente:

1
2
3
4
System.Object
i=10
Tapez une ligne : je suis là
ligne=je suis là
  • línea 1: la salida generada por la línea 10 del código. El método obj.ToString() ha mostrado el nombre del tipo de la variable obj: System.Object. El tipo object es un alias en C# del tipo .NET System.Object.

3.3.4. Redirección de E/S

En DOS y UNIX existen tres dispositivos estándar denominados:

  1. dispositivo de entrada estándar: por defecto, se refiere al teclado y lleva el n.º 0
  2. dispositivo de salida estándar: por defecto, se refiere a la pantalla y lleva el n.º 1
  3. dispositivo de error estándar: por defecto, se refiere a la pantalla y tiene el n.º 2

En C#, el flujo de escritura Console.Out escribe en el dispositivo 1, el flujo de escritura Console.Error escribe en el dispositivo 2 y el flujo de lectura Console.In lee los datos procedentes del dispositivo 0.

Al ejecutar un programa en DOS o Unix, se pueden especificar cuáles serán los dispositivos 0, 1 y 2 para el programa ejecutado. Consideremos la siguiente línea de comandos:

pg arg1 arg2 .. argn

Tras los argumentos argi del programa pg, se pueden redirigir los dispositivos de E/S estándar a archivos:

0<in.txt
el flujo de entrada estándar n.º 0 se redirige al archivo in.txt. Por lo tanto, en el programa, el flujo Console.In obtendrá sus datos del archivo in.txt.
1>out.txt
redirige la salida n.º 1 al archivo out.txt. Esto hace que, en el programa, el flujo Console.Out escriba sus datos en el archivo out.txt
1>>out.txt
Lo mismo, pero los datos escritos se añaden al contenido actual del archivo out.txt.
2>error.txt
redirige la salida n.º 2 al archivo error.txt. Esto hace que, en el programa, el flujo Console.Error escriba sus datos en el archivo error.txt
2>>error.txt
Lo mismo, pero los datos escritos se añaden al contenido actual del archivo error.txt.
1>out.txt 2>error.txt
Los dispositivos 1 y 2 se redirigen ambos a archivos

Cabe señalar que, para redirigir los flujos de E/S del programa pg a archivos, no es necesario modificar el programa pg. Es el sistema operativo el que determina la naturaleza de los dispositivos 0, 1 y 2. Consideremos el siguiente programa:


using System;

namespace Chap1 {

    // redirecciones
    public class P04 {
        public static void Main(string[] args) {
            // lectura del flujo In
            string data = Console.In.ReadLine();
            // escritura del flujo de salida
            Console.Out.WriteLine("écriture dans flux Out : " + data);
            // escritura del flujo de error
            Console.Error.WriteLine("écriture dans flux Error : " + data);
        }//Principal
    }//clase
}

Generemos el ejecutable a partir de este código fuente:

  • en [1]: el ejecutable se crea haciendo clic con el botón derecho del ratón sobre el proyecto / Build
  • en [2]: en una ventana de DOS, se ha creado el ejecutable 04.exe en la carpeta bin/Release del proyecto.

Introducimos los siguientes comandos en la ventana de DOS [2]:

1
2
3
4
5
6
7
8
...\04\bin\Release>echo test >in.txt
...\04\bin\Release>more in.txt
test
...\04\bin\Release>04 0<in.txt 1>out.txt 2>err.txt
...\04\bin\Release>more out.txt
écriture dans flux Out : test
...\04\bin\Release>more err.txt
écriture dans flux Error : test
  • línea 1: se introduce la cadena test en el archivo in.txt
  • líneas 2-3: se muestra el contenido del archivo in.txt para su verificación
  • línea 4: ejecución del programa 04.exe. El flujo In se redirige al archivo in.txt, el flujo Out al archivo out.txt, y el flujo Error al archivo err.txt. La ejecución no genera ninguna salida.
  • líneas 5-6: contenido del archivo out.txt. Este contenido nos muestra que:
  • se ha leído el archivo in.txt
  • la salida a pantalla se ha redirigido al archivo out.txt
  • líneas 7-8: comprobación análoga para el archivo err.txt

Se ve claramente que los flujos Out y In no escriben en los mismos dispositivos, ya que se han podido redirigir por separado.

3.3.5. Asignación del valor de una expresión a una variable

Nos centramos aquí en la operación variable=expression;

La expresión puede ser de tipo: aritmético, relacional, booleano o de caracteres

3.3.5.1. Interpretación de la operación de asignación

La operación variable=expression;

es en sí misma una expresión cuya evaluación se lleva a cabo de la siguiente manera:

  • se evalúa el lado derecho de la asignación: el resultado es un valor V.
  • el valor V se asigna a la variable
  • el valor V es también el valor de la asignación, considerada esta vez como una expresión.

Así es como la operación

    V1=V2=expression

es válida. Debido a la prioridad, se evaluará el operador = situado más a la derecha. Por lo tanto, tenemos

    V1=(V2=expression)

La expresión V2=expresión se evalúa y tiene como valor V. La evaluación de esta expresión ha provocado la asignación de V a V2. A continuación, se evalúa el siguiente operador = de la forma:

    V1=V

El valor de esta expresión sigue siendo V. Su evaluación provoca la asignación de V a V1.

Por lo tanto, la operación V1=V2=expresión

es una expresión cuya evaluación

  • provoca la asignación del valor de expression a las variables V1 y V2
  • devuelve como resultado el valor expression.

Esto se puede generalizar a una expresión del tipo:

V1=V2=....=Vn=expression

3.3.5.2. Expresión aritmética

Los operadores de las expresiones aritméticas son los siguientes:

  • suma

  • resta

* multiplicación

/ división: el resultado es el cociente exacto si al menos uno de los operandos es un número real. Si ambos operandos son enteros, el resultado es el cociente entero. Así, 5/2 → 2 y 5,0/2 → 2,5.

% división: el resultado es el resto, independientemente de la naturaleza de los operandos, siendo el cociente un número entero. Se trata, por tanto, de la operación módulo.

Existen diversas funciones matemáticas. Estas son algunas de ellas:

double Sqrt(double x)
raíz cuadrada
double Cos(double x)
coseno
double Sin(double x)
Senos
double Tan(double x)
Tangente
double Pow(double x,double y)
x elevado a y (x > 0)
double Exp(double x)
Función exponencial
double Log(double x)
Logaritmo natural
double Abs(double x)
valor absoluto

etc...

Todas estas funciones están definidas en una clase de C# llamada Math. Al utilizarlas, hay que anteponerles el nombre de la clase en la que están definidas. Así, se escribirá:

double x, y=4;
x=Math.Sqrt(y);

La definición completa de la clase Math es la siguiente:

Image

Image

Image

Image

Image

3.3.5.3. Prioridades en la evaluación de expresiones aritméticas

El orden de prioridad de los operadores al evaluar una expresión aritmética es el siguiente (de mayor a menor prioridad):

[fonctions], [ ( )],[ *, /, %], [+, -]

Los operadores de un mismo bloque [ ] tienen la misma prioridad.

3.3.5.4. Expresiones relacionales

Los operadores son los siguientes:

       <, <=, ==, !=, >, >=

Prioridades de los operadores

>, >=, <, <=
==, !=

El resultado de una expresión relacional es el valor booleano false si la expresión es falsa, y true en caso contrario.

      bool fin;
      int x=...;
      fin=x>4;

Comparación de dos caracteres

Supongamos que tenemos dos caracteres: C1 y C2. Es posible compararlos con los operadores

    <, <=, ==, !=, >, >=

De este modo, se comparan sus códigos Unicode, que son números. Según el orden Unicode, se dan las siguientes relaciones:

espace < .. < '0' < '1' < .. < '9' < .. < 'A' < 'B' < .. < 'Z' < .. < 'a' < 'b' < .. <'z'

Comparación de dos cadenas de caracteres

Se comparan carácter por carácter. La primera desigualdad que se encuentra entre dos caracteres da lugar a una desigualdad del mismo sentido en las cadenas.

Ejemplos:

Supongamos que queremos comparar las cadenas «Gato» y «Perro»

Esta última diferencia permite afirmar que «Gato» < «Perro».

Supongamos que queremos comparar las cadenas «Gato» y «Gatito». Hay igualdad en todo momento hasta que se agota la cadena «Gato». En este caso, la cadena agotada se declara como la más «pequeña». Por lo tanto, tenemos la relación

    "Chat" < "Chaton".

Funciones de comparación de dos cadenas

Se pueden utilizar los operadores relacionales == y != para comprobar si dos cadenas son iguales o no, o bien el método Equals de la clase System.String. Para las relaciones < <= > >=, hay que utilizar el método CompareTo de la clase System.String:


using System;

namespace Chap1 {
    class P05 {
        static void Main(string[] args) {
            string chaine1="chat", chaine2="chien";
            int n = chaine1.CompareTo(chaine2);
            bool egal = chaine1.Equals(chaine2);
            Console.WriteLine("i={0}, egal={1}", n, egal);
            Console.WriteLine("chien==chaine1:{0},chien!=chaine2:{1}", "chien"==chaine1,"chien" != chaine2);
        }
    }
}

En la línea 7, la variable i tendrá el valor:

0 si las dos cadenas son iguales

1 si la cadena n.º 1 es mayor que la cadena n.º 2

-1 si la cadena n.º 1 es menor que la cadena n.º 2

En la línea 8, la variable egal tendrá el valor true si las dos cadenas son iguales, y false en caso contrario. En la línea 10, se utilizan los operadores == y != para comprobar si dos cadenas son iguales o no.

Resultados de la ejecución:

i=-1, egal=False
chien==chaine1:False,chien!=chaine2:False

3.3.5.5. Expresiones booleanas

Los operadores que se pueden utilizar son AND (&&) OR (||) NOT (!). El resultado de una expresión booleana es un valor booleano.

Prioridades de los operadores:

  1. !
  2. &&
  3. ||

            double x = 3.5;
            bool valide = x > 2 && x < 4;

Los operadores relacionales tienen prioridad sobre los operadores && y ||.

3.3.5.6. Operaciones con bits

Los operadores

Supongamos que i y j son dos números enteros.

i<<n
desplaza i n bits hacia la izquierda. Los bits entrantes son ceros.
i>>n
desplaza i n bits a la derecha. Si i es un entero con signo (signed char, int, long), se conserva el bit de signo.
i & j
realiza la operación lógica ET entre i y j, bit a bit.
i | j
realiza la operación lógica bit a bit entre i y j (OU).
~i
complementa i a 1
i^j
realiza la operación OU EXCLUSIF entre i y j

Sea el siguiente código:


short i = 100, j = -13;
ushort k = 0xF123;
Console.WriteLine("i=0x{0:x4}, j=0x{1:x4}, k=0x{2:x4}", i,j,k);
Console.WriteLine("i<<4=0x{0:x4}, i>>4=0x{1:x4},k>>4=0x{2:x4},i&j=0x{3:x4},i|j=0x{4:x4},~i=0x{5:x4},j<<2=0x{6:x4},j>>2=0x{7:x4}", i << 4, i >> 4, k >> 4, (short)(i & j), (short)(i | j), (short)(~i), (short)(j << 2), (short)(j >> 2));
  • el formato {0:x4} muestra el parámetro n.º 0 en formato hexadecimal (x) con 4 caracteres (4).

Los resultados de la ejecución son los siguientes:

i=0x0064, j=0xfff3, k=0xf123
i<<4=0x0640, i>>4=0x0006,k>>4=0x0f12,i&j=0x0060,i|j=0xfff7,~i=0xff9b,j<<2=0xffcc,j>>2=0xfffc

3.3.5.7. Combinación de operadores

a=a+b se puede escribir como a+=b

a=a-b se puede escribir como a-=b

Lo mismo ocurre con los operadores /, %, *, <<, >>, &, |, ^. Así, a=a/2; se puede escribir como a/=2;

3.3.5.8. Operadores de incremento y decremento

La notación variable++ significa variable=variable+1 o también variable+=1

La notación variable-- significa variable=variable-1 o también variable-=1.

3.3.5.9. ¿El operador ternario?

La expresión

    expr_cond ? expr1:expr2

se calcula de la siguiente manera:

1 se evalúa la expresión expr_cond. Se trata de una expresión condicional cuyo valor es vrai o faux

2 Si es verdadera, el valor de la expresión es el de expr1 y expr2 no se evalúa.

3 Si es falsa, ocurre lo contrario: el valor de la expresión es el de expr2 y expr1 no se evalúa.

La operación i=(j>4 ? j+1:j-1); asignará a la variable i: j+1 si j>4, j-1 en caso contrario. Es lo mismo que escribir if(j>4) i=j+1; else i=j-1;, pero es más conciso.

3.3.5.10. Prioridad general de los operadores

() []  fonction                  
gd
! ~ ++ --                        
dg
new (type) opérateurs cast       
dg
*  /  %                          
gd
+  -                             
gd
<<  >>                           
gd
< <=  > >= instanceof            
gd
==    !=                         
gd
&                                
gd
^                                
gd
|                                
gd
&&                               
gd
||                               
gd
?   :                            
dg
= += -= etc. .                   
dg

gd indica que, en caso de igualdad de prioridad, se aplica la prioridad de izquierda a derecha. Esto significa que, cuando en una expresión hay operadores con la misma prioridad, se evalúa primero el operador situado más a la izquierda en la expresión. dg indica una prioridad de derecha a izquierda.

3.3.5.11. Los cambios de tipo

En una expresión, es posible cambiar momentáneamente la codificación de un valor. A esto se le denomina «cambio de tipo» o, en inglés, «type casting». La sintaxis para cambiar el tipo de un valor en una expresión es la siguiente:

    (type) valeur

El valor adquiere entonces el tipo indicado. Esto conlleva un cambio en la codificación del valor.


using System;

namespace Chap1 {
    class P06 {
        static void Main(string[] args) {
            int i = 3, j = 4;
            float f1=i/j;
            float f2=(float)i/j;
            Console.WriteLine("f1={0}, f2={1}",f1,f2);
        }
    }
}
  • En la línea 7, f1 tendrá el valor 0,0. La división 3/4 es una división entera, ya que ambos operandos son de tipo int.
  • Línea 8: (float)i es el valor de i transformado en float. Ahora tenemos una división entre un número real de tipo float y un entero de tipo int. A continuación, se realiza la división entre números reales. El valor de j también se transformará en el tipo float, y luego se realizará la división entre los dos números reales. f2 tendrá entonces el valor 0,75.

Estos son los resultados de la ejecución:

f1=0, f2=0,75

En la operación (float)i:

  • i es un valor codificado de forma exacta en 2 bytes
  • (float); i es el mismo valor codificado de forma aproximada en número real de 4 bytes

Por lo tanto, se produce una transcodificación del valor de i. Esta transcodificación solo tiene lugar durante el tiempo que dura un cálculo, ya que la variable i conserva siempre su tipo int.

3.4. Las instrucciones de control del desarrollo del programa

3.4.1. Detener

El método Exit, definido en la clase Environment, permite detener la ejecución de un programa.

syntaxe        void Exit(int status)
action        arrête le processus en cours et rend la valeur status au processus père

Exit provoca la finalización del proceso en curso y devuelve el control al proceso que lo ha llamado. Este último puede utilizar el valor de status. En DOS, esta variable de estado se devuelve en la variable de sistema ERRORLEVEL, cuyo valor puede comprobarse en un archivo por lotes. En Unix, con el intérprete de comandos Shell Bourne, es la variable $? la que recoge el valor de status.

    Environment.Exit(0);

detendrá la ejecución del programa con un valor de estado igual a 0.

3.4.2. Estructura de selección simple

 syntaxe :  if (condition) {actions_condition_vraie;} else {actions_condition_fausse;}

Notas:

  • la condición va entre paréntesis.
  • Cada acción termina con un punto y coma.
  • Las llaves no terminan con punto y coma.
  • Las llaves solo son necesarias si hay más de una acción.
  • La cláusula else puede omitirse.
  • No hay ninguna cláusula then.

El equivalente algorítmico de esta estructura es la estructura «si... entonces... si no»:

Ejemplo


    if (x>0)  { nx=nx+1;sx=sx+x;} else dx=dx-x;

Se pueden anidar las estructuras de selección:

if(condition1)
if (condition2)
        {......}
      else         //condición2
         {......}
else         //condición1
     {.......}

A veces surge el siguiente problema:


using System;

namespace Chap1 {
    class P07 {
        static void Main(string[] args) {
            int n = 5;
            if (n > 1)
                if (n > 6)
                    Console.Out.WriteLine(">6");
                else Console.Out.WriteLine("<=6");
        }
    }
}

En el ejemplo anterior, ¿a qué if se refiere el else de la línea 10? La regla es que un else siempre se refiere al if más cercano: if(n>6), de la línea 8, en el ejemplo. Veamos otro ejemplo:


            if (n2 > 1) {
                if (n2 > 6) Console.Out.WriteLine(">6");
       } else Console.Out.WriteLine("<=1");    

Aquí queríamos asignar un else al if(n2>1) y no un else al if(n2>6). Debido a la observación anterior, nos vemos obligados a utilizar llaves en el if(n2>1) {...} else ...

3.4.3. Estructura de casos

La sintaxis es la siguiente:

switch(expression) {
    case v1:     
            actions1;
            break;
    case v2:     
            actions2;
            break;
         . .. .. .. .. ..
    default:     
            actions_sinon;
            break;
}

Notas

  • El valor de la expresión de control de switch puede ser un número entero, un carácter o una cadena de caracteres.
  • La expresión de control va entre paréntesis.
  • La cláusula default puede no aparecer.
  • Los valores vi son valores posibles de la expresión. Si la expresión tiene como valor vi, se ejecutan las acciones que siguen a la cláusula «case» vi.
  • La instrucción break sale de la estructura «case».
  • Cada bloque de instrucciones vinculado a un valor vi debe terminar con una instrucción de salto (break, goto, return, ...); de lo contrario, el compilador señala un error.

Ejemplo

En algoritmos

                selon la valeur de choix
                    cas 0
                         fin du module
                    cas 1
                         exécuter module M1
                    cas 2
                        exécuter module M2
                    sinon
                         erreur<--vrai
                findescas

En C#

int choix = 2; 
bool erreur = false;
switch (choix) {
   case 0: return;
   case 1: M1(); break;
   case 2: M2(); break;
   default: erreur = true; break;
}
}// fin de Main

static void M1() {
    Console.WriteLine("M1");
}

static void M2() {
    Console.WriteLine("M2");
}
}

3.4.4. Estructuras de repetición

3.4.4.1. Número de repeticiones conocido

Estructura «for»

La sintaxis es la siguiente:


    for (i=id;i<=if;i=i+ip){ 
       actions; 
      } 

Notas

  • Los tres argumentos de for van entre paréntesis y están separados por punto y coma.
  • Cada acción de for termina con un punto y coma.
  • Las llaves solo son necesarias si hay más de una acción.
  • La llave no va seguida de un punto y coma.

El equivalente algorítmico es la estructura pour:

pour i variant de id à if avec un pas de ip
    actions
finpour

que se puede traducir a una estructura tantque:

    i  id
    tantque i<=if
        actions
        i i+ip
    fintantque

Estructura «foreach»

La sintaxis es la siguiente:


foreach (Type variable in collection)
    instructions; 
}

Notas

  • collection es una colección de objetos enumerable. La colección de objetos enumerable que ya conocemos es el array
  • Type es el tipo de los objetos de la colección. En el caso de un array, sería el tipo de los elementos del array
  • variable es una variable local del bucle que irá adoptando sucesivamente todos los valores de la colección.

Así, el siguiente código:


            string[] amis = { "paul", "hélène", "jacques", "sylvie" };
            foreach (string nom in amis) {
                Console.WriteLine(nom);
         }

mostraría:

paul
hélène
jacques
sylvie

3.4.4.2. Número de repeticiones desconocido

Existen numerosas estructuras en C# para este caso.

Estructura «while»


    while(condition){
          actions;
        } 

El bucle se repite mientras se cumpla la condición. Es posible que el bucle nunca se ejecute.

Notas:

  • La condición va entre paréntesis.
  • Cada acción termina con un punto y coma.
  • Las llaves solo son necesarias si hay más de una acción.
  • La llave no va seguida de un punto y coma.

La estructura algorítmica correspondiente es la estructura «while»:

tantque condition
    actions
fintantque

Estructura «repetir hasta» (do while)

La sintaxis es la siguiente:


    do{
       instructions;
    }while(condition); 

El bucle se repite hasta que la condición se vuelve falsa. En este caso, el bucle se ejecuta al menos una vez.

Notas

  • La condición va entre paréntesis.
  • Cada acción termina con un punto y coma.
  • Las llaves solo son necesarias si hay más de una acción.
  • La llave no va seguida de un punto y coma.

La estructura algorítmica correspondiente es la estructura «repetir… hasta»:

répéter
    actions
jusqu'à condition

Estructura «for» (for)

La sintaxis es la siguiente:


for(instructions_départ;condition;instructions_fin_boucle){
    instructions; 
}

El bucle se repite mientras la condición sea verdadera (se evalúa antes de cada iteración). Las instrucciones Instructions_départ se ejecutan antes de entrar en el bucle por primera vez. Las instrucciones Instructions_fin_boucle se ejecutan después de cada iteración.

Notas

  • Las diferentes instrucciones de instructions_depart y instructions_fin_boucle están separadas por comas.

La estructura algorítmica correspondiente es la siguiente:

instructions_départ
tantque condition
    actions
    instructions_fin_boucle
fintantque

Ejemplos

Los siguientes fragmentos de código calculan la suma de los 10 primeros números enteros.

            int i, somme, n=10;
            for (i = 1, somme = 0; i <= n; i = i + 1)
                somme = somme + i;

            for (i = 1, somme = 0; i <= n; somme = somme + i, i = i + 1) ;

            i = 1; somme = 0;
            while (i <= n) { somme += i; i++; }

            i = 1; somme = 0;
            do somme += i++;
            while (i <= n);

3.4.4.3. Instrucciones para la gestión de bucles

break
sale del bucle «for», «while» o «do...while».
continue
pasa a la siguiente iteración de los bucles «for», «while» y «do...while»

3.5. Gestión de excepciones

Muchas funciones de C# pueden generar excepciones, es decir, errores. Cuando una función puede generar una excepción, el programador debe gestionarla con el fin de obtener programas más resistentes a los errores: siempre hay que evitar que una aplicación se «cuelgue» de forma imprevista.

La gestión de una excepción se realiza según el siguiente esquema:

try{
    code susceptible de générer une exception
} catch (Exception e){
    traiter l'exception e
}
instruction suivante

Si la función no genera ninguna excepción, se pasa a la siguiente instrucción; en caso contrario, se pasa al cuerpo de la cláusula catch y, a continuación, a la siguiente instrucción. Cabe destacar los siguientes puntos:

  • e es un objeto de tipo Exception o derivado. Se puede ser más preciso utilizando tipos como IndexOutOfRangeException, FormatException, SystemException, etc.: existen varios tipos de excepciones. Al escribir catch (Exception e), se indica que se quieren gestionar todos los tipos de excepciones. Si el código de la cláusula try puede generar varios tipos de excepciones, es posible que se quiera ser más preciso gestionando la excepción con varias cláusulas catch:
try{
    code susceptible de générer les exceptions
} catch ( IndexOutOfRangeException e1){
    traiter l'exception e1
}
} catch ( FormatException e2){
    traiter l'exception e2
}
instruction suivante
  • Se puede añadir una cláusula «finally» a las cláusulas try/catch:
try{
    code susceptible de générer une exception
} catch (Exception e){
    traiter l'exception e
}
finally{
    code exécuté après try ou catch
}
instruction suivante

Tanto si hay una excepción como si no, el código de la cláusula finally siempre se ejecutará.

  • En la cláusula catch, es posible que no se desee utilizar el objeto Exception disponible. En lugar de escribir catch (Exception e){..}, se escribe entonces catch(Exception){...} o, más sencillamente, catch {...}.
  • La clase Exception tiene una propiedad Message que es un mensaje que detalla el error que se ha producido. Por lo tanto, si se quiere mostrar dicho mensaje, se escribirá:
catch (Exception ex){
    Console.WriteLine("L'erreur suivante s'est produite : {0}",ex.Message);
    ...
}//catch
  • La clase Exception tiene un método ToString que devuelve una cadena de caracteres que indica el tipo de la excepción, así como el valor de la propiedad Message. De este modo, se puede escribir:
catch (Exception ex){
    Console.WriteLine("L'erreur suivante s'est produite : {0}", ex.ToString());
    ...
}//catch

También se puede escribir:

catch (Exception ex){
    Console.WriteLine("L'erreur suivante s'est produite : {0}",ex);
    ...
}//catch

El compilador asignará al parámetro {0} el valor ex.ToString().

El siguiente ejemplo muestra una excepción generada por el uso de un elemento de matriz inexistente:


using System;

namespace Chap1 {
    class P08 {
        static void Main(string[] args) {
            // declaración e inicialización de una matriz
            int[] tab = { 0, 1, 2, 3 };
            int i;
            // visualización de la matriz con un «for»
            for (i = 0; i < tab.Length; i++)
                Console.WriteLine("tab[{0}]={1}", i, tab[i]);
            // Visualización de una matriz con un bucle «for each»
            foreach (int élmt in tab) {
                Console.WriteLine(élmt);
            }
            // generación de una excepción
            try {
                tab[100] = 6;
            } catch (Exception e) {
                Console.Error.WriteLine("L'erreur suivante s'est produite : " + e);
                return;
            }//try-catch
            finally {
                Console.WriteLine("finally ...");
            }
        }
    }
}

En el ejemplo anterior, la línea 18 generará una excepción porque el array tab no tiene el elemento n.º 100. La ejecución del programa da los siguientes resultados:

tab[0]=0
tab[1]=1
tab[2]=2
tab[3]=3
0
1
2
3
L'erreur suivante s'est produite : System.IndexOutOfRangeException: L'index se trouve en dehors des limites du tableau.
   à Chap1.P08.Main(String[] args) dans C:\data\travail\2007-2008\c# 2008\poly\Chap1\08\Program.cs:línea 7
finally ...
  • línea 9: se ha producido la excepción [System.IndexOutOfRangeException]
  • línea 11: se ha ejecutado la cláusula finally (líneas 23-25) del código, a pesar de que en la línea 21 había una instrucción return para salir del método. Cabe destacar que la cláusula finally siempre se ejecuta.

He aquí otro ejemplo en el que se gestiona la excepción provocada por la asignación de una cadena de caracteres a una variable de tipo entero cuando la cadena no representa un número entero:


using System;

namespace Chap1 {
    class P08 {
        static void Main(string[] args) {

            // ejemplo 2
            // Se solicita el nombre
            Console.Write("Nom : ");
            // lectura de la respuesta
            string nom = Console.ReadLine();
            // Se pregunta por la edad
            int age = 0;
            bool ageOK = false;
            while (!ageOK) {
                // pregunta
                Console.Write("âge : ");
                // lectura y verificación de la respuesta
                try {
                    age = int.Parse(Console.ReadLine());
                    ageOK = age>=1;
                } catch {
                }//try-catch
                if (!ageOK) {
                    Console.WriteLine("Age incorrect, recommencez...");
                }
            }//while
            // visualización final
            Console.WriteLine("Vous vous appelez {0} et vous avez {1} an(s)",nom,age);
        }
    }
}
  • líneas 15-27: el bucle de introducción de la edad de una persona
  • línea 20: la línea introducida con el teclado se convierte en un número entero mediante el método int.Parse. Este método lanza una excepción si la conversión no es posible. Por eso, la operación se ha colocado dentro de un try/catch.
  • líneas 22-23: si se lanza una excepción, se pasa al bloque «catch», donde no se realiza ninguna acción. De este modo, la variable booleana ageOK, que se había establecido en false en la línea 14, permanecerá en false.
  • línea 21: si se llega a esta línea, es que la conversión de cadena a entero se ha realizado con éxito. No obstante, se comprueba que el entero obtenido sea mayor o igual que 1.
  • Líneas 24-26: se emite un mensaje de error si la edad es incorrecta.

Algunos resultados de la ejecución:

1
2
3
Nom : dupont
âge : 23
Vous vous appelez dupont et vous avez 23 an(s)
1
2
3
4
5
6
7
Nom : durand
âge : x
Age incorrect, recommencez...
âge : -4
Age incorrect, recommencez...
âge : 12
Vous vous appelez durand et vous avez 12 an(s)

3.6. Aplicación de ejemplo - V1

Nos proponemos escribir un programa que permita calcular los impuestos de un contribuyente. Consideramos el caso simplificado de un contribuyente que solo tiene que declarar su salario (cifras de 2004 correspondientes a los ingresos de 2003):

  • se calcula el número de participaciones del asalariado nbParts = nbEnfants/2 + 1 si no está casado, nbEnfants/2 + 2 si está casado, donde nbEnfants es el número de hijos que tiene.
  • Si tiene al menos tres hijos, tiene media parte más
  • se calcula su base imponible R = 0,72 × S, donde S es su salario anual
  • se calcula su coeficiente familiar QF = R/nbParts
  • se calcula su impuesto I. Consideremos la siguiente tabla:
4262
0
0
8382
0,0683
291,09
14 753
0,1914
1322,92
23 888
0,2826
2668,39
38 868
0,3738
4846,98
47 932
0,4262
6883,66
0
0,4809
9505,54

Cada línea tiene 3 campos. Para calcular el impuesto I, se busca la primera línea en la que QF sea menor o igual que el campo 1. Por ejemplo, si QF = 5000, se encontrará la línea

    8382        0.0683        291.09

El impuesto I es entonces igual a 0,0683*R - 291,09*nbParts. Si QF es tal que la relación QF <= campo1 nunca se cumple, entonces se utilizan los coeficientes de la última línea. En este caso:

0 0,4809 9505,54

lo que da como resultado el impuesto I = 0,4809*R - 9505,54*nbParts.

El programa en C# correspondiente es el siguiente:


using System;

namespace Chap1 {
    class Impots {
        static void Main(string[] args) {
            // tablas de datos necesarias para el cálculo del impuesto
            decimal[] limites = { 4962M, 8382M, 14753M, 23888M, 38868M, 47932M, 0M };
            decimal[] coeffR = { 0M, 0.068M, 0.191M, 0.283M, 0.374M, 0.426M, 0.481M };
            decimal[] coeffN = { 0M, 291.09M, 1322.92M, 2668.39M, 4846.98M, 6883.66M, 9505.54M };

            // se recupera el estado civil
            bool OK = false;
            string reponse = null;
            while (!OK) {
                Console.Write("Etes-vous marié(e) (O/N) ? ");
                reponse = Console.ReadLine().Trim().ToLower();
                if (reponse != "o" && reponse != "n")
                    Console.Error.WriteLine("Réponse incorrecte. Recommencez");
                else OK = true;
            }//while
            bool marie = reponse == "o";

            // número de hijos
            OK = false;
            int nbEnfants = 0;
            while (!OK) {
                Console.Write("Nombre d'enfants : ");
                try {
                    nbEnfants = int.Parse(Console.ReadLine());
                    OK = nbEnfants >= 0;
                } catch {
                }// try
                if (!OK) {
                    Console.WriteLine("Réponse incorrecte. Recommencez");
                }
            }// mientras

            // salario
            OK = false;
            int salaire = 0;
            while (!OK) {
                Console.Write("Salaire annuel : ");
                try {
                    salaire = int.Parse(Console.ReadLine());
                    OK = salaire >= 0;
                } catch {
                }// try
                if (!OK) {
                    Console.WriteLine("Réponse incorrecte. Recommencez");
                }
            }// while

            // cálculo del número de participaciones
            decimal nbParts;
            if (marie) nbParts = (decimal)nbEnfants / 2 + 2;
            else nbParts = (decimal)nbEnfants / 2 + 1;
            if (nbEnfants >= 3) nbParts += 0.5M;

            // renta imponible
            decimal revenu = 0.72M * salaire;

            // coeficiente familiar
            decimal QF = revenu / nbParts;

            // búsqueda del tramo impositivo correspondiente a QF
            int i;
            int nbTranches = limites.Length;
            limites[nbTranches - 1] = QF;
            i = 0;
            while (QF > limites[i]) i++;
            // el impuesto
            int impots = (int)(coeffR[i] * revenu - coeffN[i] * nbParts);

            // se muestra el resultado
            Console.WriteLine("Impôt à payer : {0} euros", impots);
        }
    }
}
  • líneas 7-9: los valores numéricos llevan el sufijo M (Money) para que sean de tipo decimal.
  • línea 16:
    • Console.ReadLine() devuelve la cadena C1 introducida mediante el teclado
    • C1.Trim() elimina los espacios iniciales y finales de C1; devuelve la cadena C2
    • C2.ToLower() devuelve la cadena C3, que es la cadena C2 convertida a minúsculas.
  • línea 21: el valor booleano marie recibe el valor true o false de la relación reponse=="o"
  • línea 29: la cadena introducida mediante el teclado se transforma en el tipo int. Si la transformación falla, se lanza una excepción.
  • línea 30: el valor booleano OK recibe el valor true o false de la relación nbEnfants>=0
  • líneas 55-56: no se puede escribir simplemente nbEnfants/2. Si nbEnfants fuera igual a 3, tendríamos 3/2, una división entera que daría 1 y no 1,5. Por lo tanto, se escribe (decimal)nbEnfants para convertir en real uno de los operandos de la división y obtener así una división entre números reales.

A continuación se muestran algunos ejemplos de ejecución:

Etes-vous marié(e) (O/N) ? o
Nombre d'enfants : 2
Salaire annuel : 60000
Impôt à payer : 4282 euros
Etes-vous marié(e) (O/N) ? oui
Réponse incorrecte. Recommencez
Etes-vous marié(e) (O/N) ? o
Nombre d'enfants : trois
Réponse incorrecte. Recommencez
Nombre d'enfants : 3
Salaire annuel : 60000 euros
Réponse incorrecte. Recommencez
Salaire annuel : 60000
Impôt à payer : 2959 euros

3.7. Argumentos del programa principal

La función principal Main puede admitir como parámetro una matriz de cadenas: String[] (o string[]). Esta matriz contiene los argumentos de la línea de comandos utilizada para ejecutar la aplicación. Así, si se ejecuta el programa P con el siguiente comando (DOS):


        P arg0 arg1 … argn

y si la función Main se declara de la siguiente manera:

public static void Main(string[] args)

tendremos args[0]="arg0", args[1]="arg1" … He aquí un ejemplo:


using System;

namespace Chap1 {
    class P10 {
        static void Main(string[] args) {
            // se enumeran los parámetros recibidos
            Console.WriteLine("Il y a  " + args.Length + " arguments");
            for (int i = 0; i < args.Length; i++) {
                Console.Out.WriteLine("arguments[" + i + "]=" + args[i]);
            }
        }
    }
}

Para pasar argumentos al código ejecutado, se procederá de la siguiente manera:

  • en [1]: clic con el botón derecho del ratón sobre el proyecto / Propiedades
  • en [2]: pestaña [Debug]
  • en [3]: introducir los argumentos

La ejecución da los siguientes resultados:

1
2
3
4
5
Il y a  4 arguments
arguments[0]=a0
arguments[1]=a1
arguments[2]=a2
arguments[3]=a3

Cabe destacar que la firma

public static void Main()

es válida si la función Main no espera parámetros.

3.8. Las enumeraciones

Una enumeración es un tipo de datos cuyo dominio de valores es un conjunto de constantes enteras. Consideremos un programa que tiene que gestionar las calificaciones de un examen. Habría cinco: Passable, AssezBien, Bien, TrèsBien y Excellent.

Entonces, podríamos definir una enumeración para estas cinco constantes:


        enum Mentions { Passable, AssezBien, Bien, TrèsBien, Excellent };

Internamente, estas cinco constantes se codifican mediante números enteros consecutivos que comienzan por 0 para la primera constante, 1 para la siguiente, etc. Se puede declarar una variable para que adopte estos valores de la enumeración:


            // una variable que toma sus valores de la enumeración «Mentions»
Mentions maMention = Mentions.Passable;

Se puede comparar una variable con los diferentes valores posibles de la enumeración:


            if (maMention == Mentions.Passable) {
                Console.WriteLine("Peut mieux faire");
}

Se pueden obtener todos los valores de la enumeración:


            // lista de menciones en forma de cadenas
            foreach (Mentions m in Enum.GetValues(maMention.GetType())) {
                Console.WriteLine(m);
}

Del mismo modo que el tipo simple int es equivalente a la estructura System.Int32, el tipo simple enum es equivalente a la estructura System.Enum. Esta estructura cuenta con un método estático GetValues que permite obtener todos los valores de un tipo enumerado que se pase como parámetro. Este debe ser un objeto de tipo Type, que es una clase que contiene información sobre el tipo de un dato. El tipo de una variable v se obtiene mediante v.GetType(). El tipo de un tipo T se obtiene mediante typeof(T). Por lo tanto, aquí maMention.GetType() devuelve el objeto Type de la enumeración Mentions, y Enum.GetValues(maMention.GetType()) la lista de valores de la enumeración Mentions.

Si ahora escribimos


            //: lista de menciones en forma de números enteros
            foreach (int m in Enum.GetValues(typeof(Mentions))) {
                Console.WriteLine(m);
}

Línea 2, la variable del bucle es de tipo entero. De este modo, se obtiene la lista de valores de la enumeración en forma de enteros. El objeto de tipo System.Type, correspondiente al tipo de datos Mentions, se obtiene mediante typeof(Mentions). Se podría haber escrito, como anteriormente, maMention.GetType().

El siguiente programa ilustra lo que acabamos de explicar:


using System;

namespace Chap1 {
    class P11 {
        enum Mentions { Passable, AssezBien, Bien, TrèsBien, Excellent };
        static void Main(string[] args) {
            // una variable cuyos valores proceden de la enumeración «Menciones»
            Mentions maMention = Mentions.Passable;
            // visualización del valor de la variable
            Console.WriteLine("mention=" + maMention);
            // prueba con un valor de la enumeración
            if (maMention == Mentions.Passable) {
                Console.WriteLine("Peut mieux faire");
            }
            // lista de menciones en forma de cadenas
            foreach (Mentions m in Enum.GetValues(maMention.GetType())) {
                Console.WriteLine(m);
            }
            //lista de menciones en forma de números enteros
            foreach (int m in Enum.GetValues(typeof(Mentions))) {
                Console.WriteLine(m);
            }
        }
    }
}

Los resultados de la ejecución son los siguientes:

mention=Passable
Peut mieux faire
Passable
AssezBien
Bien
TrèsBien
Excellent
0
1
2
3
4

3.9. Pasar parámetros a una función

Aquí nos centramos en cómo se pasan los parámetros a una función. Consideremos la siguiente función estática:


        private static void ChangeInt(int a) {
            a = 30;
            Console.WriteLine("Paramètre formel a=" + a);
}

En la definición de la función, línea 1, a se denomina parámetro formal. Solo aparece ahí para la definición de la función changeInt. También podría haberse llamado b. Veamos ahora un ejemplo de uso de esta función:


        public static void Main() {
            int age = 20;
            ChangeInt(age);
            Console.WriteLine("Paramètre effectif age=" + age);
}

Aquí, en la instrucción de la línea 3, ChangeInt(edad), age es el parámetro efectivo que va a transmitir su valor al parámetro formal a. Nos interesa saber cómo un parámetro formal recupera el valor de un parámetro efectivo.

3.9.1. Pase por valor

El siguiente ejemplo nos muestra que, por defecto, los parámetros de una función se pasan por valor, es decir, que el valor del parámetro efectivo se copia en el parámetro formal correspondiente. Se trata de dos entidades distintas. Si la función modifica el parámetro formal, el parámetro efectivo no sufre ninguna modificación.


using System;

namespace Chap1 {
    class P12 {
        public static void Main() {
            int age = 20;
            ChangeInt(age);
            Console.WriteLine("Paramètre effectif age=" + age);
        }
        private static void ChangeInt(int a) {
            a = 30;
            Console.WriteLine("Paramètre formel a=" + a);
        }
    }
}

Los resultados obtenidos son los siguientes:

Paramètre formel a=30
Paramètre effectif age=20

El valor 20 del parámetro efectivo age se ha copiado en el parámetro formal a (línea 10). A continuación, este último se ha modificado (línea 11). El parámetro efectivo, por su parte, se ha mantenido sin cambios. Este modo de paso es adecuado para los parámetros de entrada de una función.

3.9.2. Pase por referencia

En un paso por referencia, el parámetro efectivo y el parámetro formal son una misma entidad. Si la función modifica el parámetro formal, el parámetro efectivo también se modifica. En C#, ambos deben ir precedidos de la palabra clave ref:

He aquí un ejemplo:


using System;

namespace Chap1 {
    class P12 {
        public static void Main() {
            // ejemplo 2
            int age2 = 20;
            ChangeInt2(ref age2);
            Console.WriteLine("Paramètre effectif age2=" + age2);
        }
        private static void ChangeInt2(ref int a2) {
            a2 = 30;
            Console.WriteLine("Paramètre formel a2=" + a2);
        }
    }
}

y los resultados de la ejecución:

Paramètre formel a2=30
Paramètre effectif age2=30

El parámetro efectivo ha seguido la modificación del parámetro formal. Este modo de paso es adecuado para los parámetros de salida de una función.

3.9.3. Pase por referencia con la palabra clave «out»

Consideremos el ejemplo anterior en el que la variable age2 no se inicializaría antes de llamar a la función changeInt:


using System;

namespace Chap1 {
    class P12 {
        public static void Main() {
            // ejemplo 2
            int age2;
            ChangeInt2(ref age2);
            Console.WriteLine("Paramètre effectif age2=" + age2);
        }
        private static void ChangeInt2(ref int a2) {
            a2 = 30;
            Console.WriteLine("Paramètre formel a2=" + a2);
        }
    }
}

Al compilar este programa, se produce un error:

    Use of unassigned local variable 'age2'

Se puede solucionar este problema asignando un valor inicial a age2. También se puede sustituir la palabra clave «ref» por la palabra clave «out». De este modo, se indica que el parámetro es únicamente un parámetro de salida y, por lo tanto, no necesita un valor inicial:


using System;

namespace Chap1 {
    class P12 {
        public static void Main() {
            // ejemplo 3
            int age3;
            ChangeInt3(out age3);
            Console.WriteLine("Paramètre effectif age3=" + age3);
        }
        private static void ChangeInt3(out int a3) {
            a3 = 30;
            Console.WriteLine("Paramètre formel a3=" + a3);
        }
    }
}

Los resultados de la ejecución son los siguientes:

Paramètre formel a3=30
Paramètre effectif age3=30