Sunteți pe pagina 1din 90

DESARROLLO DE APLICACIONES EN C#

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: }

Todo el código escrito en C# se ha de escribir dentro de una definición de clase, y lo que


en la línea 1: se dice es que se va a definir una clase (class) de nombre HolaMundo1 cuya
definición estará comprendida entre la llave de apertura de la línea 2: y su correspondiente
llave de cierre en la línea línea 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.

La palabra static que antecede a la declaración del tipo de valor devuelto es


un modificador del significado de la declaración de método que indica que el método está
asociado a la clase dentro de la que se define y no a los objetos que se creen a partir de ella.
Main() es lo que se denomina el punto de entrada de la aplicación, que no es más que el
método por el que comenzará su ejecución. Necesita del modificador static para evitar que
para llamarlo haya que crear algún objeto de la clase donde se haya definido.

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:

System.Console.WriteLine("Tengo {0} años", x);

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()

static int Main()

static int Main(string[] args)

static void Main(string[] args)

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

Compilación en línea de comandos

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

csc.exe es el compilador de C# incluido en el .NET Framework SDK para Windows de


Microsoft. Aunque en principio el programa de instalación del SDK lo añade
automáticamente al path para poder llamarlo sin problemas desde cualquier directorio, si lo
ha instalado a través de VS.NET esto no ocurrirá y deberá configurárselo ya sea
manualmente, o bien ejecutando el fichero por lotes Common7\Tools\vsvars32.bat que
VS.NET incluye bajo su directorio de instalación, o abriendo la ventana de consola desde el
icono Herramientas de Visual Studio.NET à Símbolo del sistema de Visual
Studio.NET correspondiente al grupo de programas de VS.NET en el menú Inicio de
Windows que no hace más que abrir la ventana de consola y llamar automáticamente
a vsvars32.bat. En cualquier caso, si usa otros compiladores de C# puede que varie la forma
de realizar la compilación, por lo que lo que aquí se explica en principio sólo será válido
para los compiladores de C# de Microsoft para Windows.
Tras la compilación se obtendría un ejecutable llamado HolaMundo.exe cuya ejecución
produciría la siguiente salida por la ventana de consola:

Hola Mundo!

Si la aplicación que se vaya a compilar no utilizase la ventana de consola para mostrar su


salida sino una interfaz gráfica de ventanas, entonces habría que compilarla pasando al
compilador la opción /t con el valor winexe antes del nombre del fichero a compilar. Si no
se hiciese así se abríría la ventana de consola cada vez que ejecutase la aplicación de
ventanas, lo que suele ser indeseable en este tipo de aplicaciones. Así, para compilar
Ventanas.cs como ejecutable de ventanas sería conveniente escribir:

C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727>csc /t:winexe Ventanas.cs

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:

C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727>csc /t:library HolaMundo.cs

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 A.cs

En general /r permite referenciar a tipos definidos en cualquier ensamblado, por lo


que el valor que se le indique también puede ser el nombre de un ejecutable. Además, en
cada compilación es posible referenciar múltiples ensamblados ya sea incluiyendo la
opción /r una vez por cada uno o incluiyendo múltiples referencias en una única
opción /r usando comas o puntos y comas como separadores. Por ejemplo, las siguientes
tres llamadas al compilador son equivalentes:
C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727>csc
/r:HolaMundo.dll;Otro.dll;OtroMás.exe A.cs

C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727>csc
/r:HolaMundo.dll,Otro.dll,OtroMás.exe A.cs

C:\WINDOWS\....\v2.0.50727>csc /t:HolaMundo.dll /r:Otro.dll /r: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:

C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727>csc /t:module HolaMundo.cs

Con la instrucción anterior se generaría un módulo llamado HolaMundo.netmodule


que podría ser añadido a compilaciones de ensamblados incluyéndolo como valor de la
opción /addmodule. Por ejemplo, para añadir el módulo anterior a la compilación del
fuente librería Lib.cs como librería se escribiría:

C:\WINDOWS\...\v2.0.50727>csc /t:library /addmodule:HolaMundo.netmodule Lib.cs

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:

C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727>csc /t:library A.cs B.cs

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.

A la hora de especificar ficheros a compilar también se pueden utilizar los caracteres de


comodín típicos del sistema operativo. Por ejemplo, para compilar todos los ficheros con
extensión .cs del directorio actual en una librería llamada Varios.dll se haría:

