Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Aplicacin:
Manual de C# 2
TEMA 5. EL LENGUAJE C#
5.1 Introduccin
Este tema tratar de explicar los elementos del lenguaje C#, profundizando en los tipos de datos definidos, las
estructuras de programacin, as como en las caractersticas del lenguaje que nos permitir desarrollar programas.
Bsicamente cuestiones relacionadas con la sintaxis y el significado de los elementos del lenguaje.
Hay caracteres que no son imprimibles y no tienen una representacin grfica para su uso. En el cdigo UNICODE,
los 31 primeros caracteres no son imprimibles. Con el objeto de poder representar este tipo de caracteres, se puede
utilizar otra forma de representacin para tales caracteres: mediante las secuencias de escape. Una secuencia de
escape est formada por el carcter \ seguido de una combinacin de dgitos o de una letra.
1. Si se usa una combinacin de dgitos, stos deben expresar en hexadecimal el cdigo ASCII o UNICODE del
carcter a representar. Si se usa el cdigo ASCII, se deben preceder los dgitos con la letra x. Si se usa el
cdigo UNICODE se usa la letra u. As, para representar el carcter avance de lnea, cuyo cdigo es 10,
podramos hacerlo de las formas '\x0A' y \u000A.
2. Las secuencias de escape formadas por el carcter \ seguido de letra es una forma equivalente a la anterior.
El motivo es que es ms fcil recordar una letra que el cdigo asociado a dicho carcter. As por ejemplo la
secuencia de escape del tabulador horizontal es \t. No todos los caracteres permiten esta representacin.
La primera forma de representacin tambin sirve para aquellos caracteres que son imprimibles. As el carcter 'K'
tambin se puede representar de las formas '\x4B' y \u004B.
En la siguiente tabla se tienen las secuencias de escape que usan el formato \ seguido de letra.
Retorno de Carro CR 13 \r
Fin Archivo EOF 26 no existe
Escape ESC 27 no existe
Comillas " 34 \"
Apstrofo ' 39 \'
Barra Invertida \ 92 \\
5.3. Tipos
Como ya comentamos, los tipos de C# se clasifican en tipos valor y tipos referencia. Una variable de un tipo valor
almacena directamente un valor, mientras que una variable de un tipo referencia lo que permite almacenar es una
referencia a un objeto, es decir la posicin de memoria donde se encuentra el objeto. Los tipos valor se clasifican en
tipos primitivos, estructuras y tipos enumerados.
Los tipos primitivos se clasifican en enteros, reales y el tipo bool. Los tipos enteros pueden ser con signo:
sbyte, short, int, long y sin signo: byte, ushort, uint, ulong y char. Los tipos reales pueden ser de coma
flotante: float y double o de coma fija: decimal.
Las variables de tipo primitivo que declaremos en nuestros programas deberemos elegirlas segn el tipo de
valores que vayan a almacenar y del rango de stos.
Las estructuras son parecidas a las clases, pero de tipo valor y con ciertas restricciones. Se suelen utilizar
para representar objetos ligeros (que ocupen poca memoria y necesiten procesarse con cierta velocidad).
Las estructuras no pueden derivar de ningn tipo y ningn tipo puede derivar de ellas. Al ser de tipo valor,
las asignaciones de variables de tipo estructura, provocan la copia de los valores que almacena la
estructura, y no la direccin de memoria como ocurre con los tipos referencia.
Para definir estructuras se realiza igual que las clases pero usando la palabra struct en lugar de class. Veamos el
siguiente ejemplo:
using System;
using System.Collections.Generic;
using System.Text;
namespace Estructuras
{
struct Punto
{
public int x, y; // Campos de la estructura
public Punto(int xx, int yy) // Constructor parametrizado
{
x = xx;
y = yy;
}
}
class Program
{
static void Main(string[] args)
{
Punto p1 = new Punto(10, 10);
Punto p2 = p1; // p2 y p1 No referencian al mismo Punto. Son dos objeto Punto
// distintos pero que almacenan los mismos valores en sus campos
p2.x = 100;
Console.WriteLine(p1.x);
}
}
}
La ejecucin presentar en pantalla 10. Esto se debe a que el valor de x modificado es el de p2, y este objeto p2
almacena en su espacio de memoria sus propios valores. Si Punto hubiese sido definido como una clase, si que
hubiese mostrado por pantalla 100, ya que p2 y p1 referenciaran el mismo espacio de memoria.
Todas las estructuras derivan implcitamente del tipo System.ValueType, que a su vez deriva de la clase
System.Object. En la clase Objects est definido el mtodo Equals() el cual permite determinar si dos referencias
sealan al mismo objeto. Ahora bien, en la clase ValueType se ha redefinido el mtodo Equals(), el heredado de
Objects, de modo que devuelva true si los objetos comparados tienen el mismo valor en todos sus campos y false en
caso contrario. Ahora lo que se comparan son los valores almacenados.
Todos los tipos primitivos vistos anteriormente tienen una estructura de datos asociada, de forma que los nombres de
dichos tipos primitivos son en realidad alias de estructuras. As por ejemplo el tipo double es un alias de la
estructura System.Double, o el tipo char es un alias de System.Char. Por lo tanto, los tipos primitivos son objetos
pero que se almacenan por valor.
Estas estructuras tienen definidas propiedades y mtodos. As por ejemplo, la estructura Int32, encapsula un nmero
entero (dato tipo int). Esta estructura dispone de una serie de campos y mtodos que podemos usar. Los ms
importantes son:
Campo Descripcin
MinValue Valor ms pequeo que se puede almacenar de tipo int
MaxValue Valor ms grande que se puede almacenar de tipo int.
Mtodo Descripcin
Parse(string) Convierte un string a un valor int.
ToString() Convierte un valor int en una cadena (objeto string).
Las otras estructuras disponen de atributos y mtodos anlogos. La estructura Double dispone del atributo NaN, (Not
is A Number), la cual representa una constante que es devuelta cuando el resultado de una operacin no est
definido. El mtodo IsNaN(double) devuelve true si el argumento no es un nmero. Los atributos NegativeInfinity y
PositiveInfinity representan los valores infinito negativo y positivo respectivamente El mtodo IsInfinity(double)
devuelve true si el valor del argumento es infinito.
Los tipos enumerados son tipos definidos por el usuario. Permiten utilizar un grupo de constantes a travs de
nombres asociados. Estos nombres son ms representativos que las constantes a las que representan.
Las constantes que se pueden usar deben ser de tipo byte, char, int o long.
Para definir tipos enumerados se utiliza la sintaxis enum tipo_a_definir seguida de los nombres asociados a las
constantes encerrados entre llaves. Por defecto las constantes asociadas son de tipo int y empiezan por el valor cero.
Un tipo enumerado puede definirse tanto fuera como dentro de una clase. Ejemplo:
using System;
using System.Text;
namespace Enumeracion
{
// Definicin del enumerado dia
enum dia { lunes, martes, miercoles, jueves, viernes, sabado, domingo };
class Program
{
static void Main(string[] args)
{
dia d = dia.lunes; // d es de tipo dia y se le asigna lunes
if (d != dia.domingo)
Console.WriteLine("Hoy no es " + dia.domingo);
if (d == 0)
Console.WriteLine("Horror, es " + d);
d = (dia)4; // 4 se convierte a tipo dia, tomando viernes
Console.WriteLine("Por fin es " + d); // se convierte a "viernes"
}
}
}
Hoy no es domingo
Horror, es lunes
Por fin es viernes
Segn el ejemplo anterior, el primer valor de la enumeracin, lunes, se corresponde con 0, el segundo, martes, con
1, mircoles con 2, etc. Para asignar un valor a una variable de tipo enum se usa la sintaxis
nombre_enumeracion.constante. Tambin se puede asignar a la variable el valor entero correspondiente, pero es
necesario realizar una conversin explcita al tipo de la enumeracin. El ejemplo muestra cmo en la sentencias
WriteLine() se realiza una conversin de forma implcita del tipo de la enumeracin, dia, a tipo string.
Si queremos que las constantes se asocien a otro tipo que no sea int o que los valores enteros asociados sean otros,
podemos hacerlo de la forma:
enum dia : byte { lunes = 1, martes, miercoles, jueves, viernes, sabado = 9, domingo };
En este caso el tipo asociado a las constantes es byte y adems lunes se corresponde con 1, martes con 2, mircoles
con 3, jueves con 4, viernes con 5, sbado con 9 y domingo con 10.
5.4 Literales
Un literal es un valor constante que podr ser de algunos de los tipos primitivos, de tipo string (literal de cadena), o
bien la expresin null (referencia nula). Por lo tanto, un literal puede ser: un entero, un real, un valor booleano, un
carcter, una cadena de caracteres o un valor nulo. Todos ellos son valores constantes.
Los literales enteros se pueden expresar en decimal o en hexadecimal. Se le puede aadir un signo ms (+) o menos
(-). El tipo de un literal entero depender de su base, de su valor y de su sufijo.
Si el literal entero no tiene sufijo, su tipo es el primero de los tipos int, uint, long o ulong en el que su valor pueda
ser representado. Los sufijos pueden ser U, L o UL. Si es U, su tipo es el primero de los tipos uint o ulong en el que
su valor pueda ser representado; si es L, su tipo es el primero de los tipos long o ulong y si es UL su tipo es ulong.
Ejemplo:
Los literales enteros expresados en hexadecimal deben ir precedidos por 0x o 0X. Ejemplo:
-17.24
-0.1724E2
-1.724e+01
Las constantes reales son siempre de tipo double, salvo que se le aada el sufijo f o F en cuyo caso sern de tipo
float, o bien el sufijo m o M con lo que sera de tipo decimal. Ejemplos:
23.45e-02F
1234567.00M
Un literal carcter es un slo carcter encerrado entre comillas simples. Se pueden usar secuencias de escape para
representarlos. Ejemplos:
Espacio en blanco
a Letra minscula a
\n Retorno de carro ms avance de lnea (CR+LF)
\0 Carcter nulo (UNICODE \u0000)
El valor de una constante tipo carcter es el valor del cdigo numrico del juego de caracteres usado. As, la
constante A tiene asociado el valor 65 segn el juego de caracteres UNICODE.
Los literales de tipo string, llamados literales de cadena de caracteres o simplemente cadenas de caracteres estn
formados por una secuencia de caracteres, incluidas las secuencias de escape, encerradas entre comillas dobles. Las
cadenas de caracteres son en realidad objetos de tipo string; cada vez que usemos una cadena de caracteres, se crea
un objeto de tipo string con el valor del literal. Ejemplos:
5.5 Identificadores
Los identificadores son los nombres que se dan a variables, clases, estructuras, interfaces, eventos, mtodos,
espacios de nombre, etc. Las reglas respecto a las posibilidades de eleccin de nombres son las siguientes:
En general es aconsejable elegir nombres de mtodos y de variables de forma que permitan conocer a simple vista
qu tipo de funcin o de variable representan, utilizando para ello tantos caracteres como sean necesarios. Esto
simplifica enormemente la tarea de programacin y sobre todo de correccin y mantenimiento de los programas. Es
cierto que los nombres largos son ms laboriosos de teclear, pero en general resulta rentable tomarse esa pequea
molestia. [Tema 0 ConvencinEstilosC#].
En C#, como en cualquier otro lenguaje, existen una serie de palabras clave (keywords) que el usuario no puede
utilizar como identificadores. Estos identificadores predefinidos tienen un significado especial para el compilador.
Una lista de estas palabras se puede obtener de la bibliografa. Importante, siempre se escriben en minsculas, tal
como aparecen.
La declaracin de una constante simblica consiste en dar un nombre o identificador y su valor. Para ello su usa el
calificador const. El formato es:
Una vez se haya declarado una constante, no se le puede asignar otro valor. Ejemplos:
const int iva = 16;
const string despedida = "Hasta luego";
despedida = "Adios"; // Error...
El uso ms frecuente de una constante es para poder modificar ms fcilmente un programa. Si por ejemplo en un
programa utilizamos 20 veces la constante iva de valor 16 y la hemos definido como en el ejemplo anterior, y
pasado un tiempo la legislacin cambia fijando el tipo de IVA general al nuevo valor del 21 %, slo es necesario
modificar una lnea, la de la definicin de la constante y volver a compilar. En cambio, si no hemos declarado iva,
sino que hemos utilizado el valor 16 directamente las 20 veces, tendramos que realizar 20 cambios en el cdigo.
5.8 Variables
Ya sabemos que una variable representa un espacio de memoria para almacenar un valor de un determinado tipo.
Dicho valor, a diferencia de una constante puede cambiar durante la ejecucin de un programa. La declaracin de
una variable se realiza con la siguiente sintaxis:
La declaracin de una variable se puede realizar a nivel de clase (atributos o campos de la clase), a nivel de mtodo
(dentro de la definicin del mtodo) o a nivel de un bloque de cdigo. Dependiendo de dnde se declare, su uso
estar limitado a la clase, al mtodo, o al bloque de cdigo que la define. Es lo que se conoce como mbito de una
variable.
Las variables miembro o campos de una clase pueden ser declaradas en cualquier parte dentro de la clase, siempre
que sea fuera de todo mtodo y estarn disponibles para todo el cdigo de la clase. Las variables declaradas dentro
de un mtodo y los parmetros de ste son locales al mtodo. Las variables declaradas dentro del bloque de una
sentencia compuesta, son locales a dicho bloque.
Una variable local existe y tiene valor desde su punto de declaracin hasta el final del bloque donde est definida.
Las variables locales de un mtodo se definen, es decir, se reserva espacio de memoria, cada vez que se ejecuta un
mtodo y dejan de existir cuando finaliza la ejecucin del mtodo.
Recordar que las variables miembro de una clase son iniciadas por omisin por el compilador, aunque podemos
iniciarlas explcitamente con los valores que indiquemos. Los valores asignados por defecto son 0 para las variables
numricas, los caracteres con \0, los tipos bool con el valor false y las referencias a null. En cambio las variables
locales no son inicializadas por el compilador, siendo esta tarea obligacin del programador.
5.9 Operadores
Un operador es un carcter o grupo de caracteres que acta sobre uno, dos o ms operandos para realizar una
determinada operacin generando un determinado resultado. Ejemplos tpicos de operadores son suma (+),
diferencia (-), producto (*), mayor (>), etc.
Los operadores pueden ser unarios, binarios y ternarios, segn acten sobre uno, dos o tres operandos,
respectivamente. En C# existen muchos operadores de diversos tipos (ste es uno de los puntos fuertes del lenguaje),
que se vern a continuacin.
Suma: +
Resta:
Multiplicacin: *
Divisin: /
Resto o mdulo: %
Los operadores pueden aceptar una mezcla de operandos enteros y reales. Generalmente si ambos operandos son
enteros, el resultado es un entero. Por otro lado, si uno de los operandos es real, el resultado ser real (de tipo double
para ser ms exacto).
Si en una divisin ambos operandos son enteros, la divisin se realiza como una divisin entera, es decir, el
resultado se redondea producindose otro entero. Por ejemplo:
Si queremos realizar una divisin real cuando ambos operadores son enteros, deberamos realizar una conversin
explcita sobre uno de los operandos para que actuara como real:
int sumaNotas = 65, numAlu = 10;
double notaMedia = sumaNotas/(double)numAlu; // da 6.5
El operador mdulo (%) devuelve el resto de la divisin entera. Es decir 13%3 devolver el valor 1. Es posible que
el valor devuelto por una expresin aritmtica sea demasiado grande como para almacenarlo en una variable de un
tipo dado. Esta situacin de desbordamiento (overflow) provoca un error en tiempo de ejecucin de forma que la
mquina virtual lanza una excepcin, que si no es capturada y tratada provoca la interrupcin del programa y la
aparicin de un mensaje de error en la consola. Las excepciones son el mecanismo bsico para el tratamiento de
errores. Las veremos.
byte k;
k = 20 * 45; // Desbordamiento: 900 > 255
Recordar que matemticamente la operacin de divisin por cero no est permitida. El resultado es un error en
tiempo de ejecucin provocando el lanzamiento de una excepcin del tipo division by zero, la cual si no es tratada, el
programa termina en ese momento con el consiguiente mensaje de error.
En el lenguaje natural, existen varias palabras o formas de indicar si se cumple o no una determinada condicin. En
informtica se ha hecho bastante general el utilizar las formas (true, false). Si una condicin se cumple, el resultado
es el valor booleano true; en caso contrario, el resultado es false.
Igual que: ==
Menor que: <
Mayor que: >
Menor o igual que: <=
Mayor o igual que: >=
Distinto que: !=
Todos los operadores relacionales son operadores binarios (tienen dos operandos), y su forma general es la
siguiente:
Los caracteres pueden usarse como operandos, ya que lo que se compara son los valores numricos correspondientes
a su cdigo de representacin. Por ejemplo:
Para las cadenas de caracteres, objetos de tipo string y por tanto de tipo referencia, los operadores de igualdad (== y
!=) se han definido para comparar los valores que referencian los objetos string, en lugar de comparar si dichas
referencias sealan al mismo lugar de memoria. De esta forma la comparacin es lexicogrfica que es lo que
generalmente se pretende al comprobar la igualdad o la desigualdad entre cadenas.
Si se quiere comparar si una cadena es lexicogrficamente menor o mayor que otra, no podemos utilizar los
operadores de relacin; es necesario utilizar mtodos de la clase string para tal fin. Aunque hay varios mtodos, uno
de ellos es el siguiente:
Este mtodo usa como argumentos las dos cadenas a comparar, y retorna un nmero negativo si la primera cadena es
menor que la segunda, un nmero positivo si la primera cadena es mayor que la segunda, y cero si ambas cadenas
son iguales. As, el siguiente ejemplo provoca la salida en pantalla del mensaje: Mayor.
string st1 = "PEPE";
string st2 = "PEPA";
if(String.Compare(st1,st2) > 0)
Console.WriteLine("Mayor");
El operador negacin es un operador unario, el cual niega el valor lgico del operando sobre el que acta. Si el
operando es true da como resultado false, y si es false se devuelve true.
El operador && devuelve true si tanto expresion1 como expresion2 son verdaderas y false en caso contrario; es
decir, si una de las dos expresiones o las dos son falsas el resultado es falso. Por otra parte, el operador || devuelve
true si al menos una de las expresiones es cierta. El operador ^ devuelve true si una de las expresiones es verdadera
y la otra falsa; si las dos expresiones son verdaderas o las dos falsas, el resultado se evala como falso.
Es importante tener en cuenta que el compilador de C# trata de optimizar la ejecucin de estas expresiones, lo cual
puede tener a veces efectos no deseados. Por ejemplo: para que el resultado del operador && sea verdadero, ambas
expresiones tienen que ser verdaderas; si se evala expresion1 y es falsa, ya no hace falta evaluar expresion2, y de
hecho no se evala. Algo parecido pasa con el operador ||: si expresion1 es verdadera, ya no hace falta evaluar
expresion2.
Los operadores && y || se pueden combinar entre s (quizs agrupados entre parntesis), dando a veces un cdigo de
difcil interpretacin. Por ejemplo:
bool r;
r = (2==1) || (-1==-1) // El resultado es true
r = (2==2) && (3==-1) // El resultado es false
r = ((2==2) && (3==3)) || (4==0) // El resultado es true
r = ((6==6) || (8==0)) && ((5==5) && (3==2)) // El resultado es false
Operador Accin
& AND
| OR
^ XOR (OR exclusivo)
Complemento a uno
>> Desplazamiento a la derecha
<< Desplazamiento a la izquierda.
Los operadores AND, OR, XOR y complemento a uno, estn gobernados por la mismas reglas que sus equivalentes
lgicos, excepto en que trabajan bit a bit. El bit uno se considera true y el bit cero false.
Los operandos para los operadores &, | y ^ tienen que ser de un tipo entero o bool, y para los desplazamientos el
primer operando debe ser tipo int, uint, long o ulong y el segundo tipo int.
Los operadores a nivel de bits son frecuentes usados en aplicaciones de controladores de dispositivos. Veamos con
algunos ejemplos los usos ms frecuentes de los operadores a nivel de bits.
El operador & (AND) se suele utilizar para desactivar (poner a cero) los bits de un operando mediante la mscara de
otro operando. Ejemplo: para desactivar los bits 3 y 4 de una variable A, podemos emplear la mscara de un
operando B que tenga los bits 3 y 4 un cero y en los dems bits un uno.
Tambin se puede utilizar para conocer el valor de un determinado bit de un operando, mediante la mscara de otro
operando. Ejemplo: para conocer el valor del bit 3 de una variable A, podemos emplear la mscara de un operando
B que tenga un uno en el bit 3 y ceros en el resto de los bits.
int tira = 0xCC; // 000000000000000000000000 1 1 0 0 1 1 0 0
int mascara = 0x4; // 000000000000000000000000 0 0 0 0 0 1 0 0
int resultado;
resultado = tira & mascara; // 000000000000000000000000 0 0 0 0 0 1 0 0
El operador | (OR) se puede utilizar para activar (poner a uno) los bits de un operando, mediante la mscara de otro
operando. Ejemplo: para activar los bits 5 y 7 de una variable A, podemos emplear la mscara de un operando B
que tenga los bits 5 y 7 a uno y cero en los dems bits.
int tira = 0x8C; // 000000000000000000000000 1 0 0 0 1 1 0 0
int mascara = 0x50; // 000000000000000000000000 0 1 0 1 0 0 0 0
int resultado;
resultado = tira | mascara; // 000000000000000000000000 1 1 0 1 1 1 0 0
El operador ^ (XOR) se puede utilizar para cambiar el valor de los bits de un operando, mediante la mscara de otro
operando. Ejemplo: para cambiar los bits 1 y 3 de una variable A, se puede emplear la mscara de un operando B
que tenga en los bits 1 y 3 un uno, y en los dems bits un cero.
int tira = 0xCC; // 000000000000000000000000 1 1 0 0 1 1 0 0
int mascara = 0x5; // 000000000000000000000000 0 0 0 0 0 1 0 1
int resultado;
resultado = tira ^ mascara; // 000000000000000000000000 1 1 0 0 1 0 0 1
El operador ~ ( NOT o complemento a uno) es unario y cambia los bits de su operando. Ejemplo: para cambiar
todos los bits de una variable A se le aplica el operador ~.
int tira = 0xCC; // 000000000000000000000000 1 1 0 0 1 1 0 0
int resultado;
resultado = ~ tira; // 111111111111111111111111 0 0 1 1 0 0 1 1
Los operadores de desplazamiento, >> y <<, mueven todos los bits de una variable a la derecha o a la izquierda,
segn se especifique. La forma general de una sentencia de desplazamiento a la derecha es:
A medida que se desplazan los bits hacia un extremo se van rellenando con ceros por el extremo opuesto. Los bits
que salen se pierden, y entran ceros. Sin embargo, un desplazamiento a la derecha de un nmero negativo introduce
unos.
Estos operadores son muy utilizados. Es importante entender muy bien por qu los resultados m y n del ejemplo
anterior son diferentes.
El operador de asignacin ms utilizado es (=), que no debe ser confundido con la igualdad matemtica, se utiliza
con el siguiente formato:
variable = expresion;
El funcionamiento es como sigue: se evala expresin y el resultado se deposita en variable, sustituyendo cualquier
otro valor que hubiera en esa posicin de memoria anteriormente. Una posible utilizacin de este operador es como
sigue:
variable = variable + 1;
Desde el punto de vista matemtico este ejemplo no tiene sentido (Equivale a 0 = 1!), pero s lo tiene considerando
que en realidad el operador de asignacin (=) representa una sustitucin; en efecto, se toma el valor de variable
contenido en la memoria, se le suma una unidad y el valor resultante vuelve a depositarse en memoria en la zona
correspondiente al identificador variable, sustituyendo al valor que haba anteriormente.
Existen unas variaciones del operador (=). Estos operadores simplifican algunas operaciones recurrentes sobre una
misma variable. Su forma general es:
variable op= expresin;
donde op representa cualquiera de los operadores (+ , -, *, /, %, &, |, ^, <<, >>). La expresin anterior es equivalente
a:
variable = variable op expresin;
Donde Exp1 debe ser una expresin booleana. El operador ? acta de la siguiente forma: Se evala Exp1 como una
condicin lgica. Si el resultado es true, la Exp2 es evaluada y su resultado se toma como resultado de la expresin
total. En caso contrario, se evala la Exp3, siendo ste el resultado final. Ejemplo:
int a = 1;
int b = 2;
int min;
min = (a < b ? a : b); // min recibe 1
Hay que tener en cuenta que slo una de las dos expresiones (Exp2 o Exp3) es evaluada. Esto hay que tenerlo en
cuenta cuando por ejemplo en estas expresiones se producen cambios en los valores de variables. Ejemplo:
min = (a < b ? a++ : b++);
la variable a es incrementada ya que se evala a++. La variable b mantiene su valor ya que b++ no se evala. Ya
que la operacin condicional es en s misma una expresin, se puede usar como una expresin de otra operacin
condicional, es decir, las expresiones condicionales se pueden anidar una dentro de otras. Por ejemplo:
int a = 1;
int b = 2;
int c = 3;
int min;
min = (a < b ? (a < c ? a : c) : (b < c ? b : c));
El operador devuelve un objeto de la clase System.Type con informacin sobre el tipo de nombreTipo. Esta
informacin se puede consultar a travs de las propiedades de los objetos de la clase Type, incluyendo detalles tales
como cules son sus miembros, cul es su tipo padre o a qu espacio de nombres pertenece.
El significado es el siguiente: se evala expresin. Si el resultado de sta es del tipo o compatible con el tipo cuyo
nombre se indica en nombreTipo se devuelve true; y si no, devuelve false. Este operador se suele utilizar con
mtodos polimrficos. Veamos un ejemplo:
using System;
using System.Collections.Generic;
using System.Text;
namespace OperTipos
{
class MiClase
{ }
class Program
{
static void Main(string[] args)
{
Type t1, t2;
t1 = typeof(byte);
t2 = typeof(MiClase);
Console.WriteLine(t1.BaseType); // Clase base del tipo byte
Console.WriteLine(t2.Namespace); // Espacio de nombres de MiClase
MiClase mc = new MiClase();
string s = "hol";
Console.WriteLine(mc is MiClase); // evidente
//se puede concatenar un string con un char obteniendo un string?
Console.WriteLine(s + 'a' is String); // pues s.
}
}
}
c * d se evala primero ya que el operador * tiene mayor prioridad que los operadores + y ==. El resultado se suma
a b ya que el operador + tiene mayor prioridad que ==. Por ltimo se evala el operador ==.
Una expresin entre parntesis siempre se evala primero. Los parntesis tienen mayor prioridad y son evaluados
desde los ms internos a los ms externos.
Como regla general decir que el orden de prioridad de mayor a menor es: operadores aritmticos, operadores
relacionales, operadores lgicos y por ltimo los operadores de asignacin. Respecto a las reglas de asociatividad
decir que todos los operadores asocian de izquierda a derecha, excepto los operadores unarios, el operador ternario y
el de asignacin.
La tabla siguiente resume las reglas de prioridad y asociatividad de todos los operadores. Las filas se han colocado
de mayor a menor prioridad. Los operadores escritos sobre una misma lnea tienen la misma prioridad.
As, por ejemplo, para poder sumar dos variables hace falta que ambas sean del mismo tipo. Si una es int y otra
float, el valor de la primera se convierte a float (es decir, el valor de la variable del tipo de menor rango se convierte
al tipo de mayor rango), antes de realizar la operacin. A esta conversin automtica e implcita de tipo (el
programador no necesita intervenir, aunque s conocer sus reglas), se le denomina conversin implcita o promocin,
pues la variable de menor rango es promocionada al rango de la otra.
En el caso de una asignacin, se convierte el valor de la derecha al tipo de la variable de la izquierda siempre que no
haya prdida de informacin; en otro caso el compilador presenta un mensaje de error. Si queremos realizar de todas
formas dicha conversin, aunque se pierda informacin, debemos realizar una conversin explcita o forzada. Esta
conversin, denomina tambin casting, se implementa de la forma:
(tipo) expresin
El resultado anterior es que una vez evaluada expresin, se convierte dicho valor resultante al tipo que hayamos
sealado entre parntesis.
La figura siguiente resume los tipos primitivos principales colocados de izquierda a derecha de menos a ms
precisos. Las flechas indican las conversiones implcitas permitidas.
Las conversiones en una expresin se realizan operacin a operacin siguiendo el orden de prioridad de los
operadores. Por ejemplo, en el siguiente fragmento de cdigo:
...
double valor;
char car = 'a';
int i = 2;
float f = 1.1F;
double d = 0.0001;
valor = (car / i) - (f * d) + (f / i);
En la primera operacin (car / i) el valor del cdigo UNICODE del carcter a (97), se convierte a tipo int ya que la
constante 2 es de tipo int. La divisin de enteros da como resultado otro entero, en este caso el resultado de esta
divisin es 48.
En el ejemplo, la variable valor est declarada como double, por lo que en la asignacin de la expresin no habra
conversin de tipo. Qu hubiera pasado si la variable valor se hubiera declarado de tipo int?. Pues que el resultado
se hubiera tenido que convertir de double a int, proceso en el que se producira prdida de informacin generndose
un error de compilacin. Si de todas formas deseamos realizar la conversin debera hacerse de forma explcita. En
el siguiente ejemplo,
la variable masa siempre ser de tipo double. Con el casting se logra que en la expresin slo se opere con la parte
entera de la variable. La constante 1.7 (que es de tipo double) intervendr en la expresin slo con su valor entero (1
en este caso). En este ejemplo
char letra = 'A';
letra = (char)(1 + letra);
char minuscula = (char)(letra + ('a' - 'A'));
Las conversiones tambin se pueden realizar entre otros objetos que no sean de tipo primitivo. Normalmente se
realizan entre objetos que pertenecen a una misma jerarqua de clases. Por ejemplo, cualquier objeto de la clase que
sea, siempre se puede convertir de forma implcita a tipo Object, ya que esta clase es la clase base para cualquier
objeto. En general una conversin implcita, la permitida directamente, se puede realizar a un tipo base o padre del
tipo del objeto a convertir. Ejemplo:
string s = "hola";
Object ob = s; // Conversin implcita.
Las conversiones explcitas entre objetos tambin se pueden hacer, siempre que el compilador pueda realizar dicha
conversin, ya que si no es as, se lanzara una excepcin durante la ejecucin del tipo InvalidCast.
Existe otro operador que permite realizar operaciones de conversin de forma muy similar al ya visto. Este operador
es el operador as, que se usa as:
expresion as tipoDestino
La forma de operar es la siguiente: si la expresin es convertible a tipoDestino, se realiza la conversin, siendo sta
el resultado; si la conversin no es posible el resultado es null. El uso del operador as sera equivalente a la siguiente
expresin:
Como vemos si expresin es del tipo o compatible con tipoDestino, se realiza la conversin de forma explcita y se
retorna sta; si no es posible la conversin, se retorna null.
El operador as slo es aplicable a tipos referencia y a aquellos casos en que existan conversiones predefinidas en el
lenguaje. Como veremos en otro tema, esto slo incluye conversiones entre un tipo y tipos padres suyos y entre un
tipo y tipos hijos suyos; es decir, entre tipos pertenecientes a una jerarqua de clases.
. . .
string s = "hol";
Console.WriteLine(s + 'a' is String);
La salida en pantalla era true ya que is evala si la expresin es de tipo string o compatible con el tipo string. Si
aadimos la sentencia siguiente:
Console.WriteLine(s + 'a' as String);
La salida en pantalla sera hola ya que al ser la expresin compatible con el tipo string, se ha retornado el valor del
resultado de dicha conversin.
Sentencia if
Esta sentencia de control permite ejecutar o no una sentencia simple o compuesta segn se cumpla o no una
determinada condicin. Esta sentencia tiene la siguiente forma general:
if (expresin booleana)
sentencia;
Sentencia if-else
Esta sentencia permite realizar una bifurcacin, ejecutando una parte u otra del programa segn se cumpla o no
una cierta condicin. La forma general es la siguiente:
if (expresin booleana)
sentencia_1;
else
sentencia_2;
Se evala expresin. Si el resultado es verdadero (true), se ejecuta sentencia_1 y si el resultado es falso (false),
se ejecuta sentencia_2. En cualquier caso la ejecucin contina en la siguiente sentencia ejecutable que haya a
continuacin de la sentencia if.
Las sentencias incluidas en la parte if o en la parte else pueden ser tambin sentencias de tipo if o de tipo if-
else. Esto se conoce como anidamiento. Por ejemplo:
if (valor > 0)
if (x > y)
mayor = x;
else
mayor = y;
Como vemos, el anidamiento puede mostrar confusin, ya que en principio podemos dudar en determinar cual
es el if correspondiente a un else. La regla es que cada else se corresponde con el if ms prximo que no est
asociado ya con un else. En el ejemplo el else se corresponde con el segundo if (x >y).
Si la lgica de la solucin impusiera que el else del ejemplo anterior se correspondiera con el primero de los if,
(if (valor>0 )), tenemos dos soluciones. La primera sera forzando a que cada if tenga un else:
if (valor > 0)
if (x > y)
mayor = x;
else
;
else
mayor = y;
Hemos forzado al if interior con un else colocando una sentencia vaca (;). La otra solucin es crear un bloque
que separe el else del if no deseado. Ahora el else no tiene conocimiento del if(x>y), ya que pertenecen a
bloques distintos. Pero lo mejor siempre consiste en hacer lo requerido de la forma ms sencilla.
Sentencia if-else mltiple.
Esta sentencia permite realizar una ramificacin mltiple, ejecutando una entre varias partes del programa
segn se cumpla una entre varias condiciones. La forma general es la siguiente:
if (expresion_1)
sentencia_1;
else if (expresion_2)
sentencia_2;
else if (...)
...
[else
sentencia_n;]
using System;
using System.Collections.Generic;
using System.Text;
namespace prueba
{
class Program
{
static void Main(string[] args)
{
int valor;
Console.Write("Teclee un nmero entero entre 0 y 2: ");
valor = Int32.Parse(Console.ReadLine());
if (valor == 0)
Console.WriteLine("Puls cero");
else if (valor == 1)
Console.WriteLine("Puls uno");
else if (valor == 2)
Console.WriteLine("Puls dos");
else
Console.WriteLine("Fuera de rango");
Console.ReadLine();
}
}
}
Sentencia switch
La sentencia que se va a describir a continuacin desarrolla una funcin similar a la de la sentencia if ... else
con mltiples ramificaciones. Esta sentencia permite ejecutar una de varias acciones, en funcin del valor de
una expresin. Su sintaxis es:
switch (expresion)
{
case expresion_cte_1:
[sentencia_1;]
case expresion_cte_2:
[sentencia_2;]
...
case expresion_cte_n:
[sentencia_n;]
[default:]
[sentencia;]
donde expresin es una expresin de tipo entero, enumerado o string y las expresiones_constantes es una
constante del mismo tipo que expresin o de un tipo que se pueda convertir implcitamente al tipo de
expresin; las sentencias pueden ser simples o compuestas. En el caso de que se trate de una sentencia
compuesta, no hace falta incluir entre llaves las sentencias simples que la forman.
La sentencia switch evala expresin y compara su valor con las constantes de cada case de la siguiente forma:
si dicho valor coincide con el valor constante expresion_cte_1, se ejecuta sentencia_1, y contina hasta una
sentencia que transfiera el control fuera o dentro del bloque switch; esta sentencia debe estar presente por cada
case as como para default. Generalmente se usa break para transferir el control fuera del bloque de la
sentencia switch. Si el resultado coincide con el valor constante expresion_cte_2, se ejecuta sentencia_2 hasta
encontrar una sentencia que transfiera el control fuera o dentro del bloque switch. Este mecanismo se repite
para todos los case que aparezcan. Si ninguna expresion_cte coincide con el valor de expresin, se ejecuta la
sentencia que est a continuacin de default si esta clusula ha sido especificada.
La sentencia switch muchas veces produce un cdigo ms legible que si hubiramos usado una estructura else-
if equivalente. As el ejemplo anterior se puede reescribir usando una sentencia switch :
switch (valor)
{
case 0:
Console.WriteLine("Puls cero");
break;
case 1:
Console.WriteLine("Puls uno");
break;
case 2:
Console.WriteLine("Puls dos");
break;
default:
Console.WriteLine("Fuera de rango");
break;
}
Se puede tener un switch formando parte de la secuencia de sentencias de otro switch. Incluso si las constantes
case del switch interior y del exterior contienen valores comunes, no aparecen conflictos, como por ejemplo:
switch(x)
{
case 1:
switch(y)
{
case 0:
Console.WriteLine("Error de divisin por cero");
break;
case 1:
dividir(x,y);
break;
}
break;
case 2:
......
Si se quiere ejecutar la misma sentencia para varios valores del resultado de la expresin, se puede hacer
poniendo varios case expresion_cte seguidos. Ejemplo:
switch (valor)
{
case 0:
case 1:
Console.WriteLine("Menor que 2");
break;
case 2:
Console.WriteLine("Igual a 2");
break;
default:
Console.WriteLine("Fuera de rango");
break;
Sentencia while
Esta sentencia permite ejecutar repetidamente, mientras se cumpla una determinada condicin, una sentencia o
bloque de sentencias. La forma general es como sigue:
while (condicin)
sentencia;
Los bucles while normalmente se utilizan cuando no se conoce con exactitud el nmero de iteraciones que se
deben realizar. Una manera de terminar la ejecucin de un bucle while cuando el usuario debe introducir una
secuencia de datos, es usar un valor centinela como ltimo dato. La condicin del bucle compara cada dato con
el valor centinela y termina cuando el usuario teclea el valor centinela. Hay que tener cuidado en elegir
adecuadamente el valor centinela.
Los conmutadores, banderas o flags tambin permiten controlar el nmero de iteraciones de un bucle while. El
conmutador se inicializa a un valor, el cual se mantendr hasta que ocurra un suceso cambindose a otro valor.
El bucle se ejecuta mientras el conmutador no cambie de valor. Por ejemplo, calcular el valor de la funcin f(x)
= log(x) donde x debe ser mayor que cero.
using System;
using System.Collections.Generic;
using System.Text;
namespace BucleWhile2
{
class Program
{
static void Main(string[] args)
{
double l, x = 0;
bool positivo = false; // Conmutador inicializado
while (!positivo) // Mientras no cambie el mismo
{
Console.Write("Valor para x: ");
x = Double.Parse(Console.ReadLine());
positivo = (x > 0); // Cambiar a true si x es positivo
}
l = Math.Log10(x);
Console.WriteLine("Log({0}) = {1:f6}", x, l);
Console.ReadLine();
}
}
}
do
sentencia;
while(condicin);
namespace CicloDoWhile
{
class Program
{
static void Main(string[] args)
{
char letra;
int con = 0;
do
{
con++;
Console.Write("Introduzca una letra [A-F]: ");
letra = (char)Console.Read();
Console.ReadLine(); // Elimina \r\n del buffer
// o bien podemos hacer
// letra = Char.Parse(Console.ReadLine());
} while ((letra < 'A') || (letra > 'F'));
if (con == 1)
Console.WriteLine("Muy bien Carlos, muy bien...a la primera");
else
Console.WriteLine("Vale. A ver si nos fijamos...");
Console.ReadLine();
}
}
}
Los usos ms frecuentes del bucle do-while coinciden con los del bucle while. La diferencia es que con un
bucle do-while nos aseguramos que el bucle se ejecuta al menos una vez, ya que la condicin se evala al final.
Sentencia for
for es quizs el tipo de bucle mas verstil y utilizado del lenguaje C#. Permite ejecutar una sentencia
repetidamente un nmero determinado de veces. Su forma general es la siguiente:
Posiblemente la forma ms sencilla de explicar la sentencia for sea utilizando la construccin while que sera
equivalente. Dicha construccin es la siguiente:
inicializacin;
while (condicin)
{
sentencia;
actualizacin;
}
Antes de iniciarse el bucle se ejecuta inicializacin, que es una o ms sentencias que asignan valores iniciales a
ciertas variables o contadores. A continuacin se evala condicin; si es falsa, la ejecucin prosigue en la
sentencia siguiente a la construccin for; si es verdadera, se ejecutan sentencia y actualizacin, y se vuelve a
evaluar condicin. El proceso prosigue hasta que condicin sea falsa.
Normalmente la parte de actualizacin sirve para actualizar variables o incrementar contadores. Un ejemplo
tpico puede ser la suma de los 10 primeros nmeros naturales:
Primeramente se inicializa la variable s a cero y la variable i a 1; el ciclo se repetir mientras que i sea menor o
igual que 10, y al final de cada ciclo el valor de i se incrementar en una unidad. En total, el bucle se repetir
10 veces.
La ventaja de la construccin for sobre la construccin while equivalente est en que en la cabecera de la
construccin for se tiene toda la informacin sobre como se inicializan, controlan y actualizan las variables del
bucle.
El bucle for implementado en C# es mucho ms potente que su equivalente en otros lenguajes. Realmente,
todas las sentencias que forman parte del bucle, lo que hemos llamado inicializacin, condicin y
actualizacin, pueden ser cualquier sentencia vlida de C#. Adems las tres partes anteriores no son
obligatorias de usar. Veamos unos ejemplos:
for(int x = 0; x != 123;)
x = Int32.Parse(Console.ReadLine()); // Se ejecuta hasta que se teclea 123
char car;
for (car = 'z'; car >= 'a'; car--)
{
Console.WriteLine(car);
if (car == 'o')
Console.WriteLine('');
}
En el siguiente ejemplo se presenta en pantalla un contador que desciende hasta 1 y que empieza por el valor
correspondiente al nmero de caracteres de una cadena previamente leda desde teclado.
string s = Console.ReadLine(); // Se lee una cadena
int l = s.Length; // Obtiene la longitud de la cadena
for (; l>0; )
{
Console.WriteLine(l);
l--;
}
Uno de los posibles usos del bucle for, aunque nada recomendable, es la de poder construir bucles infinitos.
Esto se puede conseguir dejando la expresin condicional vaca. Por ejemplo:
for (;;)
{
c = Char.Parse(Console.ReadLine());
if (c == 'A')
break; // Salir del bucle
}
Una sentencia, segn la sintaxis de C#, puede estar vaca. Esto supone que el cuerpo de un bucle pueda estar
vaco.
for (t = 0; t < VALOR; t++); // Crea un bucle de retardo temporal.
En este ejemplo se leen caracteres de teclado hasta que se pulsa 's' o 'n':
char c;
for (c = Char.Parse(Console.ReadLine());
c != 's' && c != 'n';
c = Char.Parse(Console.ReadLine()));
Sentencia foreach
Permite repetir un grupo de sentencias para cada elemento de un array o de una coleccin. Esta sentencia la
usaremos cuando estudiemos dichas estructuras de datos.
El siguiente ejemplo obtiene por pantalla la tabla de multiplicar del 1 al 10. Para ello se usan dos bucles. El bucle
que usa la variable x como variable de control se denomina bucle externo y el bucle con la variable de control y se
llama bucle interno. Para cada valor de x del bucle externo, se ejecuta el bucle interno iterando todos los valores de
y, y se realiza un salto de lnea para separar cada una de las 10 tablas.
using System;
using System.Collections.Generic;
using System.Text;
namespace BucleAnidado1
{
class Program
{
static void Main(string[] args)
{
const int MAX = 10;
int x, y, p;
for (x = 1; x <= MAX; x++)
{
for (y = 1; y <= MAX; y++)
{
p = x * y;
Console.WriteLine("{0,2} * {1,2} = {2,2}", x, y, p);
}
Console.WriteLine();
}
Console.ReadLine();
}
}
}
El siguiente ejemplo dibuja en pantalla un tringulo issceles usando un carcter como relleno
using System;
using System.Collections.Generic;
using System.Text;
namespace BucleAnidado2
{
class Program
{
static void Main(string[] args)
{
const int NUMLIN = 5;
const char SIMBOLO = '*';
const char BLANCO = ' ';
int fila, con_bla, con_sim;
Console.Clear();
Console.WriteLine();
for (fila = 1; fila <= NUMLIN; fila++)
{
for (con_bla = NUMLIN - fila; con_bla > 0; con_bla--)
Console.Write("\t{0}", BLANCO);
for (con_sim = 1; con_sim < 2 * fila; con_sim++)
Console.Write("\t{0}", SIMBOLO);
Console.WriteLine();
}
Console.ReadLine();
}
}
}
*
* * *
* * * * *
* * * * * * *
* * * * * * * * *
fil = 5;
do
{
col = fil;
do
{
Console.Write(col);
col--;
} while (col > 0);
Console.WriteLine();
fil--;
} while (fil > 0);
Console.ReadLine();
}
}
}
La sentencia continue obliga a ejecutar la siguiente iteracin del bucle donde se halla, aunque no haya terminado de
ejecutar las sentencias que incluye. Al comenzar el siguiente ciclo del bucle, se evala la condicin que controla el
bucle. En el caso que se use dentro de un bucle for, se ejecuta la parte de incremento/decremento del bucle, para
posteriormente evaluar la condicin que controla el bucle. Ejemplo:
using System;
namespace SaltoContinue
{
class Program
{
static void Main(string[] args)
{
int x;
do
{
x = Int32.Parse(Console.ReadLine());
if (x <= 0)
continue;
Console.WriteLine("Tecle el positivo " + x);
} while (x != 100);
}
}
}
En C# viene predefinido el comportamiento de sus operadores cuando se aplican a ciertos tipos de datos. Por
ejemplo, si se aplica el operador + entre dos objetos int devuelve su suma, y si se aplica entre dos objetos string
devuelve su concatenacin. Sin embargo, tambin se permite que el programador pueda definir el significado la
mayora de estos operadores cuando se apliquen a objetos cuyos tipos haya definido l. Esto es lo que se le conoce
como redefinicin de operador.
La posibilidad de redefinir un operador no aporta ninguna nueva funcionalidad al lenguaje y slo se ha incluido en
C# para facilitar la legibilidad del cdigo. Por ejemplo, si tenemos una clase Complejo que representa nmeros
complejos podramos definir un mtodo sumar() para sus objetos de modo que a travs de l se pudiese conseguir la
suma de dos objetos de esta clase como muestra este ejemplo:
Sin embargo, el cdigo sera mucho ms legible e intuitivo si en vez de tenerse que usar el mtodo sumar() se
redefiniese el significado del operador + para que al aplicarlo entre objetos Complejo devolviese su suma. Con ello,
el cdigo anterior quedara as:
Complejo c1 = new Complejo(3,2); // c1 = 3 + 2i
Complejo c2 = new Complejo(5,2); // c2 = 5 + 2i
Complejo c3 = c1 + c2; // c3 = 8 + 4i
sta es precisamente la utilidad de la redefinicin de operadores: hacer ms claro y legible el cdigo, no hacerlo ms
corto. Por tanto, cuando se redefina un operador es importante que se le d un significado intuitivo ya que si no
fuese as, ira contra de la filosofa de la redefinicin de operadores.
Los modificadores public y static pueden permutarse si se desea, lo que es importante es que siempre aparezcan en
toda redefinicin de operador. Se pueden redefinir tanto operadores unarios como binarios, y en <operandos> se
ha de incluir tantos parmetros como operandos pueda tomar el operador a redefinir, ya que cada uno representar a
uno de sus operandos. Por ltimo, en <cuerpo> se han de escribir las instrucciones a ejecutar cada vez que se
aplique la operacin cuyo operador se identifica con <smbolo> a los operandos indicados en <operandos>.
<tipoDevuelto> no puede ser void, pues por definicin toda operacin tiene un resultado, por lo que todo
operador ha de devolver algo. Adems, los operadores no pueden redefinirse con total libertad ya que ello
dificultara innecesariamente la legibilidad del cdigo, por lo que se han introducido las siguientes restricciones al
redefinirlos:
Al menos uno de los operandos ha de ser del mismo tipo de dato del que sea miembro la redefinicin del
operador.
No puede alterarse sus reglas de precedencia, asociatividad, ubicacin y nmero de operandos, pues si ya de
por s es difcil para muchos recordarlas cuando son fijas, mucho ms lo sera si pudiesen modificarse segn
los tipos de sus operandos.
No pueden definirse nuevos operadores ni combinaciones de los ya existentes con nuevos significados (por
ejemplo ** para representar exponenciacin), pues ello complicara innecesariamente el compilador, el
lenguaje y la legibilidad del cdigo cuando en realidad es algo que puede simularse definiendo mtodos.
No todos los operadores incluidos en el lenguaje pueden redefinirse, pues muchos de ellos (como ., new,
=, etc.) son bsicos para el lenguaje y su redefinicin es inviable, poco til o dificultara innecesariamente
la legibilidad del cdigo. Adems, no todos los redefinibles se redefinen usando la sintaxis general hasta
ahora vista. Cuando se necesite, se comentarn cules son las peculiaridades de aquellos que requieran una
redefinicin especial.
Hay que tener en cuenta que aquellos operadores que tengan complementario, siempre han de redefinirse junto con
ste. As, siempre que se redefina el operador > tambin ha de redefinirse en l el operador <, siempre que se
redefina >= ha de redefinirse <=, y siempre que se redefina == ha de redefinirse !=.
Tambin hay que sealar que, como puede deducirse de la lista de operadores binarios redefinibles, no se pueden
redefinir directamente ni el operador de asignacin = ni los operadores compuestos (+=, -=, etc.) Sin embargo, en el
caso de estos ltimos dicha redefinicin ocurre de manera automtica al redefinir su parte no = Es decir, al
redefinir + quedar redefinido consecuentemente +=, al redefinir * lo har *=, etc.
public Complejo() { }
Es fcil ver que lo que en el ejemplo se ha redefinido es el significado del operador + para que cuando se aplique
entre dos objetos de clase Complejo devuelva un nuevo objeto Complejo cuyas partes real e imaginaria sean la suma
de las de sus operandos.
Los operadores ++ y -- siempre han de redefinirse de manera que el tipo de dato del objeto devuelto sea igual al del
tipo en donde se define el operador. Cuando se usen de forma postfija, lo que har el compilador ser devolver el
objeto original que se les pas como parmetro en lugar del indicado en la sentencia return; y cuando se usen de
forma prefija se devolver el objeto creado y devuelto por return. Por ello es importante no modificar dicho
parmetro si es de un tipo referencia y queremos que estos operadores tengan su significado tradicional. Un ejemplo
de cmo hacerlo es la siguiente redefinicin de ++ para el tipo Complejo.
public static Complejo operator ++ (Complejo op)
{
Complejo resultado = new Complejo(op.ParteReal + 1, op.ParteImaginaria);
return resultado;
}
El siguiente cdigo hace uso de la clase Complejo mostrando cmo se usa la sobrecarga:
class PruebaComplejo
{
static void Main(string[] args)
{
Complejo c1 = new Complejo(1, 2);
Complejo c2 = new Complejo(3, 4);
Complejo c3 = c1 + c2;
c3.imprimirComplejo(); // 4+6i
Complejo c4, c5;
c4 = c1++; // Se retorna el original c1
c1.ImprimirComplejo(); // 2+2i
c4.ImprimirComplejo(); // 1+2i
c5 = ++c2; // Se retorna el c2 modificado
c2.ImprimirComplejo(); // 4+4i
c5.ImprimirComplejo(); // 4+4i
}
}
Respecto a los operadores true y false, estos indican respectivamente, cuando se ha de considerar que un objeto
representa el valor lgico cierto y cuando se ha de considerar que representa el valor lgico falso, por lo que sus
redefiniciones siempre han de devolver un objeto de tipo bool que indique dicha situacin. Adems, si se redefine
uno es obligatorio redefinir tambin el otro, pues siempre es posible usar indistintamente uno u otro para determinar
el valor lgico que un objeto de ese tipo represente.
En realidad los operadores true y false no pueden usarse directamente en el cdigo fuente, sino que redefinirlos para
un tipo de dato es til porque permiten utilizar objetos de ese tipo en expresiones condicionales tal y como si de un
valor lgico se tratase. Por ejemplo, podemos redefinir estos operadores en el tipo Complejo de modo que
consideren cierto a todo complejo distinto de 0 + 0i y falso a 0 + 0i:
public static bool operator true(Complejo op)
{
return (op.ParteReal != 0 || op.ParteImaginaria != 0);
}
Con estas redefiniciones, un cdigo como el que sigue mostrara por pantalla el mensaje Es cierto:
Complejo c1 = new Complejo(1, 0); // c1 = 1 + 0i
if (c1) Console.WriteLine(Es cierto);
(<tipoDestino>) <expresin>
Lo que este operador hace es devolver el objeto resultante de convertir al tipo <tipoDestino> el objeto resultante
de evaluar <expresin>. Para que la conversin pueda aplicarse es preciso que exista alguna definicin de cmo
se ha de convertir a <tipoDestino> los objetos del tipo resultante de evaluar <expresin>.
La sintaxis que se usa para hacer redefinir una operador de conversin es parecida a la usada para cualquier otro
operador slo que no hay que darle nombre, toma un nico parmetro y hay que preceder la palabra reservada
operator con las palabras reservadas explicit o implicit segn se defina la conversin como explcita o implcita. Por
ejemplo, para definir una conversin implcita de Complejo a float podra ser:
public static implicit operator float(Complejo op)
{
return op.ParteReal;
}
Vemos como el tipo del parmetro usado al definir la conversin se corresponde con el tipo de dato del objeto al que
se puede aplicar la conversin (tipo origen), mientras que el tipo del valor devuelto ser el tipo al que se realice la
conversin (tipo destino) Con esta definicin podran escribirse cdigos como el siguiente:
Complejo c1 = new Complejo(5,2); // c1 = 5 + 2i
float f = c1; // f = 5
Ntese que en la conversin de Complejo a float se pierde informacin (la parte imaginaria), por lo que sera mejor
definir la conversin como explcita sustituyendo en su definicin la palabra reservada implicit por explicit. En ese
caso, el cdigo anterior habra de cambiarse por:
Complejo c1 = new Complejo(5,2); // c1 = 5 + 2i
float f = (float) c1; // f = 5
Por otro lado, si lo que hacemos es redefinir la conversin de float a Complejo de forma implcita se hara:
public static implicit operator Complejo(float op)
{
return (new Complejo(op, 0));
}
En este caso nunca se perder informacin y la conversin nunca fallar, por lo que es perfectamente vlido
definirla como implcita. Adems, redefiniendo conversiones implcitas puede conseguirse que los tipos definidos
por el usuario puedan inicializarse directamente a partir de valores literales tal y como si fuesen tipos bsicos del
lenguaje.