Tema 4: Aspectos
léxicos
(C) 2001 José Antonio González Seco
Comentarios
Un comentario es texto que incluido en el código
fuente de un programa con la idea de facilitar su legibilidad a los
programadores y cuyo contenido es, por defecto, completamente
ignorado por el compilador. Suelen usarse para incluir
información sobre el autor del código, para aclarar
el significado o el porqué de determinadas secciones de
código, para describir el funcionamiento de los
métodos de las clases, etc.
En C# hay dos formas de escribir comentarios. La primera consiste
en encerrar todo el texto que se desee comentar entre caracteres
/* y */ siguiendo la siguiente sintaxis:
/*<texto>*/
Estos comentarios pueden abarcar tantas líneas como sea
necesario. Por ejemplo:
/* Esto es un comentario
que ejemplifica cómo se escribe comentarios que ocupen varias líneas */
Ahora bien, hay que tener cuidado con el hecho de que no es posible
anidar comentarios de este tipo. Es decir, no vale escribir
comentarios como el siguiente:
/* Comentario contenedor /* Comentario contenido */ */
Esto se debe a que como el compilador ignora todo el texto
contenido en un comentario y sólo busca la secuencia
*/ que marca su final, ignorará el segundo /*
y cuando llegue al primer */ considerará que ha
acabado el comentario abierto con el primer /* (no el
abierto con el segundo) y pasará a buscar código.
Como el */ sólo lo admite si ha detectado antes
algún comentario abierto y aún no cerrado (no
mientras busca código), cuando llegue al segundo */
considerará que ha habido un error ya que encontrará
el */ donde esperaba encontrar código
Dado que muchas veces los comentarios que se escriben son muy
cortos y no suelen ocupar más de una línea, C# ofrece
una sintaxis alternativa más compacta para la escritura este
tipo de comentarios en las que se considera como indicador del
comienzo del comentario la pareja de caracteres // y como
indicador de su final el fin de línea. Por tanto, la
sintaxis que siguen estos comentarios es:
// <texto>
Y un ejemplo de su uso es:
// Este comentario ejemplifica como escribir comentarios abreviados de una sola línea
Estos comentarios de una sola línea sí que pueden
anidarse sin ningún problema. Por ejemplo, el
siguiente comentario es perfectamente válido:
// Comentario contenedor // Comentario contenido
Identificadores
Al igual que en cualquier lenguaje de programación, en C# un
identificador no es más que, como su propio nombre
indica, un nombre con el que identificaremos algún elemento
de nuestro código, ya sea una clase, una variable, un
método, etc.
Típicamente el nombre de un identificador será una
secuencia de cualquier número de caracteres
alfanuméricos -incluidas vocales acentuadas y
eñes- tales que el primero de ellos no sea un número.
Por ejemplo, identificadores válidos serían: Arriba,
caña, C3P0, áëÎò, etc; pero no lo
serían 3com, 127, etc.
Sin embargo, y aunque por motivos de legibilidad del código
no se recomienda, C# también permite incluir dentro de un
identificador caracteres especiales imprimibles tales como
símbolos de diéresis, subrayados, etc. siempre y
cuando estos no tengan un significado especial dentro del lenguaje.
Por ejemplo, también serían identificadores
válidos _barco_, c¨k y A·B pero no
C# (# indica inicio de directiva de preprocesado) o a!b
(! indica operación lógica "not")
Finalmente, C# da la posibilidad de poder escribir identificadores
que incluyan caracteres Unicode que no se puedan imprimir usando el
teclado de la máquina del programador o que no sean
directamente válidos debido a que tengan un significado
especial en el lenguaje. Para ello, lo que permite es escribir
estos caracteres usando secuencias de escape, que no son
más que secuencias de caracteres con las sintaxis:
\u<dígito><dígito><dígito><dígito>
ó
\U<dígito><dígito><dígito><dígito><dígito><dígito><dígito><dígito>
Estos dígitos indican es el código Unicode del
carácter que se desea incluir como parte del identificador,
y cada uno de ellos ha de ser un dígito hexadecimal
válido. (0-9, a-f ó A-F) Hay que señalar que
el carácter u ha de escribise en minúscula
cuando se indiquen caracteres Unicode con 4 dígitos y en
mayúscula cuando se indiquen con caracteres de ocho.
Ejemplos de identificadores válidos son C\u0064 (equivale a
C#, pues 64 es el código de # en Unicode) ó
a\U00000033b (equivale a a!b).
Palabras reservadas
Aunque antes se han dado una serie de restricciones sobre
cuáles son los nombres válidos que se pueden dar en
C# a los identificadores, falta todavía por dar una: los
siguientes nombres no son válidos como identificadores ya
que tienen un significado especial en el lenguaje:
abstract, as, base, bool, break, byte, case, catch, char, checked, class, const,
continue, decimal, default, delegate, do, double, else, enum, event, explicit,
extern, false, finally, fixed, float, for, foreach, goto, if, implicit, in, int, interface,
internal, lock, is, long, namespace, new, null, object, operator, out, override,
params, private, protected, public, readonly, ref, return, sbyte, sealed, short,
sizeof, stackalloc, static, string, struct, switch, this, throw, true, try, typeof,
uint, ulong, unchecked, unsafe, ushort, using, virtual, void, while
Aparte de estas palabras reservadas, si en futuras implementaciones
del lenguaje se decidiese incluir nuevas palabras reservadas,
Microsoft dice que dichas palabras habrían de incluir al
menos dos símbolos de subrayado consecutivos (__) Por tanto,
para evitar posibles conflictos futuros no se recomienda dar a
nuestros identificadores nombres que contengan dicha secuencia de
símbolos.
Aunque directamente no podemos dar estos nombres a nuestros
identificadores, C# proporciona un mecanismo para hacerlo
indirectamente y de una forma mucho más legible que usando
secuencias de escape. Este mecanismo consiste en usar el
carácter @ para prefijar el nombre coincidente
con el de una palabra reservada que queramos dar a nuestra
variable. Por ejemplo, el siguiente código es válido:
class @class
{
static void @static(bool @bool)
{
if (@bool)
Console.WriteLine("cierto");
else
Console.WriteLine("falso");
}
}
Lo que se ha hecho en el código anterior ha sido usar
@ para declarar una clase de nombre class con un
método de nombre static que toma un parámetro de
nombre bool, aún cuando todos estos nombres son palabras
reservadas en C#.
Hay que precisar que aunque el nombre que nosotros escribamos
sea por ejemplo @class, el nombre con el que el compilador va a
tratar internamente al identificador es solamente class. De hecho,
si desde código escrito en otro lenguaje adaptado a .NET
distinto a C# hacemos referencia a éste identificador y en
ese lenguaje su nombre no es una palabra reservada, el nombre con
el que deberemos referenciarlo es class, y no @class (si
también fuese en ese lenguaje palabra reservada
habría que referenciarlo con el mecanismo que el lenguaje
incluyese para ello, que quizás también podría
consistir en usar @ como en C#)
En realidad, el uso de @ no se tiene porqué limitar a
preceder palabras reservadas en C#, sino que podemos
preceder cualquier nombre con él. Sin embargo, hacer esto no
se recomienda, pues es considerado como un mal hábito de
programación y puede provocar errores muy sutiles como el
que muestra el siguiente ejemplo:
class A
{
int a; // (1)
int @a; // (2)
public static void Main()
{}
}
Si intentamos compilar este código se producirá un
error que nos informará de que el campo de nombre a ha sido
declarado múltiples veces en la clase A. Esto se debe a que
como @ no forma parte en realidad del nombre del
identificador al que precede, las declaraciones marcadas con
comentarios como (1) y (2) son equivalentes.
Hay que señalar por último una cosa respecto al
carácter @: sólo puede preceder al
nombre de un identificador, pero no puede estar contenido
dentro del mismo. Es decir, identificadores como i5322@fie.us.es no
son válidos.
Literales
Un literal es la representación explícita de
los valores que pueden tomar los tipos básicos del lenguaje.
A continuación se explica cuál es la sintaxis con que
se escriben los literales en C# desglosándolos según
el tipo de valores que representan:
-
Literales reales: Los números reales se
escriben de forma similar a los enteros, aunque sólo se
pueden escribir en forma decimal y para separar la parte entera
de la real usan el tradicional punto decimal (carácter
.) También es posible representar los
reales en formato científico, usándose para
indicar el exponente los caracteres e ó E.
Ejemplos de literales reales son 0.0, 5.1, -5.1, +15.21,
3.02e10, 2.02e-2, 98.8E+1, etc.
Al igual que ocurría con los literales enteros, los
literales reales también pueden incluir sufijos que indiquen
el tipo de dato real al que pertenecen, aunque nuevamente no los
veremos hasta el Tema 7: Variables y tipos de datos
-
Literales lógicos: Los únicos
literales lógicos válidos son true y
false, que respectivamente representan los valores
lógicos cierto y falso.
-
Literales de carácter:
Prácticamente cualquier carácter se puede
representar encerrándolo entre comillas simples. Por
ejemplo, 'a' (letra a), ' ' (carácter de
espacio), '?' (símbolo de interrogación),
etc. Las únicas excepciones a esto son los caracteres que
se muestran en la Tabla 4.1, que han de
representarse con secuencias de escape que indiquen su valor
como código Unicode o mediante un formato especial tal y
como se indica a continuación:
|
Carácter
|
Código de escape Unicode
|
Código de escape especial
|
| Comilla simple |
\u0027 |
\' |
| Comilla doble |
\u0022
|
\"
|
| Carácter nulo
|
\u0000
|
\0
|
| Alarma
|
\u0007
|
\a
|
| Retroceso
|
\u0008
|
\b
|
| Salto de página
|
\u000C
|
\f
|
| Nueva línea
|
\u000A
|
\n
|
| Retorno de carro
|
\u000D
|
\r
|
| Tabulación horizontal
|
\u0009
|
\t
|
| Tabulación vertical
|
\u000B
|
\v
|
| Barra invertida
|
\u005C
|
\\
|
Tabla 4.1: Códigos de escape especiales
En realidad, de la tabla anterior hay que matizar que el
carácter de comilla doble también puede aparecer dentro de
un literal de cadena directamente, sin necesidad de usar secuencias
de escape. Por tanto, otros ejemplos de literales de
carácter válidos serán '\"',
'"', '\f', '\u0000', '\\',
'\'', etc.
Aparte de para representar los caracteres de la tabla anterior,
también es posible usar los códigos de escape Unicode para
representar cualquier código Unicode, lo que suele usarse
para representar literales de caracteres no incluidos en los
teclados estándares.
Junto al formato de representación de códigos de
escape Unicode ya visto, C# incluye un formato abreviado para
representar estos códigos en los literales de
carácter si necesidad de escribir siempre cuatro
dígitos aún cuando el código a representar
tenga muchos ceros en su parte izquierda. Este formato consiste en
preceder el código de \x en vez de \u. De este
modo, los literales de carácter '\U00000008',
'\u0008', '\x0008', '\x008', '\x08'
y '\x8' son todos equivalentes. Hay que tener en cuenta que
este formato abreviado sólo es válido en los
literales de carácter, y no a la hora de dar nombres a los
identificadores.
-
Literales de cadena: Una cadena no es
más que una secuencia de caracteres encerrados entre
comillas dobles. Por ejemplo "Hola, mundo",
"camión", etc. El texto contenido dentro
estos literales puede estar formado por cualquier número
de literales de carácter concatenados y sin las comillas
simples, aunque si incluye comillas dobles éstas han de
escribirse usando secuencias de escape porque si no el
compilador las interpretaría como el final de la cadena.
Aparte del formato de escritura de literales de cadenas antes
comentado, que es el comúnmente usado en la mayoría
de lenguajes de programación, C# también admite un
nuevo formato para la escritura estos literales tipo de literales
consistente en precederlas de un símbolo @, caso en
que todo el contenido de la cadena sería interpretado tal
cual, sin considerar la existencia de secuencias de escape. A este
tipo de literales se les conoce como literales de cadena
planos y pueden incluso ocupar múltiples líneas.
La siguiente tabla recoge algunos ejemplos de cómo se
interpretan:
|
Literal de cadena
|
Interpretado como...
|
"Hola\tMundo"
|
Hola Mundo
|
@"Hola\tMundo"
|
Hola\tMundo
|
@"Hola
Mundo"
|
Hola
Mundo
|
@"""Hola Mundo"""
|
"Hola Mundo"
|
Tabla 4.2: Ejemplos de literales de cadena planos
El último ejemplo de la tabla se ha aprovechado para mostrar
que si dentro de un literal de cadena plano se desea incluir
caracteres de comilla doble sin que sean confundidos con el final
de la cadena basta duplicarlos.
-
Literal nulo: El literal nulo es un valor especial
que se representa en C# con la palabra reservada null y
se usa como valor de las variables de objeto no inicializadas
para así indicar que contienen referencias nulas.
Operadores
Un operador en C# es un símbolo formado por uno o
más caracteres que permite realizar una determinada
operación entre uno o más datos y produce un
resultado.
A continuación se describen cuáles son los operadores
incluidos en el lenguaje clasificados según el tipo de
operaciones que permiten realizar, aunque hay que tener en cuenta
que C# permite la redefinición del significado de la
mayoría de los operadores según el tipo de dato sobre
el que se apliquen, por lo que lo que aquí se cuenta
se corresponde con los usos más comunes de los mismos:
-
Operaciones aritméticas: Los
operadores aritméticos incluidos en C# son los
típicos de suma (+), resta (-), producto
(*), división (/) y módulo
(%) También se incluyen operadores de "menos
unario" (-) y "más unario"
(+)
Relacionados con las operaciones aritméticas se encuentran
un par de operadores llamados
checked y unchecked que permiten
controlar si se desea detectar los desbordamientos que puedan
producirse si al realizar este tipo de operaciones el
resultado es superior a la capacidad del tipo de datos de sus
operandos. Estos operadores se usan así:
checked (<expresiónAritmética>)
unchecked(<expresiónAritmética>)
Ambos operadores calculan el resultado de
<expresiónAritmética> y lo devuelven si durante
el cálculo no se produce ningún desbordamiento. Sin
embargo, en caso de que haya desbordamiento cada uno actúa
de una forma distinta: checked provoca un error de
compilación si <expresiónAritmética> es
una expresión constante y una excepción
System.OverflowException si no lo es, mientras que
unchecked devuelve el resultado de la expresión
aritmética truncado para modo que quepa en el tamaño
esperado.
Por defecto, en ausencia de los operadores checked y
unchecked lo que se hace es evaluar las operaciones
aritméticas entre datos constantes como si se les aplicase
checked y las operaciones entre datos no constantes
como si se les hubiese aplicado unchecked.
-
Operaciones lógicas: Se incluyen operadores
que permiten realizar las operaciones lógicas
típicas: "and" (&&
y &), "or" (||
y |), "not" (!) y
"xor" (^)
Los operadores && y || se diferencia de
& y | en que los primeros realizan
evaluación perezosa y los segundos no. La evaluación
perezosa consiste en que si el resultado de evaluar el primer
operando permite deducir el resultado de la operación,
entonces no se evalúa el segundo y se devuelve dicho
resultado directamente, mientras que la evaluación no
perezosa consiste en evaluar siempre ambos operandos. Es decir, si
el primer operando de una operación && es
falso se devuelve false directamente, sin evaluar el
segundo; y si el primer operando de una || es cierto se
devuelve true directamente, sin evaluar el otro.
-
Operaciones relacionales: Se han incluido los
tradicionales operadores de igualdad (==), desigualdad
(!=), "mayor que" (>), "menor
que" (<), "mayor o igual que"
(>=) y "menor o igual que" (<=)
-
Operaciones de manipulación de bits:
Se han incluido operadores que permiten realizar a nivel de bits
operaciones "and" (&), "or"
(|), "not" (~), "xor"
(^), desplazamiento a izquierda
(<<) y desplazamiento a derecha
(>>) El operador << desplaza a
izquierda rellenando con ceros, mientras que el tipo de relleno
realizado por >> depende del tipo de dato sobre el
que se aplica: si es un dato con signo mantiene el signo, y en
caso contrario rellena con ceros.
-
Operaciones de asignación: Para realizar
asignaciones se usa en C# el operador =, operador que
además de realizar la asignación que se le
solicita devuelve el valor asignado. Por ejemplo, la
expresión a = b asigna a la variable a el valor de la
variable b y devuelve dicho valor, mientras que la
expresión c = a = b asigna a c y a el valor de b (el
operador = es asociativo por la derecha)
También se han incluido operadores de asignación
compuestos que permiten ahorrar tecleo a la hora de realizar
asignaciones tan comunes como:
temperatura = temperatura + 15; // Sin usar asignación compuesta
temperatura += 15; // Usando asignación compuesta
Las dos líneas anteriores son equivalentes, pues el operador
compuesto +=lo que
hace es asignar a su primer operando el valor que tenía
más el valor de su segundo operando. Como se ve, permite
compactar bastante el código.
Aparte del operador de asignación compuesto +=,
también se ofrecen operadores de asignación
compuestos para la mayoría de los operadores binarios ya
vistos. Estos son:
+=, -=, *=, /=, %=, &=, |=,
^=, <<= y >>=. Nótese
que no hay versiones compuestas para los operadores binarios
&& y ||.
Otros dos operadores de asignación incluidos son los de
incremento(++) y decremento (--) Estos operadores
permiten, respectivamente, aumentar y disminuir en una unidad el
valor de la variable sobre el que se aplican. Así, estas
líneas de código son equivalentes:
temperatura = temperatura + 1; // Sin usar asignación compuesta ni incremento
temperatura += 1; // Usando asignación compuesta
temperatura++; // Usando incremento
Si el operador ++ se coloca tras el nombre de la variable
(como en el ejemplo) devuelve el valor de la variable antes de
incrementarla, mientras que si se coloca antes, devuelve el valor
de ésta tras incrementarla; y lo mismo ocurre con el
operador --. Por ejemplo:
c = b++; // Se asigna a c el valor de b y luego se incrementa b
c = ++b; // Se incrementa el valor de b y luego se asigna a c
La ventaja de usar los operadores ++ y -- es que en
muchas máquinas son más eficientes que el resto de
formas de realizar sumas o restas de una unidad, pues el compilador
traducirlos en una única instrucción en código
máquina.
-
Operaciones con cadenas: Para realizar operaciones
de concatenación de cadenas se puede usar el mismo
operador que para realizar sumas, ya que en C# se ha redefinido
su significado para que cuando se aplique entre operandos que
sean cadenas o que sean una cadena y un carácter lo que
haga sea concatenarlos. Por ejemplo, "Hola"+"
mundo" devuelve "Hola mundo", y "Hola
mund" + "o" también.
-
Operaciones de acceso a tablas: Una tabla
es un conjunto de ordenado de objetos de tamaño fijo.
Para acceder a cualquier elemento de este conjunto se aplica el
operador postfijo [] sobre la tabla para indicar entre
corchetes la posición que ocupa el objeto al que se desea
acceder dentro del conjunto. Es decir, este operador se usa
así:
[<posiciónElemento>]
Un ejemplo de su uso en el que se asigna al elemento que ocupa la
posición 3 en una tabla de nombre tablaPrueba el valor del
elemento que ocupa la posición 18 de dicha tabla es el
siguiente:
tablaPrueba[3] = tablaPrueba[18];
Las tablas se estudian detenidamente en el Tema 7: Variables y
tipos de datos
-
Operador condicional: Es el único operador
incluido en C# que toma 3 operandos, y se usa así:
<condición> ? <expresión1> : <expresión2>
El significado del operando es el siguiente: se evalúa
<condición> Si es cierta se devuelve el resultado de
evaluar <expresión1>, y si es falsa se devuelve el
resultado de evaluar <condición2>. Un ejemplo de su
uso es:
b = (a>0)? a : 0; // Suponemos a y b de tipos enteros
En este ejemplo, si el valor de la variable a es superior a 0 se
asignará a b el valor de a, mientras que en caso contrario
el valor que se le asignará será 0.
Hay que tener en cuenta que este operador es asociativo por la
derecha, por lo que una expresión como a?b:c?d:e es
equivalente a a?b:(c?d:e)
No hay que confundir este operador con la instrucción
condicional if que se tratará en el Tema
8:Instrucciones, pues aunque su utilidad es similar al de
ésta, ? devuelve un valor e if no.
-
Operaciones de delegados: Un delegado es un
objeto que puede almacenar en referencias a uno o más
métodos y a través del cual es posible llamar a
estos métodos. Para añadir objetos a un delegado
se usan los operadores + y +=, mientras que para
quitárselos se usan los operadores - y
-=. Estos conceptos se estudiarán detalladamente
en el Tema 13: Eventos y delegados
-
Operaciones de acceso a objetos: Para acceder a
los miembros de un objeto se usa el operador ., cuya
sintaxis es:
<objeto>.<miembro>
Si a es un objeto, ejemplos de cómo llamar a diferentes
miembros suyos son:
a.b = 2; // Asignamos a su propiedad a el valor 2
a.f(); // Llamamos a su método f()
a.g(2); // Llamamos a su método g() pasándole como parámetro el valor entero 2
a.c += new adelegado(h) // Asociamos a su evento c el código del método h() de
// "tipo" adelegado
No se preocupe si no conoce los conceptos de métodos,
propiedades, eventos y delegados en los que se basa este ejemplo,
pues se explican detalladamente en temas posteriores.
-
Operaciones con punteros: Un puntero es una
variable que almacena una referencia a una dirección de
memoria. Para obtener la dirección de memoria de un
objeto se usa el operador &, para acceder al
contenido de la dirección de memoria almacenada en un
puntero se usa el operador *, para acceder a un miembro
de un objeto cuya dirección se almacena en un puntero se
usa ->, y para referenciar una dirección de
memoria de forma relativa a un puntero se le aplica el operador
[] de la forma puntero[desplazamiento]. Todos estos
conceptos se explicarán más a fondo en el Tema
18: Código inseguro.
-
Operaciones de obtención de información
sobre tipos: De todos los operadores que nos permiten
obtener información sobre tipos de datos el más
importante es typeof, cuya forma de uso es:
typeof(<nombreTipo>)
Este operador devuelve un objeto de tipo System.Type con
información sobre el tipo de nombre <nombreTipo> que
podremos consultar a través de los miembros ofrecidos
por dicho objeto. Esta información incluye detalles tales
como cuáles son sus miembros, cuál es su tipo padre o
a qué espacio de nombres pertenece.
Si lo que queremos es determinar si una determinada
expresión es de un tipo u otro,
entonces el operador a usar es is, cuya sintaxis es la
siguiente:
<expresión> is <nombreTipo>
El significado de este operador es el siguiente: se evalúa
<expresión>. Si el resultado de ésta es del
tipo cuyo nombre se indica en <nombreTipo>se devuelve
true; y si no, se devuelve false. Como se verá
en el Tema 5: Clases, este operador suele usarse en
métodos polimórficos.
Finalmente, C# incorpora un tercer operador que permite obtener
información sobre un tipo de dato: sizeof Este
operador permite obtener el número de bytes que
ocuparán en memoria los objetos de un tipo, y se usa
así:
sizeof(<nombreTipo>)
sizeof sólo puede usarse dentro de
código inseguro, que por ahora basta considerar que son
zonas de código donde
es posible usar punteros. No será hasta el Tema 18:
Código inseguro cuando lo trataremos en profundidad.
Además, sizeof sólo se puede aplicar sobre
nombres de tipos de datos cuyos objetos se puedan almacenar
directamente en pila. Es decir, que sean estructuras (se
verán en el Tema 13) o tipos enumerados (se
verán en el Tema 14)
-
Operaciones de creación de objetos: El
operador más típicamente usado para crear objetos
es new, que se usa así:
new <nombreTipo>(<parametros>)
Este operador crea un objeto de <nombreTipo> pasándole
a su método constructor los parámetros indicados en
<parámetros> y devuelve una referencia al mismo. En
función del tipo y número de estos parámetros
se llamará a uno u otro de los constructores del objeto.
Así, suponiendo que a1 y a2 sean variables de tipo
Avión, ejemplos de uso del operador new son:
Avión a1 = new Avión(); // Se llama al constructor sin parámetros de Avión
Avión a2 = new Avión("Caza"); // Se llama al constructor de Avión que toma
// como parámetro una cadena
En caso de que el tipo del que se haya solicitado la
creación del objeto sea una clase, éste se
creará en memoria dinámica, y lo que
new devolverá será una referencia a la
dirección de pila donde se almacena una referencia a la
dirección del objeto en memoria dinámica. Sin
embargo, si el objeto a crear pertenece a una estructura o a un
tipo enumerado, entonces éste se creará directamente
en la pila y la referencia devuelta por el new se
referirá directamente al objeto creado. Por estas razones, a
las clases se les conoce como tipos referencia ya que de sus
objetos en pila sólo se almacena una referencia a la
dirección de memoria dinámica donde verdaderamente se
encuentran; mientras que a las estructuras y tipos enumerados se
les conoce como tipos valor ya sus objetos se almacenan
directamente en pila.
C# proporciona otro operador que también nos permite
crear objetos. Éste es stackalloc, y se usa
así:
stackalloc <nombreTipo>[<nElementos>]
Este operador lo que hace es crear en pila una tabla de tantos
elementos de tipo <nombreTipo> como indique
<nElementos> y devolver la dirección de memoria en que
ésta ha sido creada. Por ejemplo:
stackalloc sólo puede usarse para inicializar
punteros a objetos de tipos valor declarados como variables
locales. Por ejemplo:
int * p = stackalloc[100]; // p apunta a una tabla de 100 enteros.
-
Operaciones de conversión: Para convertir
unos objetos en otros se utiliza el operador de
conversión, que no consiste más que en preceder la
expresión a convertir del nombre entre paréntesis
del tipo al que se desea convertir el resultado de evaluarla.
Por ejemplo, si l es una variable de tipo long y se
desea almacenar su valor dentro de una variable de tipo
int llamada i, habría que convertir
previamente su valor a tipo int así:
i = (int) l; // Asignamos a i el resultado de convertir el valor de l a tipo int
Los tipos int y long están
predefinidos en C# y permite almacenar valores enteros con signo.
La capacidad de int es de 32 bits, mientras que la de
long es de 64 bits. Por tanto, a no ser que hagamos uso del
operador de conversión, el compilador no nos dejará
hacer la asignación, ya que al ser mayor la capacidad de los
long, no todo valor que se pueda almacenar en un
long tiene porqué poderse almacenar en un
int. Es decir, no es válido:
i = l; //ERROR: El valor de l no tiene porqué caber en i
Esta restricción en la asignación la impone el
compilador debido a que sin ella podrían producirse errores
muy difíciles de detectar ante truncamientos no esperados
debido al que el valor de la variable fuente es superior a la
capacidad de la variable destino.
Existe otro operador que permite realizar operaciones de
conversión de forma muy similar al ya visto. Éste es
el operador as, que se usa así:
<expresión> as <tipoDestino>
Lo que hace es devolver el resultado de convertir el resultado de
evaluar <expresión> al tipo indicado en
<tipoDestino> Por ejemplo, para almacenar en una variable p
el resultado de convertir un objeto t a tipo tipo Persona se
haría:
p = t as Persona;
Las únicas diferencias entre usar uno u otro operador de
conversión son:
-
as sólo es aplicable a tipos referencia y sólo
a aquellos casos en que existan conversiones predefinidas en el
lenguaje. Como se verá más adelante, esto sólo
incluye conversiones entre un tipo y tipos padres suyos y
entre un tipo y tipos hijos suyos.
Una consecuencia de esto es que el programador puede definir
cómo hacer conversiones de tipos por él definidos y
otros mediante el operador (), pero no mediante as.
Esto se debe a que as únicamente indica que se desea que una
referencia a un objeto en memoria dinámica se trate como si
el objeto fuese de otro tipo, pero no implica conversión
ninguna. Sin embargo, () sí que implica
conversión si el <tipoDestino> no es compatible con el
tipo del objeto referenciado. Obviamente, el operador se
aplirará mucho más rápido en los casos
donde no sea necesario convertir.
-
En caso de que se solicite hacer una conversión
inválida as devuelve null mientras que
() produce una excepción
System.InvalidCastException.