C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727>csc /t:library /out:varios.dll *.cs

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:

C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727>csc /main:Principal A.cs B.cs

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)

Compilación con Visual Studio.NET

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.

En el recuadro de la ventana mostrada etiquetado como Project Types se ha de seleccionar


el tipo de proyecto a crear. Obviamente, si se va a trabajar en C# la opción que habrá que
escoger en la misma será siempre Visual C# Projects.

En el recuadro Templates se ha de seleccionar la plantilla correspondiente al subtipo de


proyecto dentro del tipo indicado en Project Types que se va a realizar. Para realizar un
ejecutable de consola, como es nuestro caso, hay que seleccionar el icono etiquetado como
Console Application. Si se quisiese realizar una librería habría que seleccionar Class Library,
y si se quisies realizar un ejecutable de ventanas habría que seleccionar Windows
Application. Nótese que no se ofrece ninguna plantilla para realizar módulos, lo que se debe
a que desde Visual Studio.NET no pueden crearse.

Por último, en el recuadro de texto Name se ha de escribir el nombre a dar al proyecto y


en Location el del directorio base asociado al mismo. Nótese que bajo de Location aparecerá
un mensaje informando sobre cual será el directorio donde finalmente se almacenarán los
archivos del proyecto, que será el resultante de concatenar la ruta especificada para el
directorio base y el nombre del proyecto.

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.

En el extremo derecho de la ventana principal de Visual Studio.NET puede encontrar el


denominado Solution Explorer (si no lo encuentra, seleccione View à Solution Explorer),
que es una herramienta que permite consultar cuáles son los archivos que forman el proyecto.
Si selecciona en él el icono correspondiente al proyecto en que estamos trabajando y

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;

public class Empezando {


public static void Main () {
string cadena = "hola";
int entero = 7;
double real = 0.2;
bool booleana = true;

Console.WriteLine ("definición de varias variables, de


varios tipos...");

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


Console.WriteLine ("entero={0}", entero );
Console.WriteLine ("booleana={0}", booleana );
Console.WriteLine ("real={0}", real );
}
}

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).

Tipos de datos enteros

Tipo Tamaño (en bits) Rango

sbyte 8 -128 to 127

byte 8 0 to 255
short 16 -32768 to 32767

ushort 16 0 to 65535

int 32 -2147483648 to 2147483647

uint 32 0 to 4294967295

long 64 -9223372036854775808 to 9223372036854775807

ulong 64 0 to 18446744073709551615

char 16 0 to 65535

Tipos de datos reales

Tipo Tamaño (en bits) Precisión Rango

float 32 7 digits 1.5 x 10-45 to 3.4 x 1038

double 64 15-16 digits 5.0 x 10-324 to 1.7 x 10308

decimal 128 28-29 decimal places 1.0 x 10-28 to 7.9 x 1028

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.

Vamos a hacer un repaso a los tipos de operadores que tenemos en C#:

 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:

false && (otra cosa)


El resultado de esta operación siempre será false, y (otra cosa) ni siquiera se evalúa.
De igual forma, si tenemos

true || (otra cosa)

el resultado será true, y la parte derecha nunca se evaluará.

Operadores relacionales: igualdad (==), desigualdad (!=), "mayor que" (>), "menor
que" (<), "mayor o igual que" (>=) y "menor o igual que" (<=)

Operadores de Manipulación de Bits: Tenemos las siguientes operaciones: and (&),


or (|), not (~), xor (^), desplazamiento a la izquierda (<<), y desplazamiento a la
derecha (>>). El desplazamiento a la izquierda rellena con ceros. El desplazamiento
a la derecha, si se trata de un dato con signo, mantiene el signo. Si no, rellena con
ceros.

Operadores de Asignación: El operador básico de asignación es =. Además, tenemos


las clásicas abreviaturas +=, -=, *=, /=, &=, |=, ^=, <<= y >>=

Estas abreviaturas se usan para evitar tecleo en operaciones como esta:

variable1 = variable1 + variable2;

Se puede escribir de esta forma abreviada:

variable1 += variable2;

También tenemos operadores de incremento (++) y decremento (--), que incrementan


en una unidad el valor de la variable sobre la que se aplican. Por tanto, estas tres líneas
de código son casi iguales:

