Documente Academic
Documente Profesional
Documente Cultură
PARTE I
Elaborado por:
Ing. Andrés Chalá
INTRODUCCION
C# es el nuevo lenguaje de propósito general orientado a objetos creado por Microsoft para
su nueva plataforma .NET.
Microsoft.NET es el conjunto de nuevas tecnologías en las que Microsoft ha estado
trabajando estos últimos años con el objetivo de mejorar tanto su sistema operativo como su
modelo de componentes (COM) para obtener una plataforma con la que sea sencillo el
desarrollo de software en forma de servicios web.
El acceso a estos servicios se realiza en base a estándares de Internet, como son diferentes
mecanismos del protocolo HTTP (GET y PUT) o el novedoso protocolo RPC conocido como
SOAP (Simple Access Object Protocol), que no es más que una combinación de estándares
como HTTP y XML para realizar llamadas a los miembros de estos servicios web. La idea
detrás de SOAP consiste sencillamente en utilizar HTTP como medio de transporte para el
envío de los mensajes de solicitud de ejecución de los miembros de servicios web remotos
(lo que permite atravesar barreras tales como firewalls) y utilizar XML como lenguaje con el
que escribir los cuerpos de estos mensajes.
Pero la plataforma .NET no son sólo los servicios web, sino que también ofrece numerosos
servicios a las aplicaciones que para ella se escriban, como son un recolección de basura,
independencia de la plataforma, total integración entre lenguajes (por ejemplo, es posible
escribir una clase en C# que derive de otra escrita en Visual Basic.NET que a su vez derive
de otra escrita en Cobol)
Como se deduce del párrafo anterior, es posible programar la plataforma .NET en
prácticamente cualquier lenguaje, pero Microsoft ha decidido sacar uno nuevo porque ha
visto conveniente poder disponer de un lenguaje diseñado desde cero con vistas a ser
utilizado en .NET, un lenguaje que no cuente con elementos heredados de versiones
anteriores e innecesarios en esta plataforma y que por tanto sea lo más sencillo posible para
programarla aprovechando toda su potencia y versatilidad.
C# combina los mejores elementos de múltiples lenguajes de amplia difusión como C++,
Java, Visual Basic o Delphi. De hecho, su creador Anders Heljsberg fue también el creador
de muchos otros lenguajes y entornos como Turbo Pascal, Delphi o Visual J++. La idea
principal detrás del lenguaje es combinar la potencia de lenguajes como C++ con la sencillez
de lenguajes como Visual Basic, y que además la migración a este lenguaje por los
porgramadores de C/C++/Java sea lo más inmediata posible.
Módulo 1: Revisión de la sintaxis de C#
Aplicación básica ¡Hola Mundo!
Básicamente una aplicación en C# puede verse como un conjunto de uno o más ficheros
de código fuente con las instrucciones necesarias para que la aplicación funcione como se
desea y que son pasados al compilador para que genere un ejecutable. Cada uno de estos
ficheros no es más que un fichero de texto plano escrito usando caracteres Unicode y
siguiendo la sintaxis propia de C#.
Como primer contacto con el lenguaje, nada mejor que el típico programa de iniciación
“¡Hola Mundo!” que lo único que hace al ejecutarse es mostrar por pantalla el mensaje
¡Hola Mundo! Su código es:
1: class HolaMundo
2: {
3: static void Main()
4: {
5: System.Console.WriteLine("¡Hola Mundo!");
6: }
7: }
Dentro de la definición de la clase (línea 3:) se define un método de nombre Main cuyo
código es el indicado entre la llave de apertura de la línea 4: y su respectiva llave de cierre
(línea 6:) Un método no es más que un conjunto de instrucciones a las que se les asocia un
nombre, de modo que para posteriormente ejecutarlas baste referenciarlas por su nombre en
vez de tener que rescribirlas.
La partícula que antecede al nombre del método indica cuál es el tipo de valor que se
devuelve tras la ejecución del método, y en este caso es void que significa que no se devuelve
nada. Por su parte, los paréntesis colocados tras el nombre del método indican cuáles son los
parámetros que éste toma, y el que estén vacíos significa que el método no toma ninguno.
Los parámetros de un método permiten modificar el resultado de su ejecución en función de
los valores que se les dé en cada llamada.
Finalmente, la línea 5: contiene la instrucción con el código a ejecutar, que lo que se hace
es solicitar la ejecución del método WriteLine() de la clase Console definida en el espacio de
nombres System pasándole como parámetro la cadena de texto con el contenido ¡Hola
Mundo! Nótese que las cadenas de textos son secuencias de caracteres delimitadas por
comillas dobles aunque dichas comillas no forman parte de la cadena. Por su parte, un espacio
de nombres puede considerarse que es para las clases algo similar a lo que un directorio es
para los ficheros: una forma de agruparlas.
El método WriteLine() se usará muy a menudo en los próximos temas, por lo que es
conveniente señalar ahora que una forma de llamarlo que se utilizará en repetidas ocasiones
consiste en pasarle un número indefinido de otros parámetros de cualquier tipo e incluir en
el primero subcadenas de la forma {i}. Con ello se consigue que se muestre por la ventana
de consola la cadena que se le pasa como primer parámetro pero sustituyéndole las
subcadenas {i} por el valor convertido en cadena de texto del parámetro que ocupe la
posición i+2 en la llamada a WriteLine(). Por ejemplo, la siguiente instrucción mostraría
Tengo 5 años por pantalla si x valiese 5:
Para indicar cómo convertir cada objeto en un cadena de texto basta redefinir su
método ToString(), aunque esto es algo que no se verá hasta el Tema 5: Clases.
Antes de seguir es importante resaltar que C# es sensible a las mayúsculas, los que significa
que no da igual la capitalización con la que se escriban los identificadores. Es decir, no es lo
mismo escribir Console que COnsole o CONSOLE, y si se hace de alguna de las dos últimas
formas el compilador producirá un error debido a que en el espacio de nombres System no
existe ninguna clase con dichos nombres. En este sentido, cabe señalar que un error común
entre programadores acostumbrados a Java es llamar al punto de entrada main en vez de
Main, lo que provoca un error al compilar ejecutables en tanto que el compilador no detectará
ninguna definición de punto de entrada.
Puntos de entrada
Ya se ha dicho que el punto de entrada de una aplicación es un método de nombre Main que
contendrá el código por donde se ha de iniciar la ejecución de la misma. Hasta ahora sólo se
ha visto una versión de Main() que no toma parámetros y tiene como tipo de retorno void,
pero en realidad todas sus posibles versiones son:
static void Main()
Como se ve, hay versiones de Main() que devuelven un valor de tipo int. Un int no es más
que un tipo de datos capaz de almacenar valor enteros comprendidos entre –2.1471483.648
y 2.1471483.647, y el número devuelto por Main() sería interpretado como código de retorno
de la aplicación. Éste valor suele usarse para indicar si la aplicación a terminado con éxito
(generalmente valor 0) o no (valor según la causa de la terminación anormal), y en el Tema
8: Métodos se explicará como devolver valores.
También hay versiones de Main() que toman un parámetro donde se almacenará la lista de
argumentos con los que se llamó a la aplicación, por lo que sólo es útil usar estas versiones
del punto de entrada si la aplicación va a utilizar dichos argumentos para algo. El tipo de este
parámetro es string[], lo que significa que es una tabla de cadenas de texto (en el Tema 5:
Claes se explicará detenidamente qué son las tablas y las cadenas), y su nombre -que es el
que habrá de usarse dentro del código de Main() para hacerle referencia- es args en el
ejemplo, aunque podría dársele cualquier otro
Una vez escrito el código anterior con algún editor de textos –como el Bloc de Notas de
Windows- y almacenado en formato de texto plano en un fichero HolaMundo.cs[3], para
compilarlo basta abrir una ventana de consola (MS-DOS en Windows), colocarse en el
directorio donde se encuentre y pasárselo como parámetro al compilador así:
C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727>csc HolaMundo.cs
Hola Mundo!
Nótese que aunque el nombre winexe dé la sensación de que este valor para la opción /t sólo
permite generar ejecutables de ventanas, en realidad lo que permite es generar ejecutables sin
ventana de consola asociada. Por tanto, también puede usarse para generar ejecutables que
no tengan ninguna interfaz asociada, ni de consola ni gráfica.
Si en lugar de un ejecutable -ya sea de consola o de ventanas- se desea obtener una librería,
entonces al compilar hay que pasar al compilador la opción /t con el valor library. Por
ejemplo, siguiendo con el ejemplo inicial habría que escribir:
En este caso se generaría un fichero HolaMundo.dll cuyos tipos de datos podrían utilizarse
desde otros fuentes pasando al compilador una referencia a los mismos mediante la opción /r.
Por ejemplo, para compilar como ejecutable un fuente A.cs que use la clase HolaMundo de
la librería HolaMundo.dll se escribiría:
C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727>csc
/r:HolaMundo.dll,Otro.dll,OtroMás.exe A.cs
Hay que señalar que aunque no se indique nada, en toda compilación siempre se
referencia por defecto a la librería mscorlib.dll de la BCL, que incluye los tipos de uso más
frecuente. Si se usan tipos de la BCL no incluidos en ella habrá que incluir al compilar
referencias a las librerías donde estén definidos (en la documentación del SDK sobre cada
tipo de la BCL puede encontrar información sobre donde se definió)
Tanto las librerías como los ejecutables son ensamblados. Para generar un módulo de
código que no forme parte de ningún ensamblado sino que contenga definiciones de tipos
que puedan añadirse a ensamblados que se compilen posteriormente, el valor que ha de
darse al compilar a la opción /t es module. Por ejemplo:
Aunque hasta ahora todas las compilaciones de ejemplo se han realizado utilizando un
único fichero de código fuente, en realidad nada impide que se puedan utilizar más. Por
ejemplo, para compilar los ficheros A.cs y B.cs en una librería A.dll se ejecutaría:
Nótese que el nombre que por defecto se dé al ejecutable generado siempre es igual al del
primer fuente especificado pero con la extensión propia del tipo de compilación realizada
(.exe para ejecutables, .dll para librerías y .netmodule para módulos) Sin embargo, puede
especificárse como valor en la opción /out del compilador cualquier otro tal y como muestra
el siguiente ejemplo que compila el fichero A.cs como una librería de nombre Lib.exe:
C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727>csc /t:library /out:Lib.exe A.cs
Véase que aunque se haya dado un nombre terminado en .exe al fichero resultante, éste
sigue siendo una librería y no un ejecutable e intentar ejecutarlo produciría un mensaje de
error. Obviamente no tiene mucho sentido darle esa extensión, y sólo se le ha dado en este
ejemplo para demostrar que, aunque recomendable, la extensión del fichero no tiene porqué
corresponderse realmente con el tipo de fichero del que se trate.
Con lo que hay que tener cuidado, y en especial al compilar varios fuentes, es con que no
se compilen a la vez más de un tipo de dato con punto de entrada, pues entonces el compilador
no sabría cuál usar como inicio de la aplicación. Para orientarlo, puede especificarse como
valor de la opción /main el nombre del tipo que contenga el Main() ha usar como punto de
entrada. Así, para compilar los ficheros A.cs y B.cs en un ejecutable cuyo punto de entrada
sea el definido en el tipo Principal, habría que escribir:
Lógicamente, para que esto funcione A.cs o B.cs tiene que contener alguna definición de
algún tipo llamado Principal con un único método válido como punto de entrada
(obviamente, si contiene varios se volvería a tener el problema de no saber cuál utilizar)
Para compilar una aplicación en Visual Studio.NET primero hay que incluirla dentro de
algún proyecto. Para ello basta pulsar el botón New Project en la página de inicio que se
muestra nada más arrancar dicha herramienta, tras lo que se obtendrá una pantalla con el
aspecto mostrado en la Ilustración 1.
Una vez configuradas todas estas opciones, al pulsar botón OK Visual Studio creará toda
la infraestructura adecuada para empezar a trabajar cómodamente en el proyecto. Como
puede apreciarse en la Ilustración 2 , esta infraestructura consistirá en la generación de un
fuente que servirá de plantilla para la realización de proyectos del tipo elegido (en nuestro
caso, aplicaciones de consola en C#):
A partir de esta plantilla, escribir el código de la aplicación de ejemplo es tan sencillo con
simplemente teclear System.Console.WriteLine(“¡Hola Mundo!”) dentro de la definición del
método Main() creada por Visual Studio.NET. Claro está, otra posibilidad es borrar toda la
plantilla y sustituirla por el código para HolaMundo mostrado anteriormente.
Sea haga como se haga, para compilar y ejecutar tras ello la aplicación sólo hay que
pulsar CTRL+F5 o seleccionar Debug à Start Without Debugging en el menú principal de
Visual Studio.NET. Para sólo compilar el proyecto, entonces hay que
seleccionar Build à Rebuild All. De todas formas, en ambos casos el ejecutable generado se
almacenará en el subdirectorio Bin\Debug del directorio del proyecto.
Esta ventana permite configurar de manera visual la mayoría de opciones con las que se
llamará al compilador en línea de comandos. Por ejemplo, para cambiar el nombre del fichero
de salida (opción /out) se indica su nuevo nombre en el cuadro de texto Common
Properties à General à Assembly Name, para cambiar el tipo de proyecto a generar (opción /t)
se utiliza Common Properties à General à Output Type (como verá si intenta cambiarlo, no
es posible generar módulos desde Visual Studio.NET), y el tipo que contiene el punto de
entrada a utilizar (opción /main) se indica en Common Properties à General à Startup Object
Variables
Dentro de una clase a parte de los métodos que definen los que hace la clase, tenemos los
elementos que definen el estado de una clase y sus instancias, que son las constantes y las
variables.
Una variable almacena datos de cierto tipo. Podremos guardar cadenas, enteros, reales, etc
(según el tipo de la variable), para obtener el valor posteriormente en una expresión de C#.
Este lenguaje es fuertemente tipificado, por lo que el compilador cuidará que los valores que
guardemos en las variables correspondan con los tipos con que éstas se definieron.
Para definir una variable debemos indicar primero el tipo y a continuación el nombre de ésta.
Podemos asignarle un valor en la misma definición (como en Java o C++).
bool variable;
bool var2 = true;
int entero = 7;
Ya vimos el ejemplo ej3.cs en el que definimos dos variables para mostrarlas por pantalla.
Veamos un ejemplo en el que definamos más tipos de variables:
using System;
Tipos de datos
Además de los tipos vistos en el ejemplo anterior, podemos usar varios tipos de datos
enteros o reales (ver las siguientes tablas).
byte 8 0 to 255
short 16 -32768 to 32767
ushort 16 0 to 65535
uint 32 0 to 4294967295
ulong 64 0 to 18446744073709551615
char 16 0 to 65535
Operadores
Los operadores son símbolos que permiten realizar operaciones con uno o más datos, para
dar un resultado. El ejemplo clásico y obvio de operador es el símbolo de la suma (+),
aunque hay otros muchos.
Operadores Aritméticos: Son la suma (+), resta (-), producto (*), división (/) y
módulo (%)
Operadores Lógicos: Son "and" (&& y &), "or" (|| y |), "not" (!) y "xor" (^)
La diferencia entre && y &, y entre || y | es que && y || hacen lo que se llama
"evaluación perezosa": si evaluando sólo la primera parte de la operacion se puede
deducir el resultado, la parte derecha no se evaluará. Es decir, si tenemos por
ejemplo:
Operadores relacionales: igualdad (==), desigualdad (!=), "mayor que" (>), "menor
que" (<), "mayor o igual que" (>=) y "menor o igual que" (<=)
variable1 += variable2;
variable1 = variable1 + 1;
variable1 += 1;
variable1++;
Hay que tener en cuenta que no es lo mismo poner variable++ que ++variable. Ambas
formas son correctas, pero no significan lo mismo. Lo vemos con un ejemplo:
variable1 = ++variable2;
variable1 = variable2++;
A.metodo1 ();
(expresion) is TIPO
De esta forma, variable2 será tratada como si fuera un dato de tipo int, aunque no lo
sea.
using System;
class Operadores1 {
public static void Main() {
int unario = 0;
int preIncremento;
int preDecremento;
int postIncremento;
int postDecremento;
sbyte bitNot;
bool logNot;
preIncremento = ++unario;
Console.WriteLine("Pre-Incremento: {0}", preIncremento);
preDecremento = --unario;
Console.WriteLine("Pre-Decremento: {0}", preDecremento);
postDecremento = unario--;
Console.WriteLine("Post-Decremento: {0}", postDecremento);
postIncremento = unario++;
Console.WriteLine("Post-Incremento: {0}", postIncremento);
bitNot = 0;
bitNot = (sbyte)(~bitNot);
Console.WriteLine("Negación a nivel de bit: {0}", bitNot);
logNot = false;
logNot = !logNot;
Console.WriteLine("Negación lógica: {0}", logNot);
}
}
using System;
class Operadores2 {
public static void Main() {
int x,y,resultado;
x = 7;
y = 5;
resultado = x + y;
Console.WriteLine(" x+y = {0}", resultado);
resultado = x - y;
Console.WriteLine(" x-y = {0}", resultado);
resultado = x * y;
Console.WriteLine(" x*y = {0}", resultado);
resultado = x / y;
Console.WriteLine(" x/y = {0}", resultado);
}
}
Hasta ahora hemos visto programas muy simples con una serie de pasos secuenciales.
En ellos no se comprueban valores para tomar decisiones, en función de las entradas,
por ejemplo.
En esta sección vamos a estudiar las estructuras de control de C#, tanto las
condicionales como los bucles.
Estructuras condicionales: if
comprobar una condición y en función de ésta realizar una acción u otra (excluyentes)
if (expresión) {
instrucción o bloque de intrucciones 1;
}
[else {
instrucción o bloque de intrucciones 2;
} ]
if (expresión 1) {
instrucción o bloque de instrucciones 1;
}
else if (expresión 2) {
instrucción o bloque de instrucciones 2;
}
else if (expresión 3) {
instrucción o bloque de instrucciones 3;
}
else {
intrucción o bloque de instrucciones 4;
}
using System;
class condicionalIF {
public static void Main() {
string myInput;
int myInt;
Console.Write("Please enter a number: ");
myInput = Console.ReadLine();
myInt = Int32.Parse(myInput);
// decisión MÚLTIPLE
if (myInt < 0 || myInt == 0) {
Console.WriteLine("el número {0} es menor o igual a
cero", myInt);
} else if (myInt > 0 && myInt <= 10) {
Console.WriteLine("el número {0} está entre 1 y 10",
myInt);
} else if (myInt > 10 && myInt <= 20) {
Console.WriteLine("el número {0} está entre 11 y 20",
myInt);
} else if (myInt > 20 && myInt <= 30) {
Console.WriteLine("el número {0} está entre 21 y 30",
myInt);
} else {
Console.WriteLine("el número {0} es mayor que 30",
myInt);
}
}
}
En este ejemplo, leemos un valor desde teclado, que nos es devuelto como una cadena de
caracteres que debemos transformar en número antes de asignarla a una variable entera.
Para ello utilizaremos el método de Console que ya conocemos ReadLine(). La
transformación la haremos mediante el método Parse de la clase Int32 del espacio de
nombres System:
myInput = Console.ReadLine();
myInt = Int32.Parse(myInput);
Como vemos, en función de la condición, podemos tomar una o varias acciones mútuamente
exclusivas (sólo una de ellas se ejecutará). También hay que hacer notar que si dicha acción
es una sola instrucción, se pueden omitir la definición de bloque ( { } ).
En este caso, seguido de la palabra reservada debemos poner una expresión que se evaluará
a uno de los siguientes tipos: sbyte, byte, short, ushort, int, uint, long, ulong, char, string, or
enum. A continación viene el bloque del switch, en el que cada posible caso está etiquetado
con la palabra case <expresion>:
Como veremos en el siguiente ejemplo, podemos poner un caso por defecto que se ejecutará
si ninguno de los casos comprobados conincidía:
using System;
class condicionalSWITCH {
public static void Main() {
string myInput;
int myInt;
Console.Write("Please enter a number: ");
myInput = Console.ReadLine();
myInt = Int32.Parse(myInput);
switch( myInt ) {
case 1:
Console.WriteLine("primera opción");
break;
case 2:
Console.WriteLine("segunda opción");
break;
case 3:
Console.WriteLine("tercera opción");
break;
case 4:
Console.WriteLine("cuarta opción");
break;
default:
Console.WriteLine("opción por defecto");
break;
}
}
}
Para hacer que cierto grupo de instrucciones se ejecuten cuando se cumplan varios casos,
podemos utilizar una estructura como la que sigue:
switch( myInt ) {
case 1:
case 2:
case 3:
Console.WriteLine("está entre 1 y 3");
break;
default:
Console.WriteLine("no está entre 1 y 3");
break;
}
Una vez vistas las estructuras condicionales, veamos los tipos de bucle disponibles en C#.
while
while (expresión) {
instrucción o bloque de instrucciones;
}
El siguiente ejemplo de manejo de esta estructura muestra por pantalla los números del 0 al
9:
using System;
class bucleWHILE {
public static void Main() {
int myInt = 0;
El funcionamiento de este tipo de bucle es similar al anterior (while) salvo por el detalle de
que aquél comprueba la condición antes de entrar al bucle, por lo que si no se cumple, no
realiza ninguna iteración.
Sin embargo, este otro, primero entra en el bucle, por lo que realiza una iteración antes de
comprobar por primera vez la condición (siempre realizará por lo menos una iteración).
using System;
class bucleWHILE {
public static void Main() {
int myInt = 100;
En este caso, no realiza ninguna iteración. Veamos el siguiente programa, en el cual, a pesar
de que no se cumple la condición, sí hace una iteración:
using System;
class bucleDOWHILE {
public static void Main() {
int myInt = 100;
do {
Console.Write("{0} ",myInt);
myInt++;
} while ( myInt < 10 );
Console.WriteLine();
}
}
for
donde:
using System;
class bucleFOR {
public static void Main() {
for (int i=0; i < 20; i++) {
Console.Write("{0} ", i);
}
Console.WriteLine();
}
}
foreach
La instrucción foreach es similar a la función del mismo nombre de los Shells de Unix o de
Perl. Asocia iterativamente a una variable cada elemento de la lista. Esta sucesión de valores
sirve para parametrizar la ejecución del bloque de instrucción. La sintaxis de la instrucción
foreach es:
Las intrucciones for y foreach son equivalentes. Sin embargo, la utilización de una de estas
instrucciones se justifica generalmente por el contexto. La utilización de la instrucción
foreach permitirá recorrer la lista de forma más elegante. El ejemplo siguiente lo ilustra:
using System;
class bucleFOREACH {
public static void Main() {
string[] lista = {"cadena1", "otra cadena", "tercer elemento",
"ultimo"};
foreach (string var in lista) {
Console.WriteLine("{0} ", var);
}
}
}
DEMOSTRACIÓN
Al levantar la solución fijese que solo se ejecuta la acción a partir de los Operadores
Corremos la solución:
Y el resultado es el siguiente:
Todas las instrucciones deben estar incluidas en un procedimiento o función, a las que
llamaremos mediante su identificador. A estas funciones y procedimientos podemos pasarles
parámetros.
En C# tenemos 4 tipos:
1. Los procedimientos que ejecutan un código a petición sin devolver ningún resultado.
2. Las funciones que ejecutan un código y devuelven el resultado al código que las
llamó.
3. Los procedimientos de propiedades que permiten manejar las propiedades de los
objetos creados.
4. Los procedimientos de operador utilizados para modificar el funcionamiento de un
operador cuando se aplica a una clase o una estructura.
Procedimiento
void VerResultado() {
Console.WriteLine("¡¡¡Ganador!!!");
}
Función
int calculo () {
...
instrucciones
...
return resultado;
}
Procedimiento de propiedades
Estos procedimientos se llaman “encapsuladores” ya que el valor de la propiedad se
encapsula. Se utilizarán cuando queramos modificar y/o recuperar un valor (Set / Get).
Procedimiento de operador
Permite la redifinicón de un operador estándar del lenguaje para utilizarlo en tipo
personalizados (clase o estructura).
struct Cliente {
public int codigo;
public string apellido;
public string nombre;
}
c2.codigo = 125;
c2.nombre = "Perico";
c2.apellido = "Palotes";
c3 = c1 + c2;
//Aquí el compilador daría error porque no se pueden aplicar el operando al tipo.
}
struct Cliente {
public int codigo;
public string apellido;
public string nombre;
public static Cliente operator + (Cliente cl1, Cliente cl2) {
Cliente c;
c.codigo = cl1.codigo + cl2.codigo;
c.apellido = cl1.apellido + cl2.apellido;
c.nombre = cl1.nombre + cla2.nombre;
return c;
}
}
Por valor:
...
double PrecioNeto;
Console.WriteLine(PrecioNeto);
En este caso PrecioBruto se está pasando por valor ya que de forma predeterminada los
valores enteros, números flotantes, decimales, booleanos y estructuras definidas por el
usuario se pasan por valor. Los demás tipos siempre se pasan por referencia, es decir se le
pasa la dirección de memoria a la que apunta la variable de forma que el procedimiento o
función pueden manipular directamente el valor de esa variable.
Por referencia:
Podemos forzar el paso por referencia de una variable que por defecto se pasa como valor.
para ello utilizaremos la palabra reservada “ref” o “out“, la etiqueta “ref” se debe utilizar
tanto en la lista de parámetros de la función o procedimiento, como en la propia llamada a la
función o procedimiento y además debe ser inicializada, por el contrario, la etiqueta “out”
funciona de igual manera pero sin la exigencia de inicializar la variable.
public static double CalculoNETO (double Pbruto, double Tasa, ref double iva) {
return Pbruto+iva; }
...
double PrecioNeto;
double importeIva=0;
double suma=0;
}
return suma /notas.Length;
Parámetros opcionales
Se puede indicar que un parámetro es opcional asignándole un valor, pero con la precaución
de asignar también a todos los parámetros restantes un valor ya que al declarar un parámetro
como opcional, el resto de parámetros también deberían serlo.
double calculoNeto(double Pbruto, double Tasa = 21);
La siguiente declaración estaría mal ya que el último parámetro no sería opcional:
double calculoNeto(double Pbruto, double Tasa = 21; String divisa);
Lo correcto sería
Por otro lado en la llamada a la función si se especifica un parámetro opcional, todos los
anteriores también se deberían definir:
calculoNeto(10);
1 calculoNeto(10,5.5);
1 calculoNeto(10,5.5,"$");
Excepciones en C# (CSharp)
try {
} catch(Exception e) {
En el ejemplo anterior, cuándo se lance una excepción del tipo Exception por que se
produjo un error debido a la ejecución de una operación de entrada/salida, el
objeto e almacenará la información relativa al error ocurrido, información que puede ser
manipulada a través de las propiedades y métodos de su clase; por
ejemplo, Message muestra un mensaje indicando qué error ocurrió. En el ejemplo anterior,
al especificar que el tipo de excepciones que se desea atrapar son de la clase Exception,
cabe cualquier excepción de cualquier clase derivada de Exception. Si quisiéramos filtrar
un tipo particular de excepciones deberemos indicarlo explícitamente. Por ejemplo, cuándo
ejecute el siguiente programa, observa que lanza una excepción de la
clase FormatException por utilizar un formato d para mostrar un valor double. Por tanto,
dicha excepción será atrapada por el primer bloque catch. Cuándo una excepción se atrapa,
se considera manipulada.
using System;
class Test {
try {
c = a + b;
} catch(FormatException) {
} catch(Exception e) {
DEMOSTRACIÓN
Luego escribiremos otra serie de métodos que contienen una serie de parámetros y devuelven
un resultado de un cálculo cualquiera:
Luego escribiremos los métodos que nos servirán como ejemplo para el manejo y control de
excepciones:
En el método Main() escribiremos el código para ejecutar dichos métodos:
Al ejecutar la solución
Variables y Constantes
Una variable o una constante representan un posición de memoria ocupada por un tipo de
dato. Una varible puede ser local, el parámetro de un método, el elemento de un array, un
campo de una instancia de un objeto o un campo estático de un objeto, y su valor puede ser
modificado, en cambio, el valor de una constante siempre es el mismo.
Dado que C# es fuertemente tipificado todas las variables y constantes tienen asociado un
tipo que define los posibles valores que puede tomar.
En C# a todas las variables y constantes se les debe asignar un valor antes de poder utilizarlas.
Esta asignación de valor puede ser tanto explícita como automática a través de la asignación
del valor por defecto para un tipo. Estos son los valores por defecto para los diferentes tipos:
tipos numéricos 0
bool false
char '\0'
enumeraciones 0
referencias null
Cuando intentamos utilizar una variable sin asignarle un valor se producirá un error como
sucede con el siguiente ejemplo:
// variable.cs
using System;
public class Variable
{
int v;
public Variable() {} // v = valor por
defecto
public Variable(int a) { v = a; } // v = a
Variable v;
console.WriteLine(v); // error, v
sin asignar
}
}
Todos los tipos en C# pertenecen a una de las siguientes tres categorías, cuya diferencia
fundamental entre estas tres categorias es la forma en que son tratados en memoria.
Tipos valor
Estos tipos son los más sencillos de comprender. Directamente contienen los datos, por
ejemplo, un tipo int contiene su valor, o un tipo bool vale true o false. Una de sus
características es que cuando se asignan el valor de una variable a otra, se crea una copia de
dicho valor.
// valor.cs
using System;
class Valor
{
static void Main()
{
int x = 3;
int y = x; // y es una copia de x
++x; // incrementa x a 4
Console.WriteLine("x = {0} y = {1}", x, y);
}
}
Este ejemplo crea dos variables de tipo int, x e y. Inicialmente x vale 3 e y pasa a ser una
copia de x. Al incrementar el valor de x, el valor de y no cambia puesto que ambos valores
son copias independientes en memoria.
Todos los tipos simples y sus combinaciones a través de structs pertenecen a esta categoría.
Tipos referencia
Los tipos referencia son algo más complejos. En un tipo referencia hemos de diferenciar
entre un objeto y una referencia a un objeto. Veamos con un ejemplo cada una de estas
partes:
// referencia.cs
using System;
using System.Text;
class Referencia
{
static void Main()
{
StringBuilder x = new StringBuilder("hola");
StringBuilder y = x;
x.Append(", ¿estás aprendiendo mucho?");
Console.WriteLine("x = '{0}' y = '{1}'", x, y);
}
}
En este caso StringBuilder es un tipo referencia, a diferencia de int que era un tipo valor.
Cuando se declara x en el ejemplo anterior, realmente se están haciendo dos cosas a la vez:
StringBuilder x;
x = new StringBuilder("hola");
StringBuilder y = x;
Com esta linea también se llevan a cabo dos acciones diferentes: por un lado se crea una
nueva referencia, y, a un objeto de tipo StringBuilder y por otro lado se hace que y apunte
al mismo objeto al que x lo hace. Por este motivo al modificar x también se modifica y.
Ambos comparten la misma representación interna de su valor.
Los tipos object y string, asi como cualquier clase definida por nosotros pertenece a esta
categoría de tipos.
Enumeraciones
Para utilizarlas tenemos que declarar una variable del tipo de la enumeración y asignarle un
valor dentro de los que dicha enumeración posea.
Por defecto, a cada valor de la enumeración se le asigna como valor una constante entera 0,
1, 2,... Opcionalmente se puede especificar un tipo numérico alternativo y, además,
asignarle un valor de este tipo a cada uno de los componentes.
[Flags]
public enum Rumbo: byte {Norte = 1, Sur = 2, Este = 4,
Oeste = 8};
Console.WriteLine(barco.Format()); // muestra
"Norte|Oeste"
Console.WriteLine(barco); // muestra "9"
El tipo System.Enum proporciona una gran cantidad de métodos estáticos muy útilies para
manejar las enumeraciones:
// enum.cs
using System;
class Prueba
{
public static void Main()
{
Type t = Enum.GetUnderlyingType(typeof(Logico));
Console.WriteLine(t); // imprime "Byte";
bool hay_medio = Enum.IsDefined(typeof(Logico),
"Medio");
Console.WriteLine(hay_medio); // imprime "False"
foreach(Logico logico in
Enum.GetValues(typeof(Logico)))
Console.WriteLine("{0} = {1}", logico,
Enum.Format(typeof(Logico),
logico, "D"));
}
}
Estructuras
Una estructura es similar a una clase. Se declaran utilizando la palabra clave struct. Las
principales diferencias con una clase son:
Una clase es un tipo referencia mientras que una estructura es un tipo valor. En
consecuencia, las estructuras suelen utilizarse para declarar tipos simples en que la
semántica de tipo-valor sea deseable.
Una estructura no puede heredar de una clase ni de otra estructura. Tampoco puede
ser la base de una clase. Sin embargo, heredan de la clase object. Al igual que las
clases, las estructuras pueden implementar interfaces.
Las clases pueden tener destructores, pero las estructuras no.
Las clases pueden tener constructores sin parámetros y además inicializar los
campos de una instancia, y en cambio, una estructura no. El constructor por defecto
sin parámetros de las estructuras inicializa cada campo con el valor por defecto que
le corresponda, y si declara un constructor, a todos los campos debe asignarsele un
valor.
// struct1.cs
using System;
struct SimpleStruct
{
private int xval;
public int X
{
get { return xval; }
set { if (value < 100) xval = value; }
}
class TestClass
{
public static void Main()
{
SimpleStruct ss = new SimpleStruct();
ss.X = 5;
ss.DisplayX();
}
}
A causa de que son tipos valor, cuando son pasados a un método, se pasan por valor en
lugar de por referencia como ocurre con las clases.
// struct2.cs
using System;
class TheClass
{
public int x;
}
struct TheStruct
{
public int x;
}
class TestClass
{
public static void structtaker(TheStruct s)
{
s.x = 5;
}
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Explicit)]
struct PosicionExplicita
{
[FieldOffset(0)]
public byte b0;
[FieldOffset(1)]
public byte b1;
[FieldOffset(2)]
public byte b2;
[FieldOffset(3)]
public byte b3;
[FieldOffset(0)]
public int i0;
[FieldOffset(1)]
public int i1;
[FieldOffset(0)]
public long l;
}
Colecciones de datos
Vectores
Como lo explique en un post anterior, las colecciones de datos son algunas de las
herramientas mas utilizadas a la hora de desarrollar una aplicación ya que nos permiten
manipular información en memoria de una manera fácil y flexible.
La plataforma .NET ofrece una gran variedad de colecciones que debemos conocer y saber
utilizar para emplear la mas indicada en nuestras aplicaciones y obtener así el mejor
rendimiento posible tanto en procesamiento como en manejo de memoria. A continuación se
explican algunas de las colecciones incluidas en el lenguaje C#.NET.
La colección mas sencilla que utiliza como clase base System.Array, es lo que se conoce
comúnmente como vector. Para definir un vector, debemos tener claro que tipo de dato
queremos que nuestro vector almacene y la cantidad de valores que deseamos almacenar ya
que son requisitos indispensables para la definición y creación de vectores.
En C#, un vector se define con un par de corchetes [] después del tipo de dato, como se
muestra a continuación.
Sin embargo, para hacer uso de nuestro vector, debemos primero crear la instancia declarando
cuantas posiciones máximas tendrá, como se muestra a continuación:
Luego de esto, podremos almacenar tantos datos del mismo tipo como posiciones tenga el
vector. Por ejemplo en la variable booleanos se podrá almacenar hasta 3 variables de tipo
bool y en la variable texto se podrá almacenar hasta 100 variables de tipo string.
Es importante tener en cuenta que la primera posición de un vector es la posición 0, por lo
cual si la longitud es de 5, las posiciones van desde la 0 hasta la 4.
Para almacenar información en un vector, solo debemos hacer referencia a la posición del
vector donde deseamos almacenarla y utilizar el operador de asignación (=).
A continuación se almacena en cada posición restante del vector una cadena de texto
mediante un operador de control de flujo (for). Este operador es posible utilizarlo con todas
las colecciones en .NET ya que todas son enumerables debido a que implementan la interface
IEnumerable.
Un vector puede tener mas de una dimensión según sea necesario. Un vector de dos
dimensiones es considerado una matriz de datos.
Para definir un vector de mas de una dimensión, se utiliza el carácter ‘,’ en los corchetes de
la definición del mismo.
Las propiedades mas importantes que exponen los vectores se presentan a continuación:
Length: Indica la longitud total del vector (En todas las dimensiones).
Para instanciar un jagged array se debe indicar el numero de posiciones que tendrá el vector
contenedor, como se hace con un vector normal de una dimensión.
Igualmente se puede crear un array de arrays de varias dimensiones, donde cada vector
almacenado tendrá 2, 3 o mas dimensiones según sea necesario.
Sin embargo, la complejidad en este caso aumenta bastante y se debe buscar siempre obtener
el mejor rendimiento posible en nuestras aplicaciones, por lo tanto estas colecciones
complejas deben ser utilizadas únicamente cuando sea necesario y cuando ninguna de las
colecciones disponibles en la plataforma .NET es suficiente para almacenar la información.
ArrayList
Esta es una clase que representa una lista de datos. Es bastante parecida a la colección
explicada anteriormente, con la diferencia que el ArrayList puede aumentar o disminuir su
tamaño dinámicamente de una manera eficiente.
Con un array de datos no era posible aumentar la capacidad del vector ya que dicho parámetro
es especificado en el momento de crear la instancia del objeto. El ArrayList a diferencia,
brinda la posibilidad de aumentar o disminuir su tamaño dinámicamente según sea necesario.
Para crear una instancia de este objeto, se debe utilizar la clase ArrayList incluida en el
espacio de nombre System.Collections como se muestra a continuación.
Si es necesario agregar un objeto a la colección, se debe utilizar el método Add, el cual inserta
el nuevo elemento en la última posición, o el método Insert el cual lo inserta en la posición
indicada.
Todos los objetos almacenados en un Arraylist son tratados como objetos, por lo tanto es
posible agregar todo tipo de datos, es decir, se puede agregar enteros, cadenas de texto,
objetos de clases propias, etc. Y a diferencia de los array vistos en la parte I, no todos los
elementos deben ser del mismo tipo de dato. Esto en algunas ocasiones puede ser una ventaja
ya que permite almacenar gran variedad de información en una sola colección, sin embargo,
por razones de rendimiento (cast, boxing, unboxing), hay ocasiones en las que es preferible
utilizar las colecciones genéricas que serán tratadas mas adelante.
Las propiedades mas utilizadas de esta colección son : Count y Capacity. La primera sirve
para conocer la cantidad actual de elementos que contiene la colección. La segunda indica la
capacidad máxima actual de la colección para almacenar elementos. Es necesario tener
presente que la capacidad de la colección, aumenta en caso de ser necesario al insertar un
elemento, con lo que se garantiza el redimensionamiento automático.
La capacidad de una colección nunca podrá ser menor a la cantidad total de elementos
contenidos, por lo que si se modifica manualmente la propiedad Capacity y se le asigna un
valor menor que el valor devuelto por la propiedad Count, obtendremos una excepción de
tipo ArgumentOutOfRangeExcepetion .
Determinar que tipo de colección usar en un caso especifico, es tarea del desarrollado y se
debe evaluar las condiciones para determinar la manera mas eficiente de administrar los
recursos. Si es un escenario donde no conocemos el tamaño que tendrá la colección y si
además será muy probable que el tamaño varíe, entonces será recomendable bajo todas las
demás circunstancias usar un ArrayList en lugar de un array debido a que el ArrayList brinda
la posibilidad de redimensionarlo automáticamente. Sin embargo, para escenarios donde se
conoce de antemano la cantidad total de elementos a almacenar y si todos son del mismo
tipo, se debe usar el array convencional ya que los objetos son almacenados en su tipo de
datos nativo y no es necesario hacer conversiones.
Stack & Queue
Las pilas (Stack) y las colas (Queue) son dos colecciones muy similares entre si ya que solo
varia la forma en que guardan y extraen los elementos que contienen. En ciertas cosas estas
dos colecciones se parece a un ArrayList, como por ejemplo que soporta el
redimensionamiento automático y que los elementos son almacenados como objetos
(System.Object). Pero también tienen algunas diferencias, como por ejemplo que no se puede
cambiar su capacidad y no se puede acceder a sus elementos a través de índices
En algunas ocasiones, es importante tener un control sobre el orden en que los elementos son
ingresados y obtenidos de la colección. Por esta razón existen las colecciones Stack y Queue.
Como se menciono anteriormente, en estas colecciones NO es posible acceder aleatoriamente
mediante índices a sus elementos, sino que es necesario utilizar un método encargado de
extraer un elemento a la vez. Pero cual elemento?. Precisamente en la respuesta a esa
pregunta radica la diferencia entre estas dos colecciones.
La pila (Stack), es una colección en la que todo nuevo elemento se ingresa al final de la
misma, y únicamente es posible extraer el ultimo elemento de la colección. Por este
comportamiento, el Stack es conocido como una colección LIFO (Last Input Fisrt Output)
ya que siempre el ultimo elemento ingresado a la colección, será el primero en salir. Quizás
la mejor manera de recordar el comportamiento de un Stack, es asociándolo con una “pila”
de platos en donde cada plato esta encima del otro y en caso de querer ingresar un plato a la
pila, lo que se debe hacer es ponerlo encima del ultimo plato. Luego cuando se quiere sacar
un plato de la pila, solo podemos coger el ultimo plato.
La cola (Queue), tiene el comportamiento contrario a la pila. Todo nuevo elemento se agrega
al principio de la colección y solo se puede extraer el ultimo elemento. Por esta razón, la cola
se conoce como una colección FIFO (Fisrt Input First Output) ya que el primer elemento que
ingresa a la cola es el primer elemento que sale. Para recordar este comportamiento se puede
asociar la Queue con la fila que se debe hacer en un banco para realizar una consignación.
En ese caso, el cajero atiende en el orden en que llegan las personas a la cola.
Para implementar cada una de ellas se debe utilizar la clase Stack y Queue respectivamente
y utilizar sus métodos que ofrecen la posibilidad de agregar elementos a la colección y extraer
elementos según el comportamiento de la colección que se este utilizando.
Como se ve en la figura anterior, para agregar elementos a una pila se debe utilizar el método
Push que recibe como parámetro un Object. Mientras que en la cola se debe utilizar el método
Enqueue (encolar). Ambos métodos, incrementan automáticamente la capacidad de la
colección.
Resultado:
DEMOSTRACIÓN
Ejecutamos
Y el resultado es el siguiente:
Seguimos con el ejemplo, y debajo del método Main(), crearemos el procedimiento Legth
que especifica un arreglo:
Note en la ventana de inspección que se inirtió el orden de los valores asignados en cada
posición del arreglo
Ahora seguiremos corriendo la depuración con la tecla F11 hasta llegar al otro arreglo
Cuando empezamos a correr con F11 nuestro ArrayList empieza agregar Valores con la
sentencia Add
Seguimos con el siguiente Array
Empezamos la depuración
Verificando que la instrucción Enqueue la cola se carga
Para remover algún valor se utiliza la instrucción Dequeue
La salida de la pantalla seria la siguiente:
Seguimos con el ejemplo de las pilas
Comentamos nuevamente el código anterior y hacemos llamado al procedimiento de la pila
Empezamos la depuración
Verificando que la instrucción Push la cola se carga