Las bases del lenguaje
Introducción
Como todos los lenguajes de programación, C# impone ciertas reglas al desarrollador. Estas reglas se ponen de manifiesto a través de la sintaxis del lenguaje, aunque cubren el amplio espectro funcional propuesto por C#. Antes de explorar en profundidad las funcionalidades del lenguaje en el siguiente capítulo, estudiaremos las nociones esenciales y fundamentales de C#: la creación de los datos que puede utilizar una aplicación y el procesamiento de estos datos.
Las variables
Los datos que se utilizan en un programa C# se representan mediante variables. Una variable es un espacio de memoria reservado al que se asigna arbitrariamente un nombre y cuyo contenido es un valor con un tipo fijado. Es posible manipular ese contenido en el código utilizando el nombre de la variable.
1. Nomenclatura de las variables
La especificación del lenguaje C# establece algunas reglas que deben tenerse en cuenta cuando se asigna el nombre a una variable:
-
El nombre de una variable puede contener únicamente cifras, caracteres del alfabeto latino -acentuados o no-, el carácter ç o los caracteres especiales _ y µ.
-
El nombre de una variable no puede, en ningún caso, empezar por una cifra. No obstante, puede incluirlas en cualquier otra posición.
-
El lenguaje es sensible a mayúsculas y minúsculas: las variables unavariable y unaVariable son, por tanto, variables distintas.
-
La longitud de un nombre de variable es prácticamente ilimitada: ¡el compilador no muestra ningún error hasta que tenga 30.000 caracteres! Se recomienda no utilizar nombres de variables demasiado largos, la longitud máxima, en la práctica, no suele superar un máximo de treinta caracteres.
-
El nombre de una variable no puede ser una palabra clave del lenguaje. Es posible, no obstante, prefijar una palabra clave con un carácter autorizado o con el carácter @ para utilizar un nombre de variable similar.
Los nombres de variables siguientes son válidos en el compilador C#:
-
miVariable
-
miVariableNumero1
-
@void (void es una palabra clave de C#)
-
µn4_VàRïãbl3
De manera general, es preferible utilizar nombres de variables explícitos, es decir, que permitan saber a qué se corresponde el valor almacenado en la variable, como nombreCliente, importeCompra o edadDelEmpleado.
2. Tipo de las variables
Una de las características de C# es la noción de tipado estático: cada variable se corresponde con un tipo de datos y no puede cambiar. Además, este tipo debe poder determinarse en tiempo de compilación.
a. Tipos valor y tipos referencia
Los distintos tipos que pueden utilizarse en C# se dividen en dos familias: los tipos valor y los tipos referencia. Esta noción puede resultar algo desconcertante a primera vista, pues una variable representa exactamente un dato y, por tanto, un valor....
Las constantes
Cuando se está programando una aplicación, llega un momento en el que se desea utilizar determinados valores que no sufren modificaciones. Un ejemplo típico es la constante matemática π. Este valor se define como se muestra a continuación en la clase Math del framework .NET:
public const double PI = 3.14159;
Esta instrucción es una declaración de variable a la que se le agrega la palabra clave const. El uso de esta palabra clave permite fijar el valor de PI.
Es posible definir constantes calculadas a partir de otras constantes:
public const double 2PI = 2 * Math.PI;
Los operadores
Los operadores son palabras clave del lenguaje que permiten ejecutar procesamientos sobre variables, valores de retorno de funciones o valores literales (los operandos).
La combinación de un operador y de uno o varios operandos, constituye una expresión que se evaluará en tiempo de ejecución y devolverá un resultado en función de los distintos elementos que la componen.
Ya hemos visto el operador de asignación =, que permite asignar un valor a una variable. Este valor puede ser otra variable, un valor literal, el resultado de una función o el resultado de una expresión.
Los distintos operadores pueden clasificarse en seis familias, relativas al tipo de procesamiento que permiten realizar.
1. Los operadores de acceso
El lenguaje C# define varios modos de acceso a los datos de los objetos que permite manipular. Cada uno de estos modos utiliza un operador particular.
a. Acceso simple: .
C# utiliza el símbolo . (punto) para permitir acceder a los miembros de un objeto, o eventualmente de un tipo.
//Aquí, se utiliza el operador . para acceder
//al miembro "Apellido" de la variable "persona"
string apellidoAlumno = persona.Apellido;
b. Acceso indexado: [ ]
El operador de acceso indexado ofrece la posibilidad de acceder a un valor contenido en una variable proporcionando un índice. El uso de variables de tipo array implica, por lo general, el uso de este operador para la lectura o la modificación de los datos que contiene.
Elemento único
Para acceder a un elemento particular de la matriz, el índice debe ser un valor entero que represente la posición del elemento dentro de la matriz. Tenga cuidado, en C #, el primer elemento de una matriz siempre tiene índice 0.
int[] array = new int[10];
array[0] = 123;
Subconjuntos de elementos
A partir de C # 8, también es posible utilizar este operador para extraer un subconjunto de elementos de una matriz. Para ello, el índice debe ser de tipo Range. Este tipo representa un intervalo que respeta las siguientes reglas:
-
El índice de inicio es inclusivo: el elemento de este índice se incluye en el resultado.
-
El índice final es exclusivo: el resultado excluye el elemento de este índice.
El intervalo matemático [1 , 3[ se representa en C # por la expresión...
Las estructuras de control
Las estructuras de control permiten crear un flujo de procesamiento complejo. Existen dos tipos de estructuras de control:
-
Las estructuras condicionales, que ejecutan un procesamiento en función de una condición.
-
Las estructuras de bucles, que ejecutan varias veces un mismo procesamiento.
1. Las estructuras condicionales
Existen dos estructuras condicionales.
a. if ... else
La estructura if ... else recibe como parámetro una expresión que devuelve un valor booleano, como una expresión de comparación. Si la condición se cumple, el bloque de código correspondiente se ejecuta.
En caso contrario, se ofrecen tres opciones:
-
Se define un bloque else justo a continuación del if y en ese caso se ejecuta el código de este bloque.
-
Existen varios bloques else if tras el if y cada expresión de condición se ejecuta hasta que una de ellas devuelve true. Entonces se ejecuta el bloque de la instrucción else if (o else, eventualmente).
-
No existe ningún bloque else o else if, y la ejecución del código continúa tras la salida del bloque if.
La sintaxis de esta estructura es la siguiente:
if (<expresión de condición>)
{
//Situar aquí el código a ejecutar si la expresión devuelve true
}
[ else if (<expresión de condición2>)
{
//Situar aquí el código a ejecutar si la primera expresión
//devuelve false y la segunda expresión devuelve true
}]
[else if...]
[ else
{
//Situar aquí el código a ejecutar si ninguna de las expresiones
//de los bloques if y else if devuelve true
}]
El uso de bloques else if o else es opcional. Pero, lógicamente, un bloque else o else if no puede existir sin un bloque if.
Esta estructura puede tener varias decenas de bloques else if asociados, pero no puede tener más que un único bloque else.
b. switch
La estructura switch selecciona una rama de ejecución en función de un argumento y la comprobación de los valores de ese parámetro. El argumento que se pasa a esta estructura debe ser de uno de los siguientes...
Las funciones
Las funciones son un elemento central en el desarrollo con C#. En efecto, todas las instrucciones de una aplicación escrita en C# deben situarse en funciones.
Cada función representa una unidad de procesamiento reutilizable que puede recibir uno o varios parámetros y devolver un valor.
La escritura de funciones permite estructurar el código desacoplando de manera lógica las funcionalidades desarrolladas. Se recomienda, para una buena legibilidad y una buena mantenibilidad, limitar la longitud de las funciones.
Muchos desarrolladores optan, de este modo, por una longitud que permita ver el código completo de una función en "una pantalla". Esta longitud es del todo relativa, pero puede convenir a cada uno. Esta regla no es, evidentemente, absoluta, pero puede ayudar, especialmente en casos de trabajo en equipo o en la relectura y la depuración.
Para alcanzar este objetivo es necesario limitar la responsabilidad de las funciones: cada una debería realizar un tipo de tarea únicamente.
1. Escritura de una función
La sintaxis general para escribir una función es la siguiente:
<modificador de acceso> <tipo de retorno> <Nombre de la función>(
[parámetro 1, [parámetro 2]...])
{
//Procesamientos
return <valor>;
}
El par nombre/lista de parámetros define la firma de la función. Es lo que va a permitir al compilador reconocer la existencia de una función durante su llamada.
El tipo de retorno de la función puede ser cualquier tipo accesible por el código de la función.
Una función debe, obligatoriamente y en todos los casos, devolver un valor explícito. Para ello, se utiliza la palabra clave return seguida del valor a devolver.
public int CalcularEdadDelCapitan()
{
int edadDelCapitan = 0;
//Procesamientos...
return edadDelCapitan;
}
public string RecuperarTipoDeDia()
{
if (DateTime.Today.Day % 2 == 0)
return "DIA PAR";
}
Aquí, la función RecuperarTipoDeDia no es válida. En efecto, no siempre devuelve un valor, salvo cuando el día es par....
Las tuplas
Cuando una función debe devolver varios valores, el uso de parámetros de escritura es una solución muy funcional. Por otro lado, puede ser tedioso debido a la pesadez de escritura que implica. Para evitar esto, la solución más coherente es devolver los diferentes valores en forma de conjunto. En C#, estos conjuntos de datos se llaman objetos: esto se explicará en detalle en el siguiente capítulo. La séptima versión del lenguaje integra la creación y el uso de objetos simples que se pueden utilizar para devolver varios valores: las tuplas.
Una tupla se describe bajo la forma de un conjunto de tipos entre paréntesis. Cada uno de los tipos corresponde a un dato que se puede integrar en el conjunto de datos. La siguiente línea permite declarar una tupla que contiene dos valores enteros.
(int, int) miVariable;
Las tuplas pueden tener más de dos valores, e incluso un número virtual ilimitado de datos. De todos modos no se aconseja utilizarlas para agrupar un gran número de elementos. La siguiente declaración de variables agrupa tres valores enteros y una cadena de carácteres, y representa unas coordenadas espaciales X, Y, Z y un identificador asociado.
(int, int, int, string) coordenadas;
La asignación de un valor a una variable de tipo tupla tiene una nomenclatura parecida a su declaración. Los valores se pasan como una lista entre paréntesis....
Los atributos
Los atributos son elementos de código que permiten proveer información relativa a otros elementos de una aplicación.
Se almacenan en los metadatos de un ensamblado en tiempo de compilación y pueden leerse una vez finaliza la compilación. Pueden describir un ensamblado, proveer información útil para la depuración o dar alguna pista sobre el uso de un elemento de código. Es posible definir atributos sobre una gran cantidad de elementos del código. Se listan en la enumeración System.AttributeTargets e incluyen, en particular, los ensamblados, las clases y los métodos.
Un atributo se define situando en el código el nombre de su tipo entre los símbolos [ y ]. Por convención, los tipos de atributos son un nombre que termina en Attribute, aunque es posible acortar este nombre cuando se utilizan omitiendo el sufijo. Las dos siguientes declaraciones de clase producen exactamente el mismo efecto.
[ObsoleteAttribute]
public class Client
{
}
[Obsolete]
public class Client
{
}
La reflexión
El framework .NET incluye un juego de clases y métodos que permite leer los metadatos de los ensamblados, de los que forman parte los atributos. Se encuentran principalmente en el espacio de nombres System.Reflection. Las posibilidades que ofrecen estos tipos permiten implementar escenarios...