variable1 = variable1 + 1;
variable1 += 1;
variable1++;

El "casi iguales" lo ponemos porque en muchas máquinas, el operador ++ es más


rápido que la operación "+ 1", ya que el compilador lo traduce a una única instrucción
máquina.

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++;

En el primer caso, primero se incrementa variable2 y luego se hace la asignación. En


el segundo caso primero se hace la asignación, y luego el incremento.
Operador Condicional: Es el único operador de C# que tiene tres operandos. Su
sintaxis es esta:

 <condición> ? <expresión1> : <expresión2>

Quiere decir que si la condición es true, se evalúa expresión1, y si es falsa, se evalúa


expresión2.
Ojo: No confundir con un "if". Este operador devuelve un valor, mientras que el if es
sólamente una instrucción.

Operadores de Acceso a Objetos: El operador para acceder a los miembros de un


objeto es el punto. Así esta expresión:

 A.metodo1 ();

nos permite acceder al método "metodo1" del objeto A.

Operadores de Punteros: Tenemos varios operadores. Para acceder a la dirección de


memoria a la que apunta el puntero, lo hacemos con &puntero. Para acceder al
contenido de la dirección de memoria, tenemos *puntero. Si lo que referencia el
puntero es un objeto, podemos acceder a sus miembros con puntero->miembro.

Operadores de Obtención de Información sobre Tipos: Para averiguar el tipo de una


variable, usamos el operador sizeof(variable). Nos devolverá un objeto de
tipo System.Type. Si queremos hacer una comparación, usamos algo como esto:

 (expresion) is TIPO

que, como es lógico, nos devolverá un true o un false.

Operadores de Conversión: Para convertir el tipo de un objeto en otro, precedemos el


objeto que queremos cambiar con el tipo al que queremos convertir, entre paréntesis,
de esta forma:

 variable1 = (int) variable2;

De esta forma, variable2 será tratada como si fuera un dato de tipo int, aunque no lo
sea.

Veamos un ejemplo del uso de los operadores unarios:

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);

Console.WriteLine("Valor final de unario: {0}", unario);

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);
}
}

Otro ejemplo, ahora del uso de operadores binarios:

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

Al igual que en otros lenguajes de programación, tendremos varias posibilidades para


comprobar una condición:

comprobar una condición y en función de ésta realizar una acción

comprobar una condición y en función de ésta realizar una acción u otra (excluyentes)

comprobar varias condiciones para realizar varias acciones diferentes (excluyentes)

Esta estructura es muy parecida a la utilizada en C. La sintaxis de la instrucción if es la


siguiente:

if (expresión) {
instrucción o bloque de intrucciones 1;
}
[else {
instrucción o bloque de intrucciones 2;
} ]

El programa evalúa la expresión. Cuando esta expresión resulta verdadera, se ejecuta la


instrucción o el bloque de instrucciones 1. Por contra, cuando es falsa de ejecuta la
instrucción o bloque de instrucciones 2. Esta última parte es opcional. Para representar una
estructura de varios casos se utilizará la sintaxis siguiente:

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;
}

Veamos un ejemplo del uso del if/else

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);

// comparación para una toma de decisión SIMPLE


if (myInt > 0) {
Console.WriteLine("el número {0} es mayor que cero",
myInt);
}

// comparación para una toma de decisión SIMPLE


if (myInt < 0)
Console.WriteLine("el número {0} es menor que cero",
myInt);

// comparación para tomar UNA DE DOS decisiones


if (myInt != 0) {
Console.WriteLine("el número {0} es distinto de cero",
myInt);
} else {
Console.WriteLine("el número {0} es igual a cero",
myInt);
}

// 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 ( { } ).

Estructuras condicionales: switch

De forma similar a la estructura if/else/if/else, podemos utilizar la estructura switch.

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>:

Si la expresión de un caso concreto coincide con la evaluación de la primera expresión, se


ejecutan las instrucciones que hay a continación; sino, se pasa a comprobar el siguiente caso.
Cada caso debe terminar con la instrucción break; o goto etiq para terminar el switch y evitar
que se ejecuten las instrucciones correspondientes al siguiente caso.

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

La instrucción while ejecuta iterativamente un bloque de instrucciones mientras una


expresión sea válida, evaluando la comprobación en cada iteración. Cuando la prueba es
válida, se ejecuta la instrucción o el bloque de instrucciones delimitado por las llaves. La
sintaxis de esta instrucción es:

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;

while (myInt < 10) {


Console.Write("{0} ", myInt);
myInt++;
}
Console.WriteLine();
}
}
do-while

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).

Veamos un ejemplo de lo que hemos comentado:

using System;

class bucleWHILE {
public static void Main() {
int myInt = 100;

while ( myInt < 10 ) {


Console.Write("{0} ",myInt);
myInt++;
}
Console.WriteLine();
}
}

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

La instrucción for permite ejecutar iterativamente un conjunto de instrucciones. La sintaxis


de la instrucción for es:

for (inicial_exp; test_exp; incremento_exp) {


instrucción o bloque de intrucciones;
}

donde:

 inicial_exp es la instrucción que inicializa el bucle. Consiste generalmente en la


asignación de un valor a una variable que permite controlar el número de iteraciones.
 test_exp es la expresión evaluada en cada iteración. Cuando esta expresión es
verdadera, el bloque de instrucciones se ejecuta.
 incremento_exp es la expresión que permite la actualización de la variable que
controla el número de iteraciones.

A continuación veremos un ejemplo de esta estructura para iniciarnos en su manejo:

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:

foreach (TIPO var in lista) {


instrucción o bloque de instrucciones a ejecutar sobre var
}

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

En Visual Studio vamos a dar click en menú File  New  Project

En la Ventana emergente le damos click a Window, seleccionamos la opción “Console


Application” y espacio Name colocamos el nombre del proyecto que para el Ejemplo es
“Demo 1 20483” y luego presionamos el botón OK
Fijese que se abre nuestro proyecto con un método Main, en el cual escribiremos el siguiente
ejemplo de cómo declarar y utilizar las variables:
Ejecutamos haciendo click en el botón Star

El resultado mostrado en pantalla sería el siguiente:

Luego de haber verificado el contenido de nuestra solución procederemos a comentar nuestro


código, sombreando el mismo y dando click al botón comentar.
Así nos aseguramos que la codificación no se ejecute al correr la solución.

Luego de esto escribimos el ejemplo para los operadores:

Al levantar la solución fijese que solo se ejecuta la acción a partir de los Operadores

Ahora comentaremos el código anterior:


Y colocaremos el ejemplo de concatenación:

Corremos la solución:
Y el resultado es el siguiente:

Como en los ejemplos anteriores, comentaremos el código y luego procederemos a escribir


el ejemplo de las sentencias de Decisión:
Desplegamos la pantalla:
Comentaremos nuestro código anterior y procederemos a generar el ejemplo de Interacción:
Antes de ejecutar colocamos un punto de interrupción en el titulo de foreach:
Al ejecutar fijese que VS coloca una ruptura en el punto correspondiente
En referencia names, le vamos a dar click derecho al mouse y en la ventana emerefente
cliqueamos en la definición “Agregar a Inspección”

Fijese que en la ventana inspección se carga el arreglo correspondiente a “names” aquí


podemos verificar los valores acorde a la depuración
Dandole click a F11, nuestra solución correra paso a paso y posteriormente se procedrá cargar
los valores en nuestro arreglo.

Y la salida por la pantalla es la siguiente:


Módulo 2: Creación de métodos de control de
excepciones y aplicaciones de control
Procedimientos y funciones

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

La visibilidad de un procedimiento viene determinada por la


declaración private, public o internal. Por defecto si no se indica nada se entiende que es
public.

void VerResultado() {
Console.WriteLine("¡¡¡Ganador!!!");
}

Función

La función devuelve un resultado al código invocante. La ejecución de return provoca la


salida de la 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).

public tipoDeLa Propiedad nombrePropiedad {


get {
...
//código que se ejecuta cuando se lee la propiedad
...
return variable;
}
set {
...
//código que se ejecuta durante la asignación de una propiedad
//existe una variable que se declara implícitamente y que contiene
//el valor que se debe asignar a la propiedad
...
variable = value;
...
}
}
Si una propiedad es de sólo lectura o sólo escritura, se eliminará el
bloque set y/o get correspondiente. También podemos implementar automáticamente la
encapsulación cuando no haya tratamiento alguno de la siguiente manera.

public int tasa { get; set; }

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;
}

Cliente c1, c2, c3;


c1.codigo = 200;
c1.nombre = "Juanjo";
c1.apellido = "Pedraza";

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.
}

Para que el código anterior funcione se podría hacer esto:

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;
}
}

Argumentos de los procedimientos y funciones

Por valor:

public static double CalculoNETO (double Pbruto, double Tasa) {

return Pbruto * (1+(Tasa/100));

...

double PrecioBruto = 100;

double PrecioNeto;

PrecioNeto = TestEstructura.CalculoNETO(PrecioBruto, 5.5);

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) {

iva = Pbruto * (Tasa/100);

return Pbruto+iva; }

...

double PrecioBruto = 100;

double PrecioNeto;

double importeIva=0;

PrecioNeto = TestEstructura.CalculoNETO(PrecioBruto, 5.5, ref importeIVA);

Console.WriteLine("Precio neto: {0}", PrecioNeto);

Console.WriteLine("Importe iva: {0}", importeIva);

Ejemplo de procedimiento con número de parámetros indeterminado.

public staic double media(params double[] notas) {

double suma=0;

foreach (double nota in notas) {

suma = suma + nota;

}
return suma /notas.Length;

Y alguna de las llamadas a esta función “media” serían:


Resultado = media(1,4,67,233);
Resultado = media(23,33);

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,"$");

En cambio esto daría error:


calculoNeto(10, ,"$");
Parámetros nominados:
Podemos hacer la llamada a la función sin tener en cuenta el orden siempre que nominemos
los parámetros. Por ejemplo:
calculoNeto(divisa:"$",Pbruto:250);
incluso podemos combinar los parámetros por posición y por nominación
calculoNeto(10, divisa: "$");
pero hay que seguir la regla de que un parámetro nominado sólo puede ser utilizado después
de los parámetros por posición, el siguiente ejemplo fallaría:
//daría error
calculoNeto(10,Tasa: 5.5 ,"$");

Excepciones en C# (CSharp)

Cuando durante la ejecución de un programa ocurre un error que impide su continuación,


por ejemplo, una entrada incorrecta de datos o un formato de salida no correcto, C# lanza
una excepción, que si no se captura da lugar a un mensaje de error y detiene la ejecución
(las excepciones se lanzan, no ocurren). Ahora si lo que deseamos es que la ejecución del
programa no se detenga, habrá que capturarla y manejarla adecuadamente en un intento de
reanudar la ejecución.

Las excepciones en C# son objetos de subclases de Exception. Por ejemplo, el espacio de


nombres System define la clase base SystemException para las excepciones predefinidas;
por ejemplo: aritméticas, de formato, intentar acceder a un elemento de una matriz con un
índice fuera de límites, etc. Para capturar una excepción hay que hacer dos cosas: una,
poner a prueba el código que puede lanzar excepciones dentro de un bloque try; y dos,
manejar la excepción cuándo se lance, en un bloque catch. Por ejemplo:

try {

//Código que puede lanzar una excepción

Console.WriteLine("{0:f} + {1:f} = {2:d}", a, b, c);

} catch(Exception e) {

//Manejar una excepción de la clase Exception

System.Console.WriteLine("Error: " + e.Message);

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 {

public static void Main(string[] args) {

double a = 10, b = 20, c = 0;

try {

c = a + b;

Console.WriteLine("{0:f} + {1:f} = {2:d}", a, b, c);

} catch(FormatException) {

Console.WriteLine("Ha ocurrido un error de formato");

} catch(Exception e) {

Console.WriteLine("Error: "+ e.Message);

DEMOSTRACIÓN

En Visual Studio vamos a dar click en menú File  New  Project


En la Ventana emergente le damos click a Window, seleccionamos la opción “Console
Application” y espacio Name colocamos el nombre del proyecto que para el Ejemplo es
“Demo 2 20483” y luego presionamos el botón OK
En la ventana de código, debajo del método Main() escribiremos los métodos siguientes:

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

La salida en pantalla la visualizaríamos de la siguiente forma:


Módulo 3: Desarrollar el código para una
aplicación gráfica

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.

string saludo = "hola";


const double pi = 3.1415;

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:

Tipo Valor por defecto

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

static void Main()


{
Variable[] variable = new Variable[2]; // declaramos
un array
Console.WriteLine(variable[1]); // ok, valor
por defecto

Variable v;
console.WriteLine(v); // error, v
sin asignar
}
}

Tipos valor y tipos referencia

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: enum, struct


 Tipos referencia: array, class, delegate, interface
 Tipo puntero

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");

La primera linea declara x como un referencia a un objeto de tipo StringBuilder. La


segunda crea un objeto de dicho tipo y le asigna a x su valor.

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

Las enumeraciones permiten crear un grupo de constantes numericas asociadas a un


nombre. Veamos un ejemplo:

public enum Rumbo {Norte, Sur, Este, Oeste};

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.

Rumbo avion = Rumbo.Sur;

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};

Rumbo barco = Rumbo.Norte | Rumbo.Oeste;

if ((barco & Rumbo.Norte) != 0)


Console.WriteLine("Si vas hacia el Norte hará más
frio");

El atributo [FLAGS] es opcional e informa al entorno de ejecución de que los valores de la


enumeración pueden ser combinados, y deberán ser decodificados adecuadamente en el
depurador y cuando se muestren por la consola.

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;

public enum Logico : byte { Off = 0, On = 1 };

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"

Logico l = (Logico)Enum.Parse(typeof(Logico), "On");


Console.WriteLine(Enum.Format(typeof(Logico), l,
"D")); // imprime "1"
Console.WriteLine(l); // imprime "On"

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; }
}

public void DisplayX()


{
Console.WriteLine("El valor almacenado es: {0}",
X);
}
}

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;
}

public static void classtaker(TheClass c)


{
c.x = 5;
}

public static void Main()


{
TheStruct a = new TheStruct();
TheClass b = new TheClass();
a.x = 1;
b.x = 1;
structtaker(a);
classtaker(b);
Console.WriteLine("a.x = {0}", a.x);
Console.WriteLine("b.x = {0}", b.x);
}
}
Podemos utilizar los atributos StructLayout(LayoutKind.Explicit) and FieldOffset para
indicar como deben colocarse en memoria los diferentes campos de una estructura. De esta
forma podemos conseguir el equivalente de una union de C/C++.

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 clase Array, contenida en el namespace System, provee toda la funcionalidad necesaria


en las colecciones para manipular los datos. Esta es una clase abstracta, por lo cual no es
posible crear instancias de la misma, sino que sirve como clase base (de la que otras clases
heredan) para las demás clases que representan colecciones en .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 (=).

En el ejemplo anterior, se han almacenado 4 cadenas de texto (strings) en el vector texto


declarado e instanciado anteriormente. Cada string se ha almacenado en una posición del
vector diferente. En caso de almacenar un string en una posición donde ya se había
almacenado anteriormente algo, el valor antiguo será sobrescrito por el nuevo valor.

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.

int [] a =new int[2]; //Una dimensión

int [ , ] a = new int [2,3]; //Dos dimensiones

int [ , , ]a = new int[2,3,4]; //Tres dimensiones

Las propiedades mas importantes que exponen los vectores se presentan a continuación:

Length: Indica la longitud total del vector (En todas las dimensiones).

Rank: Indica el numero total de dimensiones del vector.

Para obtener la longitud total de un determinada dimensión, se debe utilizar el método


GetLength que recibe como parámetro un entero indicando el índice de la dimensión de la
cual se desea conocer la longitud (La primera dimensión tiene como índice 0).

Aunque es posible crear vectores de mas de una dimensión, no es recomendable utilizar


mucho esta técnica en las aplicaciones ya que la complejidad de nuestro algoritmo se
incrementa considerablemente, teniendo en cuenta que el numero de iteraciones necesarias
para recorrer todas las posiciones del vector aumenta de manera exponencial en función del
numero de dimensiones. Generalmente el numero máximo de dimensiones que se utiliza en
un vector es 2 cuando se necesita trabajar con matrices de datos.

A continuación se muestra un ejemplo de una matriz de datos:


Otra de las colecciones que se pueden crear con los vectores es un jagged array o en español
un array de arrays. Se puede pensar en un jagged array como un vector de una dimensión,
donde en cada posición en lugar de almacenar un tipo de dato especifico, almacena otro
vector.

En esta imagen se ilustra gráficamente como se vería un jagged array. En


este ejemplo, se tiene un vector exterior de 4 posiciones y en cada una de
las posiciones almacena un vector también de 4 posiciones. No es
obligación que todos los vectores sean de la misma longitud.

Para declarar un array de arrays en C#, se debe utilizar la siguiente sintaxis:

int [][] jagged;

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.

int [][] jagged = new int[3][];


En el ejemplo anterior se ha definido un array de tres posiciones, donde se almacenara en
cada una de ellas, un array de enteros (indicado por el segundo par de corchetes). Luego de
inicializar el vector contenedor, podremos crear los vectores internos como se muestra a
continuació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.

ArrayList arrayList=new ArrayList();


El constructor de la clase ArrayList acepta también un parámetro tipo entero que indica la
capacidad inicial del objeto que se esta creando.

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.

Si es necesario quitar elementos de la colección, se debe usar el método remove, removeAt


o RemoveRange, los cuales eliminan el objeto pasado como parámetro, o un elemento en una
posición especifica, o un grupo de elementos respectivamente.

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 .

A continuación se muestra un ejemplo del uso de estas propiedades:


Para acceder a los elementos contenidos por la colección se puede hacer mediante el uso de
índices o mediante la instrucción foreach.

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.

Las colecciones Stack y Queue se encuentran en el espacio de nombres System.Collections


como todas las colecciones no genéricas.

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.

Para obtener un elemento de la colección, contamos con dos opciones diferentes:

1. Obtener el elemento indicado según el comportamiento de la colección sin quitarlo de la


colección. Esto se logra mediante el método Peek de cada colección.

2. Obtener un elemento de la colección, quitándolo de la misma. Esto se logra mediante el


método Pop de la pila (Stack) o el método Dequeue de la cola (Queue).

Ejemplo del Stack:

Resultado:

Ejemplo del Queue:


Este par de colecciones deben ser usadas cuando nos interesa tener control sobre el orden en
que los elementos son obtenidos de las mismas. La pila se debe usar cuando queremos
obtener los elementos en el orden inverso al cual fueron ingresados. Mientras que la cola
debe ser utilizada cuando queremos obtener los elementos en el mismo orden que fueron
ingresados.

DEMOSTRACIÓN

En Visual Studio vamos a dar click en menú File  New  Project

En la Ventana emergente le damos click a Window, seleccionamos la opción “Console


Application” y espacio Name colocamos el nombre del proyecto que para el Ejemplo es
“Demo 3 20483” y luego presionamos el botón OK
En nuestro editor de código, arriba del método Main(), vamos a crear una clase de nombre
“Empleado”:
A continuación tipiaremos una enumeración de nombre “EstadoCivil”:

Y luego una Estructura de nombre “Producto”:

En el método Main() codificaremos el código para ejecutar nuestra solució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:

Comentamos el código del ejemplo anterior y llamamos en el método Main() al


procedimiento Legth

La salida de la pantalla seria la siguiente:


Seguimos con otro ejemplo de arreglos:
Comentamos el código anterior en el método Main() y hacemos el llamado al procedimiento
RV()
Colocamos un punto de interrupción y corremos paso a paso

Agregamos la referencia myArray a la ventana de inspección:

Al correr se carga la información en nuestro arreglo:


Una vez que pasa por la instrucción de Reverse

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

Agregamos el mismo a la ventana de inspección:

Fijese que se cargaron sus valores correspondientes:


Una vez que pase por la sentencia Sort

Ordenará los valores alfabéticamente

La salida de la pantalla seria la siguiente:


Ahora codificaremos el ejemplo para el ArrayList:
Seguimos Comentando el código anterior y hacemos el llamado al procedimiento en el
método Main().

Procedemos a depurar el código colocando las referencias correspondientes en la ventana de


inspección:

Cuando empezamos a correr con F11 nuestro ArrayList empieza agregar Valores con la
sentencia Add
Seguimos con el siguiente Array

La salida de la pantalla seria la siguiente:


Seguimos con el ejemplo de las colas
Comentamos nuevamente el código anterior y hacemos llamado al procedimiento de la cola

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

Para remover algún valor se utiliza la instrucción Pop


La salida de la pantalla seria la siguiente:
Y por último codificamos el código para el HashTable
Como hicimos en los ejemplos anteriores comentamos nuevamente el código anterior y
hacemos llamado al procedimiento del HashTable

Comenzamos depurando el código


Para remover algún valor de la tabla

La salida de la pantalla seria la siguiente:

S-ar putea să vă placă și