You are on page 1of 91

INSTITUTO TECNOLOGICO DE NOGALES

INGENIERIA EN SISTEMAS COMPUTACIONALES

APUNTES PARA LA MATERIA


DE PROGRAMACION ORIENTADA A OBJETOS

POR: M.C. LUCAS GALAZ VALLES

EJEMPLO................................................................................................................56
BLOQUE TRY..........................................................................................................80
BLOQUE CATCH....................................................................................................80
BLOQUE FINALLY.................................................................................................82
CAPTURA DE ERRORES NO CONTROLADOS...................................................83
........................................................................................................................................................................86
Clases usadas para E/S de archivos...........................................................................................................86
Clases usadas para leer y escribir en secuencias.......................................................................................86
Clases comunes de secuencias de E/S..............................................................................................................87
E/S y seguridad.................................................................................................................................................87
8.2 Operaciones bsicas en archivos texto y binario.....................................................................................87
8.2.1 Crear y 8.2.2 Abrir................................................................................................................................87
8.2.4 Lectura y escritura, 8.2.5 Recorrer y 8.2.3 Cerrar................................................................................89

I Arreglos unidimensionales y multidimensionales


1.1 Arreglos unidimensionales listas (vectores)
Un arreglo (matriz, vector, lista) es un tipo especial de objeto compuesto por una
coleccin de elementos del mismo tipo de datos que se almacenan consecutivamente en
memoria. La Figura 1.1 es un arreglo de 10 elementos de tipo double y se representa por
un nombre, lista, con ndices o subndices.
lista [0]
lista [1]
lista [2]
lista [3]
lista [4]
lista [5]
lista [6]
lista [7]
lista [8]

Lista es el nombre
[ i ] es el ndice

Figura 1.1. El arreglo lista de 10 elementos, con ndices de 0 a 8.


Otra forma de representar grficamente un arreglo es en forma de lista horizontal:
lista[0]

lista[1]

lista[2]

lista[3]

Figura 1.2. Arreglo lista de 10 elementos.


Los arreglos pueden ser unidimensionales (Figuras 1.1 y 1.2) conocidos tambin como
listas o vectores y multidimensionales conocidos tambin como tablas o matrices, que
pueden tener dos o ms dimensiones.
Ejemplo
El arreglo temperaturas de ocho elementos consta de los siguientes componentes:
temperaturas [0]
temperaturas [1]
temperaturas [2]
temperaturas [3]
temperaturas [4]
temperaturas [5]
temperaturas [6]
temperaturas [7]
Regla: Un arreglo tiene un nombre o identificador, un ndice que es un entero encerrado
entre corchetes, un tamao o longitud, que es el nmero de elementos que se pueden
almacenar en el arreglo cuando se le asigna espacio en memoria. Un arreglo se
representa por una variable arreglo y se debe declarar, crear, iniciar y utilizar.
Conceptos basicos
El proceso que se puede realizar con arreglos abarca las siguientes operaciones:
declaracin, creacin, inicializacin y utilizacin. Las operaciones de declaracin, creacin
e inicializacin son necesarias para poder utilizar un arreglo.
Declaracin
La declaracin de un arreglo es la operacin mediante la cual se define su nombre con un
identificador vlido y el tipo de los elementos del arreglo. La sintaxis para declarar un
arreglo adopta el siguiente formato:
tipoDato[ ] nombreArreglo
Ejemplo

double[ ] miLista;
float[ ] temperatura;

Se declara un arreglo miLista de tipo double.


Se declara un arreglo temperatura de tipo float

Las declaraciones no especifican el tamao del arreglo que se especificar cuando se


cree el mismo.
Creacin
Un arreglo C# es un tipo referencia derivado de la clase base System.Array y la
declaracin no asigna espacio en memoria para el arreglo. No se pueden asignar
elementos al arreglo a menos que el arreglo est ya creado. Despus que se ha
declarado un arreglo se puede utilizar el operador new para crear el arreglo con la sintaxis
siguiente:
nombreArreglo = new tipoDato[tamao];
nombreArreglo es el nombre del arreglo declarado previamente, tipoDato es el tipo de
dato de los elementos del arreglo y tamao es la longitud o tamao del arreglo y es una
expresin entera cuyo valor es el nmero de elementos del arreglo.
Ejemplo
miLista = new double [8];
// arreglo miLista de 8 elementos
temperatura = new float [30]; // arreglo temperatura de 30 elementos
Regla: Se pueden combinar la declaracin y la creacin de un arreglo con una sola
sentencia
tipoDato [ ] nomb,reArreglo = new tipoDato[tamao};
Ejemplo
double[ ] miLista = new double[8];
float[ ] temperatura = new float[30];
Precaucin: Una vez que un arreglo se ha creado su tamao no se puede modificar.
Inicializacin y utilizacin
Cuando se crea un arreglo, si no se inicializa en el momento en el que se declara, a los
elementos se les asigna su valor por defecto. A los elementos del arreglo se accede a
travs del ndice. Los ndices del arreglo estn en el rango de 0 a tamao-1. As miLista
contiene 8 elementos y sus ndices son 0,1,2, . . . ,7.
Cada elemento del arreglo se representa con la siguiente sintaxis:
nombreArreglo[ndice];

Ejemplo
miLista [7] representa el ltimo elemento del arreglo
Regla: En C#, un ndice del arreglo es siempre un entero que comienza en cero y termina
en tamao-1.
Precaucin: Tenga cuidado ya que, al contrario que en otros lenguajes de programacin,
los ndices siempre se encierran entre corchetes: temperaturas [15] .
Un arreglo completo se puede inicializar con una sintaxis similar a:
double[ ] miLista = { 1.5, 2.45, 3.15, 7.25, 8.4 };
esta sentencia crea el arreglo miLista que consta de cinco elementos. Tambin podran
haberse utilizado
double[ ] miLista = new int[5] { 1.5,2.45,3.15,7.25,8.4 };
double[ ] miLista = new int[ ] { 1.5, 2.45, 3.15, 7.25, 8.4 };
Clculo del tamao
El tamao de un arreglo se obtiene con la propiedad Length. As, por ejemplo, si se crea
un arreglo miLista, la sentencia miLista.Length devuelve el tamao del arreglo miLista
(10, por ejemplo).
Utilizacin de los elementos del arreglo
Las variables que representan elementos de un arreglo se utilizan de igual forma que
cualquier otra variable. Por ejemplo:
int[ ] n = new int[50];
int j =0;
//...
j = n[j] + n[10];
Algunas sentencias permitidas en C#
temperatura [5] = 45;
temperatura [8] = temperatura [5] + 10;
Los elementos de un arreglo se suelen procesar mediante bucles (por ejemplo for) por las
siguientes razones:

Todos los elementos del arreglo son del mismo tipo y tienen las mismas
propiedades; por esta razn, se procesan de igual forma y repetidamente utilizando
un bucle.

El tamao de un arreglo se conoce, por lo que resulta muy conveniente el empleo


de un bucle for.
Otra forma muy adecuada para recorrer los elementos de un arreglo es la
instruccin foreach.

Ejemplos
1. El bucle for siguiente introduce valores en los elementos del arreglo. El tamao del arreglo
se obtiene en miLista.Length.
for (int i = i; i < miLista.Length; i++)
miLista[i] = i;
2. foreach (double i in miLista)
{
MessageBox.Show("El valor es:"+ i);
}
3. int[ ] cuenta = new int[100];
int i;
for (i = 0; i < cuenta.Length; i++)
cuenta[i] = 0;
COPIA DE ARREGLOS
Con frecuencia se necesita duplicar un arreglo o bien una parte de un arreglo. Existen dos
mtodos para copiar arreglos: copiar elementos individuales utilizando un bucle y utilizar
el mtodo Arreglo.Copy.
Mtodo 1
Un mtodo para copiar arreglos es escribir un bucle que copia cada elemento desde el
arreglo origen al elemento correspondiente del arreglo destino.
Ejemplo
Este cdigo copta arregloFuente en arregloDestino
Nota: Crear un proyecto, poner un botn en la forma, hacer doble clic en el botn,
declarar e inicializar los arreglos, escribir el siguiente cdigo.
for (int i = 0; i < arregloFuente.Length; i++)
arregloDestino[i] = arregloFuente[i];
Mtodo 2
Otra forma de copiar arreglos es usar el mtodo Arreglo.Copy que tiene los siguientes
formatos:
public static void Copy(Array arregloOrigen,Array arregloDestino, int longitud)

public static void Copy (Array arregloOrigen, int pos_ini, Array arregloDestino, int pos_fin,
int longitud)
La sintaxis del mtodo Copy ( ) es:
Array.Copy(arregloOrigen, arregloDestino, longitud);
Array.Copy(arregloFuente, pos_ini, arregloDestino, pos_fin, longitud) ;
Pos_ini = Posicin donde comienza la copia.
Pos_fin = Posicin donde comienza el almacenamiento.
Longitud = Nmero de elementos a ser copiados.
Ejemplo
object[ ] arregloOrigen = {4, 5, 1, 25, 100};
int[ ] arregloDestino1 = new int[arregloOrigen.Length];
Array.Copy(arregloOrigen,0, arregloDestino1,0, arregloOrigen.Length);
for (int i = 0; i < arregloDestino1.Length; i++)
MessageBox.Show(arregloDestino1[i]);
int[ ] arregloDestino2 = new int[arregloOrigen.Length];
for (int i = 0; i < arregloOrigen.Length; i++)
arregloDestino2[i] = (int)arregloOrigen[i];
for (int i = 0; i < arregloDestino2.Length; i++)
MessageBox.Show(arregloDestino2[i]);

PASO DE ARREGLOS COMO PARMETROS


Los arreglos pueden ser pasados como parmetros por valor y tambin como parmetros
por variable, mediante el uso de los modificadores ref o out. Cuando un arreglo se pasa a
un mtodo como parmetro por valor es posible cambiar los valores de los elementos del
arreglo en el interior del mtodo y devolverlos modificados al lugar desde donde se
efectu la llamada. Esto es debido a que los arreglos son tipos referencia. y, al pasar un
arreglo por valor lo que se efecta slo es una copia de la referencia que sealaba al
arreglo.
Ejemplo
public class ArrComoParametros1
{
private static void Cambiar(int[ ] arr)
{
for (int i = 0; i < arr.Length; i++)
arr[i] = arr[i] + 2;
}

public void Principal( )


{
int[ ] arr = {1,4,5};
Cambiar(arr);
for (int i = 0; i < arr.Length; i++)
MessageBox.Show({0} +arr[i]);
}
}
El resultado de la ejecucin es: 3 6 7
Cuando se pasa un arreglo por referencia a un mtodo, no slo los cambios en los valores
de los elementos del arreglo, sino todos los cambios que tengan lugar dentro del mtodo
afectan la matriz original.
Ejemplo
El siguiente ejemplo utiliza el mtodo Aadir para incrementar el nmero de elementos de
un arreglo. Es necesario que al mtodo Aadir se le pase el arreglo precedido por el
modificador ref, para que la referencia se pueda devolver modificada.
class ArrComoParametros2
{
static void Aadir (ref int[ ] arr, int elemento)
{
int[ ] otro = arr;
int nueva longitud = arr.Length+1;
// La referencia pasa a apuntar a un nuevo lugar
arr = new int[nuevalongitud];
arr[nuevalongitud-l] = elemento;
for (int i=0; i < otro.Length; i++)
arr[i] = otro[i];
}
public void Principal( )
{
int[ ] arr = {1,4,5};
Aadir(ref arr, 8);
for (int i=0; i<arr.Length; i++)
MessageBox.Show({0} +arr[i]);
}
El resultado de la ejecucin es: 1 4 5 8
1.2 Arreglo bidimensional
En C# los arreglos pueden tener ms de una dimensin. As, las tablas o matrices se
representan mediante arreglos bidimensionales. Un arreglo bidimensional se declara:
tipo [ , ] nombre;

y se crea o instancia mediante:


nombre = new tipo[valor1, valor2];
En la instanciacin, a cada dimensin de la matriz se le asocia una longitud (valor1 y
valor2), que es un nmero entero mayor o igual que cero. Tambin es posible combinar
ambas operaciones, declaracin e instanciacin, en una nica sentencia.
tipo[ , ] nombre = new tipo[valor1, valor2];
Las tablas de valores constan de informacin dispuesta en filas y columnas. Para
identificar un elemento de una tabla concreta se deben especificar dos subndices (por
convenio, el primero identifica la fila del elemento y el segundo identifica la columna del
elemento). Si la longitud de una dimensin es valor1 los subndices pueden ir de cero a
valor1-1 en dicha dimensin.

(a) Arreglo de una dimensin

0
1
2
3
0
1
2
3
(b) Arreglo bidimensional
Figura 1.3. Arreglos: (a) Una dimensin; (b) Dos dimensiones.
La Figura 1.4 ilustra un arreglo de doble subndice, a, que contiene tres filas y cuatro
columnas (un arreglo de 3 x 4). Un arreglo con m filas y n columnas se denomina arreglo
m_por _n.
Columna 0

Columna 1

Columna 2

Columna 3

Fila 0
Fila 1
Fila 2
a[0,0]
a[1,0]
a[2,0]

a[0,1]
a[1,1]
a[2,1]

a[0,2]
a[1,2]
a[2,2]

a[0,3]
a[1,3]
a[2,3]

Figura 1.4. Un arreglo de dos dimensiones con tres filas y cuatro columnas.
Cada elemento del arreglo a se identifica como tal elemento mediante el formato a [i, j]; a
es el nombre del arreglo, e i,j son los subndices que identifican unvocamente la fila y la
columna de cada elemento de a; obsrvese que todos los elementos de la primera fila
comienzan por un primer subndice de 0 y los de la columna cuarta tienen un segundo
subndice de 3 (4 -1).
La inicializacin puede efectuarse en el momento de la declaracin especificando los
valores iniciales entre { }.
Ejemplos
1. Un arreglo b de 2x2 dimensiones se puede declarar e inicializar con:
int[ , ] b= {{5,6}, {7,8}};
b [0, 0]
(5)
b [1, 0]
(7)

b [0,1]
(6)
b [1,1]
(8)

y tambin con
int[ , ] b=new int[2, 2] {{5, 6}, {7, 8}};
o con
int[ , ] b=new int[ , ] {{5, 6}, {7, 8}};
2. La declaracin
int[ , ] b = {{4,5,6}, {7,8,9}};
crea un arreglo de dos filas y tres columnas, en el que la primera fila contiene 4 , 5, 6 .
Un medio rpido para asignar valores a los elementos de un arreglo de dos dimensiones
es utilizar bucles for
for (int x = 0; x < 3; ++x) {
for (int y = 0; y < 3; ++y) {
tabla[x , y] = 5;
}
}
Los bucles anidados funcionan del modo siguiente. El bucle externo, el bucle x, arranca
estableciendo x a 0. Como el cuerpo del bucle x es otro bucle, a continuacin arranca
dicho bucle interior, bucle y, fijando y a 0. Todo esto lleva al programa a la lnea que

10

inicializa el elemento del arreglo tabla[0, 0] al valor 5. A continuacin el bucle interior


establece y a 1, y con ello tabla[0,1] toma el valor 5. Cuando termina el bucle interno el
programa bifurca de nuevo al bucle externo y establece x a 1. El bucle interno se repite de
nuevo, pero esta vez con x igual a 1, y tomando y valores que van de 0 a 2. Por ltimo,
cuando ambos bucles terminan todos los elementos del arreglo habrn tomado el valor 5.
Ejemplo
1. Asignacin de valores a cada uno de los elementos de un arreglo
0

1
0
4
8
12

2
1
5
9
13

3
2
6
10
14

3
7
11
15

Tras crear y declarar el arreglo tabla: int[ , ] tabla = new int[3,3];


El listado siguiente recorre el arreglo y asigna valores a sus elementos:
for (int x = 0; x < 3; ++x)
for (int y = 0; Y < 3; ++y)
tabla[x,y] = x*4 + y;

2. Declarar, crear y llenar un arreglo de 8 filas y 10 columnas con 80 enteros de valor


cero
int[ , ] numeros;
numeros = new int[8,10];
for (int x = 0; x < 8; ++x)
for (int y = 0; Y < 10; ++y)
numeros[x,y] = 0;
Anlogamente, tambin es posible leer valores para cada uno de los elementos de un
arreglo de dos dimensiones anidando dos bucles for, uno externo y otro interno. El
recorrido de un arreglo para mostrar la informacin almacenada en l, tambin se efecta
utilizando bucles for anidados, ya que los bucles facilitan la manipulacin de cada uno de
los elementos de un arreglo.
Ejercicio
La siguiente aplicacin crea e inicializa un arreglo de dos dimensiones con 10 columnas y
15 filas. A continuacin presenta en pantalla el contenido del arreglo.
public class Tabla1 {

11

static int[ , ] tabla;


static void Llenar()
{
tabla = new int[15,10];
for (int x = 0; x < 15; x++)
for (int y = 0; y < 10; y++)
tabla[x,y] = x * 10 + y;
}
static void Mostrar()
{
for (int x = 0; x < 15; x++)
for (int y = 0; Y < 10; y++)
MessageBox.Show(tabla[x,y];
}
public static void Principal ()
{
//Creacin, instanciacin e inicializacin en una nica sentencia
Llenar() ;
Mostrar() ;
}
A continuacin se muestra un formulario que crea e inicializa un arreglo de dos
dimensiones con 10 columnas y 15 filas, y presenta el contenido del arreglo, de forma que
se puede ver que el arreglo contiene realmente los valores a los que se ha inicializado.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace unFormulario
{
public partial class Form1 : Form
{
int[,] tabla;
public Form1()
{
InitializeComponent();
}
public void UnFormulario()
{
Inicializar();

12

Text = "2 aplicacin Windows";


Size = new Size(400, 400);
CenterToScreen();
}
private void Inicializar()
{
tabla = new int[15, 10];
for (int x = 0; x < 15; x++)
for (int y = 0; y < 10; y++)
tabla[x, y] = x * 10 + y;
}
protected override void OnPaint(PaintEventArgs e)
{
UnFormulario();
base.OnPaint(e);
Graphics g = e.Graphics;
for (int x = 0; x < 15; x++)
for (int y = 0; y < 10; y++)
{
string s = tabla[x, y].ToString();
Font fuente = new Font("Arial", 10);
SolidBrush pincel = new SolidBrush(Color.Green);
PointF lugar = new PointF(50 + y * 25, 50 + x * 15);
g.DrawString(s, fuente, pincel, lugar);
}
}
}
}
El paso como parmetros de las tablas o matrices se efecta de forma anloga a
como se efecta el de arreglos unidimensionales. Por ejemplo:
int[,] tabla = {{3,8}, {4, 7},{5, 6},{9, 2}};
Cambiar(tabla);
static void Cambiar(int[,] t)
//cabecera del mtodo
Un arreglo multidimensional, con ms de dos dimensiones, se declara, crea e inicializa de
la misma forma que un bidimensional. La asignacin, lectura y presentacin de cada uno
de los valores almacenados tambin se realiza mediante bucles anidados.
//Declaracin: se usan comas para separar cada dimensin del arreglo.
tipo[,,] nombre;
//arreglo tridimensional
//Instanciacin
nombre = new tipo[valor1, valor2, valor3];

13

Ejemplo
//Creacin, instanciacin e inicializacin en una nica sentencia
int[,,] numeres = new int[2,3,4] {{{1,2,3,4},{2,3,4,5},{3,4,5,6}},{{6,S,4,3},{S,4,3,2},{4,3,2,1}}};
MessageBox.Show(numeros.Length);
// Resultado: 24
MessageBox.Show(numeros.Rank);
// Resultado: 3
MessageBox.Show(numeros.GetLength(0));
// Resultado: 2
Importante: La propiedad Arreglo.Rank devuelve el nmero de dimensiones de
un.arreglo.
El mtodo Arreglo.GetLength devuelve el nmero de elementos en una determinada
dimensin de un arreglo.
1.3 Arreglo Multidimensional
Los elementos de un arreglo en C# pueden ser de cualquier tipo, incluidos otros arreglos.
En los arreglos de arreglos cada fila puede contener un nmero diferente de columnas y
se pueden asignar dinmicamente, como sigue:
int[ ] [ ] b;
b = new int[3] [ ];
b[0] = new int[5];
b[1] = new int[4];
b[2] = new int[3];

// asigna filas
// asigna 5 columnas a la fila 0
// asigna 4 columnas a la fila 1
// asigna 3 columnas a la fila 2

La expresin b.Length devuelve el nmero de filas del arreglo, mientras b[1].Length


devuelve el nmero de columnas de la fila 1 del arreglo.
,
MessageBox.Show(b.Length);
MessageBox.Show(b[1].Length);
//Resultado: 3 //Resultado: 4
Ejemplo
Declarar, crear e inicializar un arreglo de arreglos:
int[ ] [ ] matriz = new int[5] [ ];
matriz[0] = new int[ ]{1, 2, 3, 4};
matriz[1] = new int[ ]{3, 4, 5, 6};
matriz[2] = new int[ ]{3, 4};
matriz[3] = new int[ ]{4, 6, 8};
matriz[4] = new int[ ] {S, 8 ,1, 7, 3, 6, 6};
Se puede utilizar tambin una notacin abreviada para declarar e inicializar un arreglo de
arreglos:
int[ ] [ ] matriz = new int [ ] [ ] {
new int[ ] {1, 2, 3, 4},

14

new int[ ] {3, 4, 5, 6},


new int[ ] {3, 4},
new int[ ] {4, 6, 5},
new int[ ] {5, 8 ,1, 7, 3, 6, 6}
};
o bien:
int[ ] [ ] matriz =
{
new int[ ] {l, 2, 3, 4},
new int[ ] {3, 4, 5, 6},
new int[ ] {3, 4},
new int[ ] {4, 6, S},
new int [ ] {5, 8 ,1, 7, 3, 6, 6}
};
Es importante destacar que no se puede omitir el operador new de la inicializacin de los
elementos.
La asignacin de valores a un elemento especfico se puede hacer con sentencias
similares a:
matriz [2] [0] = 9;
Para recorrer un arreglo de arreglos se puede usar una estructura for con una variable de
control, i, que recorra las filas (0 hasta a.Length-1) y otro bucle for con una variable de
control j que recorra los elementos de cada fila (0 hasta a[i].Length-1).
Ejemplo
La siguiente estructura declara, crea, inicializa y muestra el contenido del arreglo a, en el
que cada fila contiene un nmero diferente de columnas
int[ ] [ ] a;
a = new int[3] [ ];
a[0] = new int[5];'
a[1] = new int[7];
a[2] = new int[3];
for (int i = 0; i < a.Length; i++)
for (int j = 0; j < a[i] .Length; j++)
a[i][j]=j+1;

// asigna filas
// asigna 5 columnas a la fila 0
// asigna 7 columnas a la fila 1
// asigna 3 columnas a la fila 2

15

II Mtodos y Mensajes
2.0 Clases y Objetos
2.0.1 Clases
Las clases son estructuras o plantillas que sirven para definir un objeto. En C# una clase
puede tener los siguientes miembros: campos de datos (constantes, campos de slo
lectura o variables), declaraciones de tipos anidadas, eventos, indexadores, operadores,
constructores, destructores, propiedades y mtodos; y, habitualmente, una clase de un
objeto contiene una coleccin de mtodos y definiciones de datos. Si se disea una clase
que representa a un cliente, no se ha creado un objeto. Un objeto es una instancia
(ejemplar, caso) de la clase Cliente y, por consiguiente, puede, naturalmente, haber
muchos objetos de la clase Cliente. La creacin de una variable especfica de un tipo
particular de clase se conoce como instanciacin (creacin de instancias) de esa clase.
Una clase describe la constitucin de un objeto y sirve como plantilla para construir
objetos, especificando la interfaz pblica de un objeto. Una clase tiene un nombre y
especifica los miembros que pertenecen a la clase. Una vez que se define una clase, el
nombre de la clase se convierte en un nuevo tipo de dato y se puede utilizar para:

declarar variables de ese tipo.


crear objetos de ese tipo.

16

El siguiente ejemplo representa una clase Circulo que se utilizar para construir objetos
del tipo Crculo:
class Circulo
{
public double radio =5.0;
public double CaleularSuperficie()
{
return radio*radio*3.141592;
}
}
Esta clase Circulo es, simplemente, una definicin que se utiliza para declarar y crear
objetos Circulo.
Las clases se declaran con el siguiente formato:
class Nombre
{
// cuerpo de la clase
}
El cuerpo de la clase define los miembros. Excepto en el caso de sobrecarga, todos los
miembros deben tener nombres distintos.

2.0.2 Objeto
Un objeto es una coleccin de datos y una serie de rutinas miembros, entre las que
destacan los mtodos. Los objetos representan cosas fsicas o abstractas, pero que
tienen un estado y un comportamiento. Por ejemplo, una mesa, un estudiante, un crculo,
una cuenta corriente, un prstamo, un automvil,... se consideran objetos. As, ciertas
propiedades definen a un objeto y ciertas propiedades definen lo que hace. Las
propiedades que definen el objeto se conocen como campos de datos y el
comportamiento de los objetos, es decir las acciones que deseamos que efecten, se
define como mtodos.
La sintaxis para declarar un objeto es:
NombreClase NombreObjeto;
Circulo myCirculo;
2.1 Atributos Const y Static
Es importante el lugar donde se efecta la declaracin de las variables, pues ste
determina su mbito. Las variables miembros de una clase se denominan campos y,

17

cuando no llevan el modificador static, se asocian a una instancia de la clase, de forma


que cada instancia de la clase tiene una copia de dichas variables, por lo que reciben el
nombre de variables de instancia. Cuando los campos se declaran static se asocian con la
propia clase y reciben el nombre de variables de clase, esto quiere decir que no se har
una copia de estas variables para cada uno de los objetos de la clase y, por lo tanto, su
valor ser compartido por todas las instancias de la misma. Las variables que se declaran
en mtodos, propiedades, o indexadores se denominan variables locales. Las variables
locales slo existirn y se podr hacer referencia a ellas dentro del cuerpo del mtodo,
propiedad o indexador donde han sido declaradas. Adems, en C# es posible agrupar
sentencias simples, encerrndolas entre llaves para formar bloques o sentencias
compuestas y efectuar declaraciones de variables dentro de dichos bloques, pero habr
de tener en cuenta que su mbito ser el bloque en el que han sido declaradas u otros
interiores a l. Es decir, que si un bloque tuviera otros anidados, no se podrn declarar
variables en los bloques interiores con el nombre de otras de mbito exterior.
Ejemplo
int i = 25;
double j = Math.Sqrt(20);
i++;
j += 5;
MessageBox.Show(i+" "+j);
// A continuacin comienza un bloque
{
int aux = i;
i= (int) (j) ;
//es necesaria una conversin explcita
j= aux;
//se efecta una conversin implcita
MessageBox.Show(i+" "+j+" "+aux);
}
//Fin del bloque
MessageBox.Show(i+" "+j); // aux aqu no est definida
Es necesario destacar que la ubicacin en memoria de las variables depende no slo de
su tipo, sino tambin del lugar donde han sido declaradas y las variables miembro de una
clase (campos), aunque sean de un tipo simple, se alojan en el montculo.
Variables fijas y mviles. En C# se dice que una variable es fija cuando no se ve afectada
por el recolector de basura y mvil en caso contrario. Las variables locales son variables
fijas, mientras que los campos de los objetos son variables mviles que el recolector de
basura puede reubicar o liberar.
CONSTANTES
Las constantes son datos cuyo valor se establece en tiempo de compilacin y no puede
variar durante la ejecucin de un programa. En un programa pueden aparecer constantes
de dos tipos: literales y simblicas. Las constantes simblicas o con nombre representan
datos permanentes que nunca cambian y se declaran como las variables, pero
inicializndose en el momento de la declaracin y comenzando dicha declaracin con la
palabra reservada const, que sirve para que el valor asignado no pueda ser modificado.

18

Pueden declararse constantes tanto campos como variables locales. El tipo de una
constante puede ser cualquiera, pero a una constante de un tipo referencia, que no sea
una cadena, slo se le podr asignar el valor null.
Cuando se necesite un nombre simblico para un valor constante que no pueda
establecerse en tiempo de compilacin, en lugar de una constante, se declarar una
variable de slo lectura (readonly). Las constantes de clase se declaran en el cuerpo de la
clase y fuera de todos los mtodos; la declaracin de una constante puede ir acompaada
por un conjunto de atributos, el modificador new y alguno de los siguientes modificadores
de acceso: public, protected internal, protected, internal, o private. Las constantes son
miembros estticos, pero no pueden llevar el modificador static. La sintaxis para declarar
una constante es:
const tipoDato NOMBRE CONSTANTE = valor;
Ejemplos
1. const double PI = 3.141592;
superficie = radio * radio * PI;
2. class Prueba
public const string CADENA = "O."; void Mtodo ( )
{
/ / ...
/ / ...
}
3. using System;
class EjConstSLect
{
//Compara constantes y campos de slo lectura
class ConstSLect
{
//no se puede modificar el valor de una constante
const int X = 1;
//se establece el valor en una declaracin de inicializacin
readonly int y = 200;
public readonly int z;
//Constructores
public ConstSLect()
{
// se establece su valor en el constructor
z = 6;
}
public ConstSLect(int pl, int p2)
{
// se anula el valor establecido en la declaracin de inicializacin
y = pl; z = p2;

19

}
public int Suma()
{
return x+y+z;
}
}
public static void Main()
{
ConstSLect instancial= new ConstSLect();
/* un campo de slo lectura slo puede ser asignado en un constructor o en una
declaracin de inicializacin y la sentencia siguiente sera incorrecta
instancial.z = 4; */
Console.WriteLine(instancial.Suma()) ;
ConstSLect instancia2 = new ConstSLect(2,3);
Console.WriteLine(instancia2.Suma());
}
Las constantes literales son valores de un determinado tipo escritos directamente en un
programa. Dichas constantes podrn ser enteras, reales, lgicas, carcter, cadena de
caracteres, o el valor null.
Constantes enteras
Las constantes enteras representan nmeros enteros con y sin signo. La escritura de
constantes enteras en un programa debe seguir unas determinadas reglas:

No utilizar comas ni signos de puntuacin en nmeros enteros. 123456 en lugar de


123.456.
Cuando un literal entero es vlido para varios tipos, su tipo se elige siguiendo el
siguiente orden int, uint, long, ulong.
Puede aadirse una L o l al final del nmero para especificar que se trata de un
long 123456L.
Puede aadirse una U o u al final del nmero para especificar su pertenencia a los
tipos uint o ulong.
Si se trata de un nmero en base hexadecimal deber comenzar por 0(cero) y
a continuacin, llevar la letra x. 0x123 es una constante entera en hexadecimal.

Constantes reales
Una constante flotante representa un nmero real, siempre tiene signo y puede
representar tanto aproximaciones (coma flotante) como valores exactos (decimal). Las
constantes reales tienen el tipo double por defecto, aunque tambin pueden ir seguidas
por una da D que especifique su pertenencia a dicho tipo.
Cuando se les aade una f o F se obliga a que sean de tipo float y una m o M indican un
decimal.

20

double dr =-3.0m;
la sentencia anterior es incorrecta, no puede asignarse directamente un decimal a una
variable de tipo coma flotante
Constantes lgicas
Las constantes literales de tipo lgico disponibles en C# son true (verdadero) y false
(falso).
Constantes de tipo carcter
Una constante de tipo carcter es un carcter Unicode vlido encerrado entre comillas
simples. Los caracteres que pueden considerarse vlidos son:

Los caracteres simples, excepto ', \ y el carcter de nueva lnea.


Secuencias de escape simples, que se usan para representar ciertas constantes de
tipo carcter: \' \" \\ \0 \a \b \f \n \r \t \v.
Secuencias de escape hexadecimales: \x seguido por cuatro dgitos hexadecimales.
Por ejemplo: '\x0061'.
Secuencias de escape Unicode: \u seguido por cuatro dgitos hexadecimales o \U
seguido por ocho dgitos hexadecimales. Por ejemplo: '\U00000061' o '\u0061'.

Constantes de tipo cadena


Una constante literal de tipo cadena en C# est constituida por una serie de caracteres
Unicode, entre los que pueden aparecer secuencias de escape, encerrados entre comillas
dobles o bien por un signo @ seguido de una serie de caracteres Unicode encerrados
entre comillas. En un literal de cadena que comienza por el signo @ no se procesan las
secuencias de escape y todos los caracteres especificados entre las dobles comillas son
interpretados literalmente.
Ejemplo
// se especifican secuencias de escape
string doslineas = "1 2 3\r\n4 5 6";
/*
se escribe la cadena literalmente
y precedida por el car cter @
*/
string otrasdos= @"1 2 3 4 5 6";

2.2 Concepto de mtodo y 2.3 Declaracin de mtodos

21

Los mtodos son los miembros de un tipo clase donde se especifican las acciones que se
realizan por un objeto. Una invocacin a un mtodo es una peticin al mtodo para que
ejecute su accin y lo haga con el objeto mencionado. La invocacin de un mtodo se
denominara tambin llamar a un mtodo y pasar un mensaje a un objeto.
Nota: Existen dos tipos de mtodos: aquellos que devuelven un nico valor y aquellos
que realizan alguna accin distinta de devolver un valor. Los mtodos que realizan alguna
accin distinta de devolver un valor se denominan mtodos void.
La implementacin de los mtodos podra ser como sta:
public class CuentaCorriente
{
private double saldo;
public void Depositar(double cantidad)
{
saldo = saldo + cantidad;
}
public void Retirar(double cantidad)
{
saldo = saldo - cantidad;
}
public double ObtenerSaldo()
{
return saldo;
}
}

2.4 Llamadas a mtodos.


La llamada o invocacin a un mtodo se puede realizar de dos formas, dependiendo de
que el mtodo devuelva o no un valor.
1. Si el mtodo devuelve un valor, la llamada al mtodo se trata normalmente como un
valor. Por ejemplo:
Cantidad = myCuentaCorriente.ObtenerSaldo();
2. Si el mtodo devuelve void, una llamada al mtodo debe ser una sentencia. Por
ejemplo.
myCuentaCorriente.Depositar(5000)
Cuando un programa llama a un mtodo, el control del programa se transfiere al mtodo
llamado. Un mtodo llamado devuelve el control al llamador cuando se ejecute su
sentencia return o cuando se alcance la llave de cierre ( } ).

22

2.5 Tipos de mtodos.


2.5.1 Mtodos Const y Static.
En C# no hay mtodos Const.
Mtodos Static:
Utilice el modificador static para declarar un miembro esttico, que pertenece al propio
tipo en vez de a un objeto especfico. El modificador static puede utilizarse con clases,
campos, mtodos, propiedades operadores y eventos, pero no puede utilizarse con
indizadores, destructores o tipos que no sean clases. Por ejemplo, la siguiente clase se
declara como static y solo contiene mtodos static:
static class CompanyEmployee
{
public static string GetCompanyName(string name) { ... }
public static string GetCompanyAddress(string address) { ... }
}

Comentario

Una declaracin de constante o tipo constituye, implcitamente, un miembro


esttico.

No se puede hacer referencia a un miembro esttico por medio de una instancia.


En vez de ello, se debe hacer referencia por medio del nombre de tipo. Por
ejemplo, considere la siguiente clase:

public class MyBaseC


{
public struct MyStruct
{
public static int x = 100;
}
}
Para referirse al miembro esttico x, use el nombre completo (a menos que sea accesible
desde el mismo mbito):
MyBaseC.MyStruct.x

23

Mientras que una instancia de una clase contiene una copia independiente de
todos los campos de instancia de la clase, slo existe una copia de cada campo
esttico.

No es posible utilizar this para hacer referencia a descriptores de acceso de


propiedades o mtodos static.

Si la palabra clave static se aplica a una clase, todos los miembros de la clase
deben ser estticos.

Las clases, incluidas las clases estticas, pueden tener constructores estticos. Se
llama a los constructores estticos en algn momento comprendido entre el inicio
del programa y la creacin de instancias de la clase.

Para comprender el uso de miembros estticos, considere una clase que representa al
empleado de una compaa. Suponga que la clase contiene un mtodo que cuenta
empleados y un campo que almacena el nmero de empleados. Ni el mtodo ni el campo
pertenecen a ninguna instancia de empleado. En vez de ello, pertenecen a la clase
compaa. Por tanto, se deberan declarar como miembros estticos de la clase.
Ejemplo
Este ejemplo lee el nombre y el identificador de un nuevo empleado, incrementa en uno el
contador de empleados y muestra la informacin del nuevo empleado, as como el nuevo
nmero de empleados. Por motivos de simplicidad, el programa lee el nmero actual de
empleados desde el teclado. En una aplicacin real, esta informacin se leera desde un
archivo.
En el siguiente ejemplo vamos a usar el modo consola solo para uso de explicacin en
clase.
// cs_static_keyword.cs
using System;
public class Employee
{
public string id;
public string name;
public Employee() { }
public Employee(string name, string id)
{
this.name = name;
this.id = id;
}
public static int employeeCounter;
public static int AddEmployee()
{
return ++employeeCounter;
}

24

}
class MainClass : Employee
{
static void Main()
{
Console.Write("Enter the employee's name: "); string name = Console.ReadLine();
Console.Write("Enter the employee's ID: "); string id = Console.ReadLine();
// Create and configure the employee object:
Employee e = new Employee(name, id);
Console.Write("Enter the current number of employees: ");
String n = Console.ReadLine();
Employee.employeeCounter = Int32.Parse(n);
Employee.AddEmployee();
// Display the new information:
Console.WriteLine("Name: {0}", e.name);
Console.WriteLine("ID: {0}", e.id);
Console.WriteLine("NewNumber of Employees: {0}", Employee.employeeCounter);
}
}
Entrada
Tara Strahan
AF643G
15

Resultados del ejemplo


Enter the employee's name: Tara Strahan
Enter the employee's ID: AF643G
Enter the current number of employees: 15
Name: Tara Strahan
ID: AF643G
New Number of Employees: 16
2.6 Referencia This
La palabra clave this hace referencia a la instancia actual de la clase.
A continuacin, se indican algunos usos comunes de this:
Obtener acceso a miembros con el fin de evitar ambigedades con nombres similares, por
ejemplo:
public Employee(string name, string alias)
{
this.name = name;
this.alias = alias;
}

25

Pasar un objeto como parmetro a otros mtodos, por ejemplo, para:

CalcTax(this);

Declarar indizadores, por ejemplo:

public int this [int param] {


get
{
return array[param];
}
set
{
array[param] = value;
}
}
Debido a que las funciones miembro estticas existen en el nivel de clase y no como parte
de un objeto, no tienen un puntero this. Es un error hacer referencia a this en un mtodo
esttico.
Ejemplo
En este ejemplo, this se utiliza para calificar los miembros de la clase Employee, name y
alias, que presentan ambigedad con nombres similares. Tambin se utiliza para pasar un
objeto al mtodo CalcTax, el cual pertenece a otra clase.

// keywords_this.cs
// this example
using System;
class Employee
{
private string name;
private string alias;
private decimal salary = 3000.00;
// Constructor:
public Employee(string name, string alias)
{
// Use this to qualify the fields, name and alias:
this.name = name;
this.alias = alias;
}
// Printing method:
public void printEmployee()
{

26

Console.WriteLine("Name: {0}\nAlias: {1}", name, alias);


// Passing the object to the CalcTax method by using this:
Console.WriteLine("Taxes: {0:C}", Tax.CalcTax(this));
}
public decimal Salary
{
get
{
return salary;
}
}
}
class Tax
{
public static decimal CalcTax(Employee E)
{
return 0.08m * E.Salary,
}
}
class MainClass {
static void Main()
{
// Create objects:
Employee E1 = new Employee("John M. Trainer", "jtrainer");
// Display results:
E1.printEmployee();
}
}

Resultados
Name: John M. Trainer
Alias: jtrainer
Taxes: $240.00
2.7 Forma de pasar argumentos.
La cabecera de un mtodo especifica el nmero y tipo de parmetros formales requeridos.
En el interior de una clase, un mtodo se identifica no slo por su nombre, sino tambin
por su lista de parmetros formales. Por consiguiente, el mismo nombre de mtodo se
puede definir ms de una vez con diferentes parmetros formales para conseguir la
sobrecarga de mtodos.
Cuando se llama a un mtodo se deben proporcionar un nmero y tipo correctos de
argumentos. Los argumentos incluidos en la llamada a un mtodo se conocen como

27

argumentos (parmetros) reales o simplemente argumentos. La llamada a un mtodo


exige proporcionarle parmetros reales (actuales) que se deben dar en el mismo orden
que la lista de parmetros formales en la especificacin del mtodo. Esta regla se conoce
como asociacin del orden de los parmetros. C# soporta llamadas a un mtodo con un
nmero variable de argumentos cuando el parmetro formal correspondiente es de tipo
array, va precedido por el modificador params y es el ltimo especificado en la cabecera
del mtodo.
Ejemplos
1. El mtodo ImprimirN imprime un mensaje n veces
void ImprimirN(String mensaje, int n)
{
for (int i=0; i<n; i++)
System.Console.WriteLine(mensaje);
}
a. Invocacin correcta
Una invocacin ImprimirN ("Carchelejo" , 4) imprime la palabra Carchelejo cuatro veces.
El proceso es el siguiente
La invocacin a ImprimirN pasa el parmetro actual cadena, "Carchelejo", al parmetro
formal mensaje y el parmetro actual 4 a la variable n .
Se imprime 4 veces la frase Carchelejo.

b. Invocacin incorrecta
La sentencia ImprimirN (4, "Carchelejo") es incorrecta, ya que el tipo de dato 4 no coincide
con el tipo del parmetro mensaje y, de igual modo, el segundo parmetro "Carchelejo"
tampoco coincide con el tipo del segundo parmetro formal n.
2. El mtodo ImprimirNX imprime n veces todos los argumentos que se le pasen como
parmetros a continuacin del hmero de veces a iterar, que deben ser de tipo ushort o
implcitamente convertibles a ushort.
class Prueba
{
// conversin implcita
static void ImprimirNX(int n, params ushort[ ] x)
for (int i=0; i<n; i++)
{
foreach(char j in x)
System.Console.Write("{0} ", j);
//conversin explcita
System.Console.WriteLine();

28

}
public static void Main () {
ImprimirNX (3, 'H', 79, 76, 'A');
ImprimirNX(2);
ImprimirNX (1, 65, 68, 73, 79, 83 );
}
Salida
HoLAHoLAHOLA
Tres veces la palabra HOLA
A DIO S
Dos lneas en blanco Una vez la palabra ADIOS
La operacin de enlazar (binding) los parmetros reales a los formales se denomina
paso de parmetros. Cuando se llama a un mtodo con ms de un argumento, dichos
argumentos se evalan de modo secuencial, de izquierda a derecha. Existen dos tipos de
paso de parmetros: por valor y por referencia.
PASO DE PARMETROS POR VALOR
En C#, los parmetros pueden ser pasados por valor o por referencia. Cuando un
parmetro se pasa por valor, sus valores se copian en nuevas posiciones, que se pasan a
la subrutina; como consecuencia de esto, si el parmetro cambia su valor dentro del
mtodo, propiedad, indexador, operador o constructor, dicho valor no cambiar en el
programa llamador original. Hay que tener en cuenta que las variables que se pasan
como parmetros pueden ser de tipo valor o de tipo referencia. Una variable de un tipo
valor almacena directamente los datos, esto significa que cuando el valor de la variable se
pasa a un parmetro formal, los datos se copian y su cambio en el interior del mtodo no
repercute en el exterior. Una variable de un tipo de referencia no contiene los datos
directamente, sino una referencia al lugar donde se encuentran almacenados los mismos;
esto implica que, cuando se pasa por valor una variable de tipo referencia, es posible
devolver cambiados a los datos apuntados por la referencia, pero no el valor de la propia
referencia.
PASO DE PARMETROS POR REFERENCIA
El paso por referencia permite a los mtodos, propiedades, indexadores, operadores, y
constructores devolver el valor de los parmetros modificado. Cuando se pasa un
parmetro por referencia no se copia el valor del parmetro actual en una nueva posicin
de memoria, sino que se establece un nuevo nombre para el parmetro actual. Por
defecto, los parmetros se pasan por valor y para pasar un parmetro por referencia es
necesario emplear las palabras reservadas out o ref.
Ejemplo

29

En el siguiente programa objetos de la clase Quebrado se pasan como argumentos a los


mtodos Simplificar, Intercambiar y Mostrar. El programa consta de dos partes:

Primero comienza por simplificar un quebrado para mostrar el funcionamiento del


paso de parmetros por valor cuando los argumentos son de tipo referencia.
Despus, intercambia dos quebrados para que pueda observarse el
funcionamiento del paso de parmetros por referencia con argumentos de tipo
referencia.

using System;
class Quebrado
{
int num, den;
// Constructor
public Quebrado(int x, int y)
{
num = x;
den = y;
}
/* Variable referencia pasada por valor
Permite que el objeto referenciado se devuelva modificado */
public static void Simplificar(Quebrado q)
{
int a = q.num;
int b = q.den;
/* Clculo del mximo comn divisor entre a y b.
El mximo comn divisor se obtiene aplicando el algoritmo
de Euclides, que dice que para obtener el mximo comn divisor
entre dos nmeros, se dividen y si el resto es cero el divisor
es el mximo comn divisor, si el resto no es cero se
intercambian dividendo por divisor y divisor por resto,
repitindose la operacin hasta obtener un resto cero,
en cuyo caso el divisor ser el mximo comn divisor */
while (b > 0)
{
int r = a % b;
a = b;
b = r;
}
/* Al terminar el bucle el mximo comn divisor se encuentra
almacenado en a.
Para simplificar el quebrado se divide numerador y denominador
por su mximo comn divisor */
q.num = q.num / a;
q.den = q.den / a;
}

30

/* Variable referencia pasada por referencia.


Permite que la referencia se devuelva modificada */
public static void Intercambiar(ref Quebrado q1, ref Quebrado q2)
{
Quebrado auxi = q1;
q1 = q2;
q2 = auxi;
}
/* Uso de params para permitir llamadas con un nmero variable
de argumentos */
public static void Mostrar(params Quebrado[] arr)
{
foreach (Quebrado q in arr)
Console.Write(" {0}/{1} ", q.num, q.den);
Console.WriteLine();
}
}
class PasoDePR
{
public static void Main ()
{
Quebrado q1 = new Quebrado(18, 45);
Quebrado.Mostrar(q1);
Quebrado.Simplificar(q1);
Quebrado.Mostrar(q1);
Quebrado q2 = new Quebrado(1, 2);
Quebrado.Mostrar(q1, q2);
Quebrado.Intercambiar(ref q1, ref q2);
Quebrado.Mostrar(q1, q2);
}
}
Resultado
2/5
2/5 1/2
1/2 2/5
Como se ha podido observar en el caso de ref, los modificadores ref y out permiten que
los cambios hechos en el parmetro se reflejen en su variable asociada. La diferencia
entre ambos est en que un parmetro ref necesita que el parmetro actual
correspondiente sea previamente inicializado, mientras que esto no es necesario cuando
se trata de un parmetro out.
Ejemplo
El programa siguiente ordena 3 nmeros ledos desde teclado, utilizando dos mtodos
auxiliares: Leer3 e Intercambiar. El mtodo Leer3 devuelve al mtodo Main los 3 nmeros
introducidos desde teclado y permite mostrar el funcionamiento del modificador out. El

31

mtodo Intercambiar es similar al expuesto en el ejemplo anterior (en la clase Quebrado),


pero ahora se utiliza para mostrar el funcionamiento del modificador ref con argumentos
de tipo valor. En Leer3 se capturan las posibles excepciones generadas (try) y como
tratamiento de las mismas (catch) se efecta su lanzamiento (throw e), para que en Main
vuelvan a ser consideradas. En Main la excepcin se captura de nuevo y no se establece
ningn tratamiento para la misma, pero as, si se produce una excepcin en Leer3, el
programa principal no hace nada.
using System;
class PasoDePV
{
static void Intercambiar(ref int pl, ref int p2)
{
int auxi = pl;
pl = p2;
p2 = auxi;
}
static void Leer3(out int a, out int b, out int e)
{
a = b = e = 0;
try
{
Console.Write("lO ");
a = int.Parse(Console.ReadLine());
Console.Write("2 ");
b = int.Parse(Console.ReadLine());
Console.Write("3 ");
e = int.Parse(Console.ReadLine());
}
catch (Exception e)
{
throw e;
}
}

public static void Main ()


{
int a, b, e;
try
{
Console.WriteLine("Introdu ea tres n meros");
Leer3(out a, out b, out e);
if (a > b)
Intereambiar(ref a, ref b);
if (b > c)
Intercambiar(ref b, ref c);
if (a > b)

32

Intercambiar(ref a, ref b);


Console.WriteLine("Ordenados: {0} {l} {2}",a,b ,e);
}
catch { }
}
}
2.8 Devolver un valor desde un mtodo.
Los mtodos pueden devolver un valor al llamador. Si el tipo de valor devuelto (el que
aparece antes del nombre de mtodo) no es void, el mtodo puede devolver el valor
mediante la palabra clave return. Una instruccin con la palabra clave return, seguida de
un valor que coincida con el tipo de valor devuelto, devolver ese valor al llamador del
mtodo. La palabra clave return tambin detiene la ejecucin del mtodo. Si el tipo de
valor devuelto es void, una instruccin return sin ningn valor sigue siendo til para
detener la ejecucin del mtodo. Sin la palabra clave return, el mtodo detendr la
ejecucin cuando llegue al fin del bloque de cdigo. Es necesario que los mtodos con un
tipo de valor devuelto no nulo utilicen la palabra clave return para devolver un valor. Por
ejemplo, estos dos mtodos utilizan la palabra clave return para devolver enteros:

class SimpleMath
{
public int AddTwoNumbers(int number1, int number2)
{
return number1 + number2;
}
public int SquareANumber(int number)
{
return number * number;
}
}
Para emplear un valor devuelto por un mtodo, el mtodo de llamada puede utilizar la
propia llamada del mtodo en cualquier parte donde un valor del mismo tipo sea
suficiente. El valor devuelto tambin se puede asignar a una variable. Por ejemplo, los dos
ejemplos de cdigo siguientes logran el mismo objetivo:
int result = obj.AddTwoNumbers(1, 2);
obj.SquareANumber(result);

33

obj.SquareANumber(obj.AddTwoNumbers(1, 2));
El uso de una variable intermedia, en este caso result, para almacenar un valor es
opcional. La legibilidad del cdigo puede ser til o puede ser necesaria si el valor se va a
utilizar ms de una vez.

III Constructor y destructor


3.1 Conceptos de mtodos constructor y destructor / 3.2 Declaracin de mtodos
constructor y destructor

Los constructores son mtodos especiales que permiten controlar el proceso de


inicializacin. Se ejecutan despus de que el programa empiece o se cree una instancia
de un tipo. A diferencia de otros miembros, los constructores no se heredan y no
introducen ningn nombre en el espacio de declaracin de un tipo. A los constructores
slo se les puede invocar con expresiones de creacin de objetos o con .NET Framework.
Nunca se pueden invocar directamente.
Una clase puede tener varios constructores que toman argumentos diferentes. Los
constructores permiten al programador establecer valores predeterminados, limitar la
creacin de instancias y escribir cdigo flexible y fcil de leer.
Si no proporciona un constructor para el objeto, C# crear uno de forma predeterminada
que crea instancias del objeto y establecer las variables miembro con los valores

34

predeterminados indicados
Tipo de
valor

Valor predeterminado

bool

false

byte

char

'\0'

decimal

0,0M

double

0,0D

enum

El valor producido por la expresin (E)0, donde E es el identificador de la


enumeracin.

float

0,0F

int

long

0L

sbyte

short

struct

El valor obtenido al asignar los valores predeterminados a los campos de tipo


de valor y el valor null a los campos de tipo de referencia.

uint

ulong

ushort

Los constructores son mtodos de clase que se ejecutan cuando se crea un objeto de un
tipo determinado. Los constructores tienen el mismo nombre que la clase y, normalmente,
inicializan los miembros de datos del nuevo objeto.
En el ejemplo siguiente, una clase denominada Taxi se define con un constructor simple.
Esta clase crea instancias con el operador new. El operador new invoca el constructor
Taxi inmediatamente despus de asignar la memoria al nuevo objeto.

public class Taxi


{
public bool isInitialized;
public Taxi()
{
isInitialized = true;
}
}

35

class TestTaxi
{
static void Main()
{
Taxi t = new Taxi();
System.Console.WriteLine(t.isInitialized);
}
}
Destructores
Los destructores se utilizan para destruir instancias de clases.
Comentarios
Los destructores no se pueden definir en estructuras. Slo se utilizan con clases.
Una clase slo puede tener un destructor.
Los destructores no se pueden heredar ni sobrecargar.
No se puede llamar a los destructores. Se invocan automticamente.
Un destructor no permite modificadores de acceso ni tiene parmetros.
Por ejemplo, el siguiente cdigo muestra una declaracin de un destructor para la clase
Car:

class Car
{
~ Car() // destructor
{
// cleanup statements...
}
}
El destructor llama implcitamente al mtodo Finalize en la case base del objeto. Por lo
tanto, el cdigo de destructor anterior se traduce implcitamente a:

protected override void Finalize()


{
try
{
// cleanup statements...
}
finally
{
base.Finalize();
}
}

36

Esto significa que se llama al mtodo Finalize de forma recursiva para todas las instancias
de la cadena de herencia, desde la ms derivada hasta la menos derivada.
El programador no puede controlar cundo se llama al destructor, porque esto lo
determina el recolector de elementos no utilizados. El recolector de elementos no
utilizados comprueba si hay objetos que ya no estn siendo utilizados por ninguna
aplicacin. Si considera un objeto elegible para su destruccin, llama al destructor (si
existe) y reclama la memoria utilizada para almacenar el objeto. Tambin se llama a los
destructores cuando se cierra el programa.
Es posible forzar la recoleccin de elementos no utilizados llamando al mtodo Collect,
pero en la mayora de los casos debe evitarse su uso por razones de rendimiento.
3.3 Aplicaciones de constructores y destructores / 3.4 Tipos de constructores y
destructores
En general, C# no requiere tanta administracin de memoria como se necesita al
desarrollar con un lenguaje que no est diseado para un motor en tiempo de ejecucin
con recoleccin de elementos no utilizados. Esto es debido a que el recolector de
elementos no utilizados de .NET Framework administra implcitamente la asignacin y
liberacin de memoria para los objetos. Sin embargo, cuando la aplicacin encapsule
recursos no administrados como ventanas, archivos y conexiones de red, debera utilizar
destructores para liberar dichos recursos. Cuando el objeto se marca para su destruccin,
el recolector de elementos no utilizados ejecuta el mtodo Finalize.
Ejemplo
En el siguiente ejemplo se crean tres clases que forman una cadena de herencia. La clase
First es la clase base, Second se deriva de First y Third se deriva de Second. Las tres
tienen destructores. En Main(), se crea una instancia de la clase ms derivada. Cuando
ejecute el programa, observe que se llama a los destructores de las tres clases
automticamente y en orden, desde la ms derivada hasta la menos derivada.
class First
{
~First()
{
System.Console.WriteLine("First's destructor is called");
}
}

class Second: First


{
~Second()
{
System.Console.WriteLine("Second's destructor is called");
}
}

class Third: Second


{

37

~Third()
{
System.Console.WriteLine("Third's destructor is called");
}
}
class TestDestructors
{
static void Main()
{
Third t = new Third();
}
}
Constructores de instancias
Los constructores de instancia se utilizan para crear e inicializar instancias. El constructor
de clase se invoca al crear un objeto nuevo, por ejemplo:
class CoOrds
{
public int x, y;
// constructor
public CoOrds()
{
x = 0;
y = 0;
}
}
Se llama a este constructor cada vez que se crea un objeto basado en la clase CoOrds.
Un constructor como ste, que no toma ningn argumento, se denomina constructor
predeterminado. Sin embargo, suele ser til proporcionar constructores adicionales. Por
ejemplo, se puede agregar a la clase CoOrds un constructor que permita especificar los
valores iniciales de los miembros de datos:
// A constructor with two arguments:
public CoOrds(int x, int y)
{
this.x = x;
this.y = y;
}
Esto permite crear objetos CoOrd con valores iniciales concretos o predeterminados, del
modo siguiente:
CoOrds p1 = new CoOrds();
CoOrds p2 = new CoOrds(5, 3);

38

Si una clase no tiene un constructor predeterminado, se genera uno automticamente y


se utilizan los valores predeterminados para inicializar los campos del objeto, por ejemplo,
un campo de tipo int se inicializa en 0. Por consiguiente, como el constructor
predeterminado de la clase CoOrds inicializa todos los miembros de datos en cero, se
puede quitar del todo sin cambiar el funcionamiento de la clase.
Constructores privados
Un constructor private es un caso especial de constructor de instancia. Se utiliza
normalmente en clases que contienen slo miembros estticos. Si una clase tiene uno o
ms constructores privados y ningn constructor pblico, el resto de clases (excepto las
anidadas) no tiene permiso para crear instancias de esta clase. Por ejemplo:
class NLog
{
// Private Constructor:
private NLog() { }
public static double e = System.Math.E; //2.71828...
}
La declaracin de un constructor vaco evita la generacin automtica de un constructor
predeterminado. Observe que si no utiliza un modificador de acceso en el constructor,
ste ser private de manera predeterminada. Sin embargo, normalmente se utiliza el
modificador private de manera explcita para aclarar que no es posible crear una instancia
de la clase.
Los constructores privados se utilizan para evitar la creacin de instancias de una clase
cuando no hay campos o mtodos de instancia, por ejemplo la clase Math, o cuando se
llama a un mtodo para obtener una instancia de una clase. Si todos los mtodos de la
clase son estticos, considere convertir la clase completa en esttica.
public class Counter
{
private Counter() { }
public static int currentCount;
public static int IncrementCount()
{
return ++currentCount;
}
}
class TestCounter
{
static void Main()
{
// If you uncomment the following statement, it will generate
// an error because the constructor is inaccessible:
// Counter aCounter = new Counter(); // Error
Counter.currentCount = 100;
Counter.IncrementCount();
System.Console.WriteLine("New count: {0}", Counter.currentCount);
}
}

39

Constructores estticos
Un constructor esttico se utiliza para inicializar cualquier dato esttico o realizar una
accin determinada que slo debe realizarse una vez. Es llamado automticamente antes
de crear la primera instancia o de hacer referencia a cualquier miembro esttico.
class SimpleClass
{
// Static constructor
static SimpleClass()
{
//...
}
}
Los constructores estticos tienen las propiedades siguientes:
Un constructor esttico no permite modificadores de acceso ni tiene parmetros.
Se le llama automticamente para inicializar la clase antes de crear la primera instancia o
de hacer referencia a cualquier miembro esttico.
El constructor esttico no puede ser llamado directamente.
El usuario no puede controlar cuando se ejecuta el constructor esttico en el programa.
Los constructores estticos se utilizan normalmente cuando la clase hace uso de un
archivo de registro y el constructor escribe entradas en dicho archivo.
Los constructores estticos tambin son tiles al crear clases contenedoras para cdigo
no administrado, cuando el constructor puede llamar al mtodo LoadLibrary.
Ejemplo
En este ejemplo, la clase Bus tiene un constructor esttico y un miembro esttico, Drive().
Cuando se llama a Drive(), se invoca el constructor esttico para inicializar la clase.
public class Bus
{
// Static constructor:
static Bus()
{
System.Console.WriteLine("The static constructor invoked.");
}
public static void Drive()
{
System.Console.WriteLine("The Drive method invoked.");
}
}
class TestBus
{
static void Main()
{
Bus.Drive();
}
}

40

IV Sobrecarga
4.1 Conversin de tipos
La conversin entre tipos de datos se puede hacer de forma explcita utilizando una
conversin de tipos; en algunos casos, se permiten conversiones implcitas. Por ejemplo:
static void TestCasting()
{
int i = 10;
float f = 0;
f = i; // An implicit conversion, no data will be lost.
f = 0.5F;
i = (int) f; // An explicit conversion. Information will be lost.
}
Una conversin de tipos invoca de forma explcita al operador de conversin de un tipo a
otro. En la conversin de tipos se producir un error si no se ha definido ninguno de estos
operadores. Puede escribir operadores de conversin personalizados para convertir entre
los tipos definidos por el usuario.
class Test
{

41

static void Main()


{
double x = 1234.7;
int a;
a = (int)x; // cast double to int
System.Console.WriteLine(a);
}
}
Resultado
1234
Convert (Clase)
Convierte un tipo de datos base en otro tipo de datos base.
public static class Convert
Comentarios
Esta clase devuelve un tipo cuyo valor es equivalente al valor de un tipo especificado. Los
tipos base que se admiten son Boolean, Char, SByte, Byte, Int16, Int32, Int64, UInt16,
UInt32, UInt64, Single, Double, Decimal, DateTime y String.
Existe un mtodo de conversin para convertir todos y cada uno de los tipos base en los
dems tipos base. Sin embargo, la operacin de conversin real efectuada queda incluida
en tres categoras:
La conversin de un tipo en s mismo devuelve dicho tipo. No se lleva a cabo

realmente ninguna conversin.


La conversin que no puede producir un resultado significativo produce una
excepcin InvalidCastException. No se lleva a cabo realmente ninguna
conversin. Las conversiones de Char en Boolean, Single, Double, Decimal o
DateTime, y de estos tipos en Char producen una excepcin. Las conversiones
de DateTime en cualquier tipo excepto String, y de cualquier tipo excepto String
en DateTime producen una excepcin.
Los tipos base no descritos anteriormente pueden ser objeto de conversiones a
y desde cualquier otro tipo base.
No se producir una excepcin si la conversin de un tipo numrico produce una prdida
de precisin, es decir, la prdida de algunos de los dgitos menos significativos. Sin
embargo, la excepcin se producir si el resultado es mayor de lo que puede representar
el tipo de valor devuelto del mtodo de conversin.
Por ejemplo, cuando un tipo Double se convierte en un tipo Single, se puede producir
una prdida de precisin pero no se produce ninguna excepcin. Sin embargo, si la
magnitud del tipo Double es demasiado grande para que un tipo Single lo represente, se
produce una excepcin de desbordamiento.
Existe un conjunto de mtodos que admiten la conversin de una matriz de bytes en y
desde un tipo String o una matriz de caracteres Unicode formada por dgitos de base 64.
Los datos expresados como dgitos de base 64 se pueden transmitir fcilmente en
canales de datos que slo pueden transmitir caracteres de 7 bits.

42

Algunos de los mtodos de esta clase toman un objeto de parmetro que implementa la
interfaz IFormatProvider. Este parmetro puede proporcionar informacin de formato
especfica de la referencia cultural para ayudar en el proceso de conversin. Los tipos de
valor base pasan por alto este parmetro, pero los tipos definidos por el usuario que
implementan IConvertible pueden tenerlo en cuenta.
Para obtener ms informacin sobre los tipos de valor base, vea el tema correspondiente
que aparece en la seccin Vea tambin.
Ejemplo
En el siguiente ejemplo de cdigo, se muestran algunos de los mtodos de conversin de
la clase Convert, entre los que se incluyen ToInt32, ToBoolean y ToString.
double dNumber = 23.15;
try {
// Returns 23
int iNumber = System.Convert.ToInt32(dNumber);
}
catch (System.OverflowException) {
System.Console.WriteLine(
"Overflow in double to int conversion.");
}
// Returns True
bool bNumber = System.Convert.ToBoolean(dNumber);
// Returns "23.15"
string strNumber = System.Convert.ToString(dNumber);
try {
// Returns '2'
char chrNumber = System.Convert.ToChar(strNumber[0]);
}
catch (System.ArgumentNullException) {
System.Console.WriteLine("String is null");
}
catch (System.FormatException) {
System.Console.WriteLine("String length is greater than 1.");
}
// System.Console.ReadLine() returns a string and it
// must be converted.
int newInteger = 0;
try {
System.Console.WriteLine("Enter an integer:");
newInteger = System.Convert.ToInt32(
System.Console.ReadLine());
}
catch (System.ArgumentNullException) {
System.Console.WriteLine("String is null.");
}

43

catch (System.FormatException) {
System.Console.WriteLine("String does not consist of an " +
"optional sign followed by a series of digits.");
}
catch (System.OverflowException) {
System.Console.WriteLine(
"Overflow in string to int conversion.");
}
System.Console.WriteLine("Your integer as a double is {0}",
System.Convert.ToDouble(newInteger));
En el ejemplo de cdigo siguiente se muestran algunos de los mtodos de conversin de
la clase Convert.
// Sample for the Convert class summary.
using System;
class Sample
{
public static void Main()
{
string nl = Environment.NewLine;
string str = "{0}Return the Int64 equivalent of the following base types:{0}";
bool xBool = false;
short xShort = 1;
int xInt = 2;
long xLong = 3;
float xSingle = 4.0f;
double xDouble = 5.0;
decimal xDecimal = 6.0m;
string xString = "7";
char xChar = '8'; // '8' = hexadecimal 38 = decimal 56
byte xByte = 9;
// The following types are not CLS-compliant.
ushort xUshort = 120;
uint xUint = 121;
ulong xUlong = 122;
sbyte xSbyte = 123;
// The following type cannot be converted to an Int64.
// DateTime xDateTime = DateTime.Now;
Console.WriteLine(str, nl);
Console.WriteLine("Boolean: {0}", Convert.ToInt64(xBool));
Console.WriteLine("Int16: {0}", Convert.ToInt64(xShort));
Console.WriteLine("Int32: {0}", Convert.ToInt64(xInt));
Console.WriteLine("Int64: {0}", Convert.ToInt64(xLong));
Console.WriteLine("Single: {0}", Convert.ToInt64(xSingle));
Console.WriteLine("Double: {0}", Convert.ToInt64(xDouble));

44

Console.WriteLine("Decimal: {0}", Convert.ToInt64(xDecimal));


Console.WriteLine("String: {0}", Convert.ToInt64(xString));
Console.WriteLine("Char: {0}", Convert.ToInt64(xChar));
Console.WriteLine("Byte: {0}", Convert.ToInt64(xByte));
Console.WriteLine("DateTime: There is no example of this conversion because");
Console.WriteLine("
a DateTime cannot be converted to an Int64.");
//
Console.WriteLine("{0}The following types are not CLS-compliant.{0}", nl);
Console.WriteLine("UInt16: {0}", Convert.ToInt64(xUshort));
Console.WriteLine("UInt32: {0}", Convert.ToInt64(xUint));
Console.WriteLine("UInt64: {0}", Convert.ToInt64(xUlong));
Console.WriteLine("SByte: {0}", Convert.ToInt64(xSbyte));
}
}
/*
This example produces the following results:
Return the Int64 equivalent of the following base types:
Boolean: 0
Int16: 1
Int32: 2
Int64: 3
Single: 4
Double: 5
Decimal: 6
String: 7
Char: 56
Byte: 9
DateTime: There is no example of this conversion because
a DateTime cannot be converted to an Int64.
The following types are not CLS-compliant.
UInt16:
UInt32:
UInt64:
SByte:
*/

120
121
122
123

4.2 Sobrecarga de mtodos


La sobrecarga de los mtodos permite que una clase, estructura o interfaz declare

varios mtodos con el mismo nombre, siempre que sus firmas sean nicas dentro de
esa clase, estructura o interfaz.
La sobrecarga de los constructores de instancias permite que una clase o una

estructura declare varios constructores de instancias, a condicin de que sus firmas


sean nicas dentro de esa clase o estructura.

45

La sobrecarga de los indizadores permite que una clase, estructura o interfaz

declare varios indizadores, siempre que sus firmas sean nicas dentro de esa clase,
estructura o interfaz.

La sobrecarga de los operadores permite que una clase o una estructura declare

varios operadores con el mismo nombre, siempre que sus firmas sean nicas dentro
de esa clase o estructura.
El siguiente ejemplo muestra un conjunto de declaraciones de mtodos sobrecargados.
void F();

// F()

void F(int x);

// F(int)

void F(ref int x);

// F(ref int)

void F(int x, int y);

// F(int, int)

int F(string s);

// F(string)

int F(int x);


void F(string[] a);

// F(int)

error

// F(string[])

void F(params string[] a);


Se debe tener en cuenta que los modificadores de parmetro ref y out forman parte de
una firma. Por lo tanto, F(int) y F(ref int) son firmas nicas. Asimismo, el tipo del valor
devuelto y el modificador params no forman parte de una firma, por lo que no es posible
sobrecargar basndose exclusivamente en el tipo de valor devuelto o en la inclusin o
exclusin del modificador params. Como tales, las declaraciones de los mtodos F(int) y
F(params string[]) anteriormente identificadas producen un error en tiempo de
compilacin.

4.3 Sobrecarga de operadores


Sobrecarga de operadores
Todos los operadores unarios y binarios tienen implementaciones predefinidas que estn
disponibles automticamente en cualquier expresin. Adems de las implementaciones
predefinidas, pueden introducirse implementaciones definidas por el usuario si se incluyen
declaraciones operator en las clases y estructuras. Las implementaciones de operador
definidas por el usuario siempre tienen precedencia sobre las implementaciones de
operador predefinidas: slo se consideran las implementaciones de operador predefinidas
cuando no existen implementaciones de operador definidas por el usuario que puedan
aplicarse.
Los operadores unarios sobrecargables son:
+ - ! ~ ++ -- true false

46

Aunque true y false no se utilizan explcitamente en las expresiones, se consideran


operadores porque se invocan en varios contextos de expresin: expresiones de tipo
Boolean, expresiones con condicionales y operadores lgicos condicionales.
Los operadores binarios sobrecargables son:
+ - * / % & | ^ << >> == != > < >= <=
Slo los operadores mencionados pueden sobrecargarse. En concreto, no es posible
sobrecargar accesos a miembros, llamadas a mtodos o los operadores =, &&, ||, ?:,
checked, unchecked, new, typeof, as e is.
Cuando se sobrecarga un operador binario, el operador correspondiente de asignacin (si
existe) tambin se sobrecarga de modo implcito. Por ejemplo, una sobrecarga del
operador * tambin es una sobrecarga del operador *=. Debe tenerse en cuenta que el
propio operador de asignacin (=) no se puede sobrecargar. Una asignacin siempre
realiza una simple copia bit a bit de un valor en una variable.
Las operaciones de conversin de tipo, como (T)x, se sobrecargan proporcionando
conversiones definidas por el usuario.
El acceso a elementos, del tipo a[x], no se considera un operador sobrecargable. En lugar
de ello, se acepta la indizacin definida por el usuario mediante indizadores.
En las expresiones, las referencias a los operadores se realizan mediante la notacin de
operadores y, en las declaraciones, las referencias a los operadores se realizan mediante
la notacin funcional. En la tabla siguiente se muestra la relacin entre las notaciones de
operador y funcional para los operadores unarios y binarios. En la primera entrada, op
denota cualquier operador de prefijo unario sobrecargable. En la segunda entrada, op
denota los operadores de sufijo unarios ++ y --. En la primera entrada, op denota
cualquier operador binario sobrecargable.
Notacin de operador

Notacin funcional

op x

operator op(x)

x op

operator op(x)

x op y

operator op(x, y)

Las declaraciones de operador definidas por el usuario siempre requieren que por lo
menos uno de los parmetros sea del tipo de la clase o estructura que contiene la
declaracin del operador. Por lo tanto, no es posible que un operador definido por el
usuario tenga la misma firma que un operador predefinido.
Las declaraciones de operador definidas por el usuario no pueden modificar la sintaxis,
precedencia o asociatividad de un operador. Por ejemplo, el operador / siempre es un
operador binario, siempre tiene el nivel de precedencia especificado en la y siempre es
asociativo por la izquierda.

47

Aunque es posible que un operador definido por el usuario realice cualquier clculo que le
interese, no se recomiendan las implementaciones que generan resultados distintos de
los que intuitivamente pueden esperarse. Por ejemplo, una implementacin de operator
== debe comparar la igualdad de los dos operandos y devolver un resultado bool
apropiado.
La sobrecarga de operadores permite utilizar implementaciones de operadores definidas
por el usuario en operaciones en las que al menos uno de los operandos es de un tipo
estructura o clase definido por el usuario. El primer ejemplo muestra cmo utilizar la
sobrecarga de operadores para crear una clase de nmeros complejos que define la
suma compleja. El segundo ejemplo muestra cmo utilizar la sobrecarga de operadores
para implementar un tipo lgico de tres valores.
Ejemplo 1
Este ejemplo muestra cmo utilizar la sobrecarga de operadores para crear una clase de
nmeros complejos Complex que define la suma compleja. El programa muestra las
partes real e imaginaria de los nmeros y el resultado de la suma mediante un mtodo
sustituto del mtodo ToString.
// complex.cs
using System;
public struct Complex
{
public int real;
public int imaginary;
public Complex(int real, int imaginary)
{
this.real = real;
this.imaginary = imaginary;
}
// Declare which operator to overload (+), the types
// that can be added (two Complex objects), and the
// return type (Complex):
public static Complex operator +(Complex c1, Complex c2)
{
return new Complex(c1.real + c2.real, c1.imaginary + c2.imaginary);
}
// Override the ToString method to display an complex number in the suitable format:
public override string ToString()
{
return(String.Format("{0} + {1}i", real, imaginary));
}
public static void Main()
{
Complex num1 = new Complex(2,3);

48

Complex num2 = new Complex(3,4);


// Add two Complex objects (num1 and num2) through the
// overloaded plus operator:
Complex sum = num1 + num2;
// Print the numbers and the sum using the overriden ToString method:
Console.WriteLine("First complex number: {0}",num1);
Console.WriteLine("Second complex number: {0}",num2);
Console.WriteLine("The sum of the two numbers: {0}",sum);
}
}

Resultado
First complex number: 2 + 3i
Second complex number: 3 + 4i
The sum of the two numbers: 5 + 7i

Ejemplo 2
Este ejemplo muestra cmo utilizar la sobrecarga de operadores para implementar un tipo
lgico de tres valores. Los valores posibles de este tipo son DBBool.dbTrue,
DBBool.dbFalse y DBBool.dbNull, donde el miembro dbNull indica un valor desconocido.
Nota Los operadores True y False definidos aqu slo resultan de utilidad para tipos que
representan valores True, False y Null (ni True ni False), como los utilizados en bases de
datos.
// dbbool.cs
using System;
public struct DBBool
{
// The three possible DBBool values:
public static readonly DBBool dbNull = new DBBool(0);
public static readonly DBBool dbFalse = new DBBool(-1);
public static readonly DBBool dbTrue = new DBBool(1);
// Private field that stores -1, 0, 1 for dbFalse, dbNull, dbTrue:
int value;
// Private constructor. The value parameter must be -1, 0, or 1:
DBBool(int value)
{
this.value = value;
}
// Implicit conversion from bool to DBBool. Maps true to
// DBBool.dbTrue and false to DBBool.dbFalse:

49

public static implicit operator DBBool(bool x)


{
return x? dbTrue: dbFalse;
}
// Explicit conversion from DBBool to bool. Throws an
// exception if the given DBBool is dbNull, otherwise returns
// true or false:
public static explicit operator bool(DBBool x)
{
if (x.value == 0) throw new InvalidOperationException();
return x.value > 0;
}
// Equality operator. Returns dbNull if either operand is dbNull,
// otherwise returns dbTrue or dbFalse:
public static DBBool operator ==(DBBool x, DBBool y)
{
if (x.value == 0 || y.value == 0) return dbNull;
return x.value == y.value? dbTrue: dbFalse;
}
// Inequality operator. Returns dbNull if either operand is
// dbNull, otherwise returns dbTrue or dbFalse:
public static DBBool operator !=(DBBool x, DBBool y)
{
if (x.value == 0 || y.value == 0) return dbNull;
return x.value != y.value? dbTrue: dbFalse;
}
// Logical negation operator. Returns dbTrue if the operand is
// dbFalse, dbNull if the operand is dbNull, or dbFalse if the
// operand is dbTrue:
public static DBBool operator !(DBBool x)
{
return new DBBool(-x.value);
}
// Logical AND operator. Returns dbFalse if either operand is
// dbFalse, dbNull if either operand is dbNull, otherwise dbTrue:
public static DBBool operator &(DBBool x, DBBool y)
{
return new DBBool(x.value < y.value? x.value: y.value);
}
// Logical OR operator. Returns dbTrue if either operand is
// dbTrue, dbNull if either operand is dbNull, otherwise dbFalse:
public static DBBool operator |(DBBool x, DBBool y)
{
return new DBBool(x.value > y.value? x.value: y.value);
}

50

// Definitely true operator. Returns true if the operand is


// dbTrue, false otherwise:
public static bool operator true(DBBool x)
{
return x.value > 0;
}
// Definitely false operator. Returns true if the operand is
// dbFalse, false otherwise:
public static bool operator false(DBBool x)
{
return x.value < 0;
}
// Overload the conversion from DBBool to string:
public static implicit operator string(DBBool x)
{
return x.value > 0 ? "dbTrue"
: x.value < 0 ? "dbFalse"
: "dbNull";
}
// Override the Object.Equals(object o) method:
public override bool Equals(object o)
{
try
{
return (bool) (this == (DBBool) o);
}
catch
{
return false;
}
}
// Override the Object.GetHashCode() method:
public override int GetHashCode()
{
return value;
}
// Override the ToString method to convert DBBool to a string:
public override string ToString()
{
switch (value)
{
case -1:
return "DBBool.False";
case 0:
return "DBBool.Null";

51

case 1:
return "DBBool.True";
default:
throw new InvalidOperationException();
}
}
}
class Test
{
static void Main()
{
DBBool a, b;
a = DBBool.dbTrue;
b = DBBool.dbNull;
Console.WriteLine( "!{0} = {1}", a, !a);
Console.WriteLine( "!{0} = {1}", b, !b);
Console.WriteLine( "{0} & {1} = {2}", a, b, a & b);
Console.WriteLine( "{0} | {1} = {2}", a, b, a | b);
// Invoke the true operator to determine the Boolean
// value of the DBBool variable:
if (b)
Console.WriteLine("b is definitely true");
else
Console.WriteLine("b is not definitely true");
}
}
Resultado
!DBBool.True = DBBool.False
!DBBool.Null = DBBool.Null
DBBool.True & DBBool.Null = DBBool.Null
DBBool.True | DBBool.Null = DBBool.True
b is not definitely true
En las reglas siguientes se describen las pautas para sobrecargar operadores:
Defina los operadores en tipos de valor que sean tipos de lenguajes lgicos integrados,
como la Estructura System.Decimal.
Proporcione mtodos de sobrecarga de operadores slo en la clase en la que se definen
los mtodos. El compilador de C# cumple esta directriz.
Utilice las convenciones de firma y de nomenclatura descritas en Common Language
Specification (CLS). El compilador de C# lo hace automticamente.
Utilice la sobrecarga de operadores en los casos en los que el resultado de la operacin
es obvio. Por ejemplo, tiene sentido poder restar un valor Time de otro valor Time y

52

obtener un TimeSpan. No obstante, no es adecuado utilizar el operador or para crear la


unin de dos consultas a la base de datos, o utilizar shift para escribir una secuencia.
Sobrecargue los operadores de forma simtrica. Por ejemplo, si sobrecarga el operador
de igualdad (==), tambin debe sobrecargar el operador distinto de (!=).
Proporcione firmas alternativas. La mayora de los lenguajes no son compatibles con la
sobrecarga de operadores. Por esta razn, es un requisito de CLS que todos los tipos que
sobrecargan operadores incluyan un mtodo secundario con un nombre apropiado
especfico al dominio que proporcione la funcionalidad equivalente. La inclusin de un
mtodo secundario es un requisito de Common Language Specification (CLS). El ejemplo
siguiente es compatible con CLS.
public struct DateTime
{
public static TimeSpan operator -(DateTime t1, DateTime t2) { }
public static TimeSpan

Subtract(DateTime t1, DateTime t2) { }

V Herencia
5.1 Introduccin a la herencia
Las clases pueden heredar de otra clase. Para conseguir esto, se coloca un signo de dos
puntos despus del nombre de la clase al declarar la clase y se denomina la clase de la
cual se hereda (la clase base) despus del signo de dos puntos, del modo siguiente:
public class A
{
public A() { }
}
public class B : A
{
public B() { }
}
La nueva clase (la clase derivada) obtiene todos los datos no privados y el
comportamiento de la clase base, adems de todos los dems datos y comportamientos
que define para s misma. La nueva clase tiene dos tipos efectivos: el tipo de la nueva
clase y el tipo de la clase que hereda.
Una clase extendida hereda todos los miembros de su clase base, excepto constructores

53

y destructores, y aade nuevos miembros especficos. En esencia, una subclase hereda


las variables y mtodos de su clase base y de todos sus ascendientes. La subclase puede
utilizar estos miembros, puede ocultar las variables miembro o anular (redefinir) los
mtodos. La palabra reservada this permite hacer referencia a la propia clase, mientras
que base se utiliza para referenciar a la clase base y poder llamar a mtodos de la misma
(aunque estn redefinidos). Ninguna de estas palabras reservadas pueden utilizarse
desde mtodos estticos.
5.2 Herencia simple
En C# slo se permite la herencia simple. En otras palabras, una clase puede heredar la
implementacin de una sola clase base. Sin embargo, una clase puede implementar ms
de una interfaz. La siguiente tabla muestra ejemplos de herencia de clases e
implementacin de interfaces:

Herencia

Ejemplo

Ninguna

class ClassA { }

Simple

class DerivedClass: BaseClass { }

Ninguna, implementa dos interfaces

class ImplClass: IFace1, IFace2 { }

Simple, implementa una interfaz

class ImplDerivedClass: BaseClass, IFace1 { }

Los niveles de acceso protected y private slo se permiten en clases anidadas.


Una clase puede contener declaraciones de los siguientes miembros:
Constructores
Destructores
Constantes
Campos
Mtodos
Propiedades
Indizadores
Operadores
Eventos
Delegados
Clases
Interfaces

54

Estructuras

Ejemplo
En el siguiente ejemplo se muestra la declaracin de campos de clase, constructores y
mtodos. Tambin muestra la creacin de instancias de objetos y la impresin de datos
de instancia. En este ejemplo, se declaran dos clases, la clase Kid, que contiene dos
campos privados (name y age) y dos mtodos pblicos. La segunda clase, MainClass, se
utiliza para contener Main.
// keyword_class.cs
// class example using System;
class Kid
{
private int age;
private string name;
/ / Default constructor:
public Kid() { name = "N/A"; }
// Constructor:
public Kid(string name, int age)
{
this.name = name;
this.age = age;
}
// Printing method:
public void PrintKid() {
Console.WriteLine("{0}, {1} years old.", name, age);
}
}
class MainClass
{
static void Main()
{
// Create objects
// Objects must be created using the new operator:
Kid kid1 = new Kid("Craig", 11);
Kid kid2 = new Kid("Sally", 10);
// Create an object using the default constructor:
Kid kid3 = new Kid();
// Display results:
Console.Write("Kid #1: ");
kid1.PrintKid(); Console.Write("Kid #2: ");
kid2.PrintKid();
Console.Write("Kid #3: "); kid3.PrintKid();
}
}
Regla: Los constructores y destructores no se heredan por la subclase.

55

5.4 Clase base y clase derivada


5.4.1 Definicin y 5.4.2 Declaracion
La herencia se realiza a travs de una derivacin, lo que significa que una clase se
declara utilizando una clase base de la cual hereda los datos y el comportamiento. Una
clase base se especifica anexando dos puntos y el nombre de la clase base a
continuacin del nombre de la clase derivada.
Ejemplo

En el ejemplo siguiente, se define una clase pblica que contiene un campo nico, un
mtodo y un mtodo especial denominado constructor. Luego se crean instancias de la
clase con la palabra clave new.
public class Person
{
// Field
public string name;
// Constructor
public Person()
{
name = "unknown";
}
// Method
public void SetName(string newName)
{
name = newName;
}
}
class TestPerson
{
static void Main()
{
Person person1 = new Person();
System.Console.WriteLine(person1.name);
person1.SetName("John Smith");
System.Console.WriteLine(person1.name);
}
}
5.5 Parte protegida
5.5.1 Propsito de la parte protegida
La palabra clave protected es un modificador de acceso a miembros. Un miembro
protegido es accesible dentro de su clase y por clases derivadas.
Un miembro protegido de una clase base es accesible en una clase derivada slo si el
acceso se realiza a travs del tipo de la clase derivada. Por ejemplo, considere el
siguiente segmento de cdigo:

56

// protected_keyword.cs
using System;
class A {
protected int x = 123;
}
class B : A {
static void Main() {
A a = new A();
B b = new B();
// Error CS1540, because x can only be accessed by
// classes derived from A.
// a.x = 10;
// OK, because this class derives from A.
b.x = 10;
}
}
La instruccin a.x =10 genera un error, ya que A no se deriva de B.
Los miembros de una estructura no se pueden proteger, ya que la estructura no se puede
heredar.

Ejemplo
En este ejemplo, la clase DerivedPoint se deriva de Point; por lo tanto, puede obtener
acceso a los miembros protegidos de la clase base directamente desde la clase derivada.
// protected_keyword_2.cs
using System;
class Point
{
protected int x;
protected int y;
}
class DerivedPoint: Point
{
static void Main()
{
DerivedPoint dp = new DerivedPoint();
// Direct access to protected members:
dp.x = 10;
dp.y = 15;
Console.WriteLine("x = {0}, y = {1}", dp.x, dp.y);
}
}
Resultados

57

x = 10, y = 15

5.6 Redefinicin de los miembros de las clases derivadas


Virtual
La palabra clave virtual se utiliza para modificar un mtodo, propiedad, indizador o
declaracin de evento y permite reemplazar a cualquiera de estos en una clase derivada.
En el siguiente ejemplo, cualquier clase que hereda este mtodo puede reemplazarlo:
public virtual double Area() { return x * y; }
Comentarios
Cuando se invoca un mtodo virtual, el tipo en tiempo de ejecucin del objeto se
comprueba para ver si existe un miembro de reemplazo. Se realiza una llamada al
miembro de reemplazo que est en la clase de mayor derivacin, el cual puede ser el
miembro original, si no existe ninguna clase derivada que haya reemplazado el miembro.
De forma predeterminada, los mtodos son no virtuales. No se puede reemplazar un
mtodo no virtual.
No puede utilizar el modificador virtual con los modificadores static, abstract y override.
Las propiedades virtuales funcionan como los mtodos abstractos, salvo en lo que se
refiere a las diferencias en la sintaxis de las declaraciones e invocaciones.
Es incorrecto utilizar el modificador virtual para una propiedad esttica.
Una propiedad virtual heredada se puede reemplazar en una clase derivada si
se incluye una declaracin de propiedad que use el modificador override.
Ejemplo
En este ejemplo, la clase Dimensions contiene las dos coordenadas x, y, y el mtodo
virtual Area(). Las clases de las diferentes figuras, como Circle, Cylinder y Sphere,
heredan la clase Dimensions, que permite calcular el rea de la superficie de cada figura.
Cada clase derivada dispone de su propia implementacin de Area() (mtodo de
reemplazo). El programa calcula y muestra el rea apropiada para cada implementacin
del mtodo Area() segn el objeto asociado al mtodo.
Observe que todas las clases heredadas Circle, Sphere y Cylinder utilizan constructores
que inicializan la clase base, por ejemplo:
public Cylinder(double r, double h): base(r, h) {}
// cs_virtual_keyword.cs
using System;
class TestClass {
public class Dimensions
{
public const double PI = Math.PI;
protected double x, y;
public Dimensions() { }
public Dimensions(double x, double y) { this.x = x; this.y = y; }
public virtual double Area() { return x * y; }
}
public class Circle : Dimensions {
public Circle(double r) : base(r, 0) { }

58

public override double Area() { return PI * x * x; }


}
class Sphere : Dimensions
{
public Sphere(double r) : base(r, 0) { }
public override double Area() { return 4 * PI * x * x; }
}
class Cylinder : Dimensions
{
public Cylinder(double r, double h) : base(r, h) { }
public override double Area() { return 2 * PI * x * x + 2 * PI * x * y; }
}
static void Main()
{
double r = 3.0, h = 5.0;
Dimensions c = new Circle(r);
Dimensions s = new Sphere(r);
Dimensions l = new Cylinder(r, h);
// Display results:
Console.WriteLine("Area of Circle = {0:F2}", c.Area());
Console.WriteLine("Area of Sphere = {0:F2}", s.Area());
Console.WriteLine("Area of Cylinder = {0:F2}", l.Area());
}
}
Resultados
Area of Circle = 28.27
Area of Sphere = 113.10
Area of Cylinder = 150.80
Override
El modificador override es necesario para ampliar o modificar la implementacin
abstracta o virtual de un mtodo, propiedad, indizador o evento heredado.
En este ejemplo, la clase Square debe proporcionar una implementacin de reemplazo de
Area porque sta se hereda de la clase abstracta ShapesClass:
abstract class ShapesClass
{
abstract public int Area();
}
class Square : ShapesClass
{
int x, y;
// Because ShapesClass.Area is abstract, failing to override
// the Area method would result in a compilation error.
public override int Area() { return x * y; }
}

59

Comentarios
El mtodo override proporciona una nueva implementacin de un miembro heredado de
una clase base. El mtodo reemplazado por una declaracin override se conoce como
mtodo base reemplazado. El mtodo base reemplazado debe tener la misma firma que
el mtodo override. No se puede reemplazar un mtodo esttico o no virtual. El mtodo
base reemplazado debe ser virtual, abstract u override.
Una declaracin override no puede cambiar la accesibilidad del mtodo virtual. El mtodo
override y el mtodo virtual deben tener el mismo modificador de nivel de acceso.
No se pueden utilizar los modificadores new, static, virtual o abstract para modificar un
mtodo override.
Una declaracin de propiedad de reemplazo debe especificar el mismo modificador de
acceso, tipo y nombre que la propiedad heredada, y la propiedad reemplazada debe ser
virtual, abstract u override.
Ejemplo
Este ejemplo define una clase base denominada Employee y una clase derivada
denominada SalesEmployee. La clase SalesEmployee incluye una propiedad adicional,
salesbonus, y reemplaza al mtodo CalculatePay para tenerlo en cuenta.
using System;
class TestOverride
{
public class Employee
{
public string name;
// Basepay is defined as protected, so that it may be
// accessed only by this class and derrived classes. protected decimal basepay;
// Constructor to set the name and basepay values.
public Employee(string name, decimal basepay)
{
this.name = name;
this.basepay = basepay;
}
// Declared virtual so it can be overridden.
public virtual decimal CalculatePay() { return basepay; }
}
// Derive a new class from Employee.
public class SalesEmployee : Employee
{
// New field that will affect the base pay. private decimal salesbonus;
// The constructor calls the base-class version, and
// initializes the salesbonus field.
public SalesEmployee(string name, decimal basepay, decimal salesbonus) :
base(name, basepay)
{
this.salesbonus = salesbonus;
}
// Override the CalculatePay method
// to take bonus into account.

60

public override decimal CalculatePay()


{
return basepay + salesbonus;
}
}
static void Main()
{
// Create some new employees.
SalesEmployee employee1 = new SalesEmployee("Alice", 1000, 500);
Employee employee2 = new Employee("Bob", 1200);
Console.WriteLine("Employee " + employee1.name + " earned: " +
employee1.CalculatePay());
Console.WriteLine("Employee " + employee2.name + " earned: " +
employee2.CalculatePay());
}
}
Resultados
Employee Alice earned: 1500 Employee Bob earned: 1200
5.7 Clases virtuales y visibilidad
En C# no hay clases virtuales directamente.
5.8 Constructores y destructores en las clases derivadas
Constructores
Todos los constructores de instancia (excepto aquellos para la clase object) incluyen
implcitamente una invocacin de otro constructor de instancia inmediatamente antes que
el cuerpo-del-constructor (constructor-body). El constructor que se invoca implcitamente
est determinado por el inicializador-de-constructor (constructor-initializer):
Un inicializador de constructor de instancia de la forma base (lista de argumentos
opcional o argument-listopt) causa la invocacin de un constructor de instancia desde
la clase base directa. El conjunto de constructores de instancia candidatos est
formado por todos los constructores de instancia accesibles que contiene la clase
base directa. Si el conjunto est vaco o no es posible identificar el mejor constructor
de instancia, se produce un error en tiempo de compilacin.
Un inicializador de constructor de instancia de la forma this(lista-de-argumentosopcional) causa la invocacin de un constructor de instancia desde la propia clase. El
conjunto de constructores de instancia candidatos est formado por todos los
constructores de instancia accesibles declarados en la clase. Si el conjunto est
vaco o no es posible identificar el mejor constructor de instancia, se produce un error
en tiempo de compilacin. Si la declaracin de un constructor de instancia incluye un
inicializador de constructor que invoca al propio constructor, se produce un error en
tiempo de compilacin.
Si un constructor de instancia no tiene inicializador de constructor, se proporciona
implcitamente uno de la forma base(). Por lo tanto, una declaracin de constructor de
instancia de la forma
C(...) {...}
equivale exactamente a
C(...): base() {...}

61

El mbito de los parmetros dados en la lista-de-parmetros-formales de una declaracin


de constructor de instancia incluye el inicializador de constructor de dicha declaracin. Por
lo tanto, un inicializador de constructor puede tener acceso a los parmetros del
constructor. Por ejemplo:
class A
{
public A(int x, int y) {}
}
class B: A
{
public B(int x, int y): base(x + y, x - y) {}
}
Un inicializador de constructor de instancia no puede tener acceso a la instancia que est
siendo creada. Por ello, se produce un error en tiempo de compilacin si se hace
referencia a this en una expresin de argumento del inicializador del constructor, al igual
que se produce un error en tiempo de compilacin si una expresin de argumento hace
referencia a cualquier miembro de instancia a travs del nombre-simple.
Ejecucin de constructor
Los inicializadores de variables se transforman en instrucciones de asignacin, que se
ejecutan antes de la invocacin del constructor de instancia de la clase base. Tal
ordenamiento garantiza que todos los campos de instancia son inicializados por sus
inicializadores de variable antes de que se ejecute cualquier instruccin que tenga acceso
a la instancia.
Dado el ejemplo:
using System;
class A
{
public A() {
PrintFields();
}
public virtual void PrintFields() {}
}
class B: A
{
int x = 1;
int y;
public B() {
y = -1;
}
public override void PrintFields() {
Console.WriteLine("x = {0}, y = {1}", x, y);
}
}
cuando se utiliza new B() para crear una instancia de B, el resultado que se produce es:
x = 1, y = 0

62

El valor de x es 1 porque el inicializador de variable se ejecuta antes de que se invoque el


constructor de instancia de la clase base. Sin embargo, el valor de y es 0 (el valor
predeterminado para un tipo int) porque la asignacin a y no se ejecuta hasta que no
devuelve el control el constructor de la clase base.
Es til considerar los inicializadores de variables de instancia y los inicializadores de
constructor como instrucciones que se insertan automticamente antes del cuerpo-delconstructor. El ejemplo
using System;
using System.Collections;
class A
{
int x = 1, y = -1, count;
public A() {
count = 0;
}
public A(int n) {
count = n;
}
}
class B: A
{
double sqrt2 = Math.Sqrt(2.0);
ArrayList items = new ArrayList(100);
int max;
public B(): this(100) {
items.Add("default");
}
public B(int n): base(n 1) {
max = n;
}
}
contiene varios inicializadores de variable e inicializadores de constructor de ambas
formas (base y this). El cdigo del ejemplo se corresponde con el cdigo siguiente, donde
cada comentario indica una instruccin que se inserta automticamente (la sintaxis
utilizada para invocar el constructor insertado automticamente no es correcta, pero sirve
para ilustrar el mecanismo).
using System.Collections;
class A
{
int x, y, count;
public A() {
x = 1;
// Variable initializer
y = -1;
// Variable initializer
object();
// Invoke object() constructor
count = 0;
}
public A(int n) {
x = 1;
// Variable initializer

63

y = -1;
object();
count = n;

// Variable initializer
// Invoke object() constructor

}
}
class B: A
{
double sqrt2;
ArrayList items;
int max;
public B(): this(100) {
B(100);
// Invoke B(int) constructor
items.Add("default");
}
public B(int n): base(n 1) {
sqrt2 = Math.Sqrt(2.0);
// Variable initializer
items = new ArrayList(100); // Variable initializer
A(n 1);
// Invoke A(int) constructor
max = n;
}

Destructores
Un destructor es un miembro que implementa las acciones necesarias para destruir una
instancia de una clase. Un destructor se declara utilizando una declaracin-de-destructor
(destructor-declaration):
destructor-declaration:
attributesopt externopt ~ identifier ( ) destructor-body
destructor-body:
block
;
Una declaracin-de-destructor puede incluir un conjunto de atributos (attributes).
El identificador de un declarador-de-destructor debe nombrar la clase en la que se declara
el destructor. Si se especifica cualquier otro nombre, se produce un error en tiempo de
compilacin.
Cuando una declaracin de destructor incluye un modificador extern, se dice que es un
destructor externo. Debido a que la declaracin de destructor externo no proporciona una
implementacin real, su cuerpo-de-destructor (destructor-body) consiste en un punto y
coma. Para el resto de destructores, el cuerpo-de-destructor consiste en un bloque que
especifica las instrucciones necesarias para destruir una instancia de la clase. Un cuerpode-destructor corresponde exactamente al cuerpo-de-un-mtodo de un mtodo de
instancia con un tipo de valor devuelto void.
Los destructores no se heredan. Por lo tanto, una clase slo tiene el destructor que se
puede declarar en la propia clase.
Como un destructor no puede tener parmetros, no puede ser sobrecargado; por lo tanto,
una clase slo puede tener como mximo un destructor.
Los destructores se invocan automticamente y no se pueden invocar explcitamente. Una
instancia se convierte en candidata para destruccin cuando ya ninguna parte de cdigo

64

puede utilizarla. La ejecucin del destructor de la instancia puede ocurrir en cualquier


momento una vez que la instancia se convierta en candidata para destruccin. Cuando se
destruye una instancia, se llama a los destructores de su cadena de herencia en orden, de
la ms derivada a la menos derivada. Un destructor puede ejecutarse en cualquier
subproceso. Para leer una explicacin ms detallada de las reglas que controlan cmo y
cundo se ejecuta un destructor. El resultado del ejemplo
using System;
class A
{
~A() {
Console.WriteLine("A's destructor");
}
}
class B: A
{
~B() {
Console.WriteLine("B's destructor");
}
}
class Test
{
static void Main() {
B b = new B();
b = null;
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
B's destructor
A's destructor
ya que en una cadena de herencia los destructores se llaman en orden, de la ms
derivada a la menos derivada.
Los destructores se implementan reemplazando el mtodo virtual Finalize en
System.Object. Los programas de C# no permiten reemplazar este mtodo o llamarlo
directamente (o a reemplazos del mismo). Por ejemplo, el programa
class A
{
override protected void Finalize() {} // error
public void F() {
this.Finalize();
// error
}
}
contiene dos errores.
El compilador se comporta como si este mtodo, y sus reemplazos, no existieran. Por lo
tanto, este programa:

65

class A
{
void Finalize() {}
}

// permitted

es vlido, y el mtodo mostrado oculta el mtodo Finalize de System.Object.


Para leer una explicacin del comportamiento producido cuando se inicia una excepcin
desde un destructor.

VI Polimorfismo y reutilizacin
6.1 Concepto del polimorfismo
El polimorfismo ayuda al programador a escribir cdigo ms fcil de modificar y ampliar.
Polimorfismo es pues la capacidad de un objeto para responder a un mensaje basado en
su tipo y posicin en la jerarqua de clases. Una respuesta apropiada implica la capacidad
del objeto para elegir la implementacin del mtodo que mejor se adapte a sus
caractersticas. Mediante tcnicas polimrficas es posible escribir cdigo que manipule
objetos de muchas clases diferentes de un modo uniforme y consistente, con
independencia de su tipo exacto. La flexibilidad y generalidad de las estructuras
polimrficas es una de las ventajas ms significativas de la programacin orientada a
objetos.
La estrategia para desarrollar una estructura polimrfica comienza con la identificacin de
los mtodos comunes a travs de un grupo de tipos de objetos similares pero no idnticos
y organizando una jerarqua de clases donde los mtodos comunes se sitan en la clase
base, mientras que los restantes se organizan en clases derivadas, deducidas de esta
clase base. La clase base define una interfaz a travs de la cual un objeto de cualquiera
de las subclases especificadas se puede manipular. Es importante considerar que los
mtodos de la interfaz compartida se deben declarar en la clase base y han de ser de
virtuales. De esta forma las clases derivadas pueden redefinir dichos mtodos mediante el
modificador override, se establece ligadura dinmica y, en tiempo de ejecucin, se busca
cul es la clase del objeto que ha recibido el mensaje y se decide qu mtodo se ejecuta.
6.2 Clases Abstractas
6.2.1 Definicin y 6.2.2 Redefinicin

66

Las clases abstractas estn estrechamente relacionadas con las interfaces. Son clases de
las que no es posible crear instancias; frecuentemente, estn implementadas slo
parcialmente o no estn implementadas. Una de las principales diferencias entre las
clases abstractas y las interfaces es que una clase puede implementar un nmero
ilimitado de interfaces, pero slo puede heredar de una clase abstracta (o de cualquier
otra clase). Una clase derivada de una clase abstracta conserva la capacidad de
implementar interfaces. Las clases abstractas son tiles para crear componentes, porque
permiten especificar un nivel invariable de funcionalidad para algunos mtodos y aplazar
la implementacin de otros hasta que se necesite una implementacin especfica de la
clase. Tambin admiten bien el uso de versiones, porque, si se necesita una funcionalidad
adicional en las clases derivadas, se puede agregar a la clase base sin romper el cdigo.
abstract class WashingMachine
{
public WashingMachine()
{
// Code to initialize the class goes here.
}
abstract public void Wash();
abstract public void Rinse(int loadSize);
abstract public long Spin(int speed);
}
class MyWashingMachine : WashingMachine
{
public MyWashingMachine()
{
// Initialization code goes here.
}
override public void Wash()
{
// Wash code goes here.
}
override public void Rinse(int loadSize)
{
// Rinse code goes here.

67

}
override public long Spin(int speed)
{
// Spin code goes here.
}
}
Cuando se implementa una clase abstracta, debe implementarse cada mtodo abstracto
(MustOverride) de esta clase; cada mtodo implementado debe recibir el mismo nmero y
tipo de argumentos y devolver el mismo valor que el mtodo especificado en la clase
abstracta.
6.3 Definicin de una interfaz y 6.4 Implementacin de la definicin de una interfaz.
Las interfaces se definen utilizando la palabra clave interface. Por ejemplo:
interface IComparable
{
int CompareTo(object obj);
}
Las interfaces describen un grupo de comportamientos relacionados que pueden
pertenecer a cualquier clase o estructura. Las interfaces pueden estar compuestas de
mtodos, propiedades, eventos, indizadores o cualquier combinacin de estos cuatro
tipos de miembros. Una interfaz no puede contener campos. Los miembros de interfaz
son automticamente pblicos.
Las clases y estructuras se pueden heredar de interfaces de manera similar a como las
clases pueden heredar una clase base o estructura, con dos excepciones:
Una clase o estructura puede heredar ms de una interfaz.
Cuando una clase o estructura hereda una interfaz, hereda definiciones de

miembro, pero no implementaciones. Por ejemplo:


public class Minivan : Car, IComparable
{
public int CompareTo(object obj)
{
//implementation of CompareTo
return 0; //if the Minivans are equal
}
}

68

Para implementar un miembro de interfaz, el miembro correspondiente de la clase debe


ser pblico, no esttico y tener el mismo nombre y la misma firma que el miembro de
interfaz. Las propiedades e indizadores de una clase pueden definir descriptores de
acceso adicionales para una propiedad o indizador definidos en una interfaz. Por ejemplo,
una interfaz puede declarar una propiedad con un descriptor de acceso get, pero la clase
que implementa la interfaz puede declarar la misma propiedad con descriptores de acceso
get y set. Sin embargo, si la propiedad o el indizador utiliza una implementacin explcita,
los descriptores de acceso deben coincidir.
Las interfaces y los miembros de interfaz son abstractos; las interfaces no proporcionan
una implementacin predeterminada.
La interfaz IComparable informa al usuario del objeto de que ste se puede comparar con
otros objetos del mismo tipo y el usuario de la interfaz no necesita saber cmo se
implementa.
Las interfaces pueden heredar otras interfaces. Es posible que una clase herede una
interfaz varias veces, a travs de las clases base o interfaces que hereda. En ese caso, la
clase slo puede implementar la interfaz una vez, siempre que sta se declare como parte
de la nueva clase. Si la interfaz heredada no est declarada como parte de la nueva
clase, la clase base que la declar proporcionar su implementacin. Es posible que una
clase base implemente miembros de interfaz a travs de miembros virtuales. En ese caso,
la clase que hereda la interfaz puede cambiar el comportamiento de la interfaz
reemplazando los miembros virtuales.

Informacin general sobre interfaces


Una interfaz tiene las siguientes propiedades:
Una interfaz es similar a una clase base abstracta. Cualquier tipo no abstracto

que hereda la interfaz debe implementar todos sus miembros.


No se pueden crear instancias directamente de una interfaz.
Las interfaces pueden contener eventos, mtodos, indizadores y propiedades.
Las interfaces no contienen implementaciones de mtodos.
Las clases y estructuras se pueden heredar de ms de una interfaz.
Una interfaz se puede heredar de varias interfaces.
6.5 Reutilizacin de la definicin de una interfaz.
Implementacin explcita de interfaz
Si una clase implementa dos interfaces que contienen un miembro con la misma firma, la
implementacin de ese miembro en la clase har que ambas interfaces usen ese miembro
como implementacin. Por ejemplo:
interface IControl
{
void Paint();
}
interface ISurface
{

69

void Paint();
}
class SampleClass : IControl, ISurface
{
// Both ISurface.Paint and IControl.Paint call this method.
public void Paint()
{
}
}
Sin embargo, los miembros de dos interfaces no realizan la misma funcin, esto puede
llevar a una implementacin incorrecta de una o ambas interfaces. Es posible implementar
un miembro de interfaz explcitamente, creando un miembro de clase que slo se llama a
travs de la interfaz y es especfico de sta. Esto se puede llevar a cabo asignando al
miembro de clase el nombre de la interfaz y un punto. Por ejemplo:
public class SampleClass : IControl, ISurface
{
void IControl.Paint()
{
System.Console.WriteLine("IControl.Paint");
}
void ISurface.Paint()
{
System.Console.WriteLine("ISurface.Paint");
}
}
El miembro de clase IControl.Paint slo est disponible a travs de la interfaz IControl,
mientras que ISurface.Paint slo est disponible a travs de ISurface. Ambas
implementaciones de mtodo son independientes y ninguna est directamente disponible
en la clase. Por ejemplo:
SampleClass obj = new SampleClass();
//obj.Paint(); // Compiler error.
IControl c = (IControl)obj;
c.Paint(); // Calls IControl.Paint on SampleClass.
ISurface s = (ISurface)obj;
s.Paint(); // Calls ISurface.Paint on SampleClass.
La implementacin explcita tambin se usa para resolver casos donde cada una de las
dos interfaces declara miembros diferentes del mismo nombre como propiedad y mtodo:
interface ILeft
{
int P { get;}
}
interface IRight
{

70

int P();
}
Para implementar ambas interfaces, una clase tiene que utilizar implementacin explcita,
ya sea para la propiedad P, el mtodo P o ambos, con el fin de evitar un error del
compilador. Por ejemplo:
class Middle : ILeft, IRight
{
public int P() { return 0; }
int ILeft.P { get { return 0; } }
}
Propiedades de interfaces
A continuacin se muestra un ejemplo de descriptor de acceso de un indizador de
interfaz:
public interface ISampleInterface
{
// Property declaration:
string Name
{
get;
set;
}
}
Un identificador de acceso de una propiedad de interfaz no tiene cuerpo. As, el propsito
de los descriptores de acceso es indicar si la propiedad es de lectura y escritura, de slo
lectura o de slo escritura.
Ejemplo
En este ejemplo, la interfaz IEmployee tiene una propiedad de lectura y escritura, Name, y
una propiedad de slo lectura, Counter. La clase Employee implementa la interfaz
IEmployee y utiliza las dos propiedades. El programa lee el nombre de un empleado
nuevo y el nmero actual de empleados, y muestra como resultado el nombre del
empleado y el nuevo nmero de empleados calculado.
Se podra utilizar el nombre completo de la propiedad, que hace referencia a la interfaz en
la que se declara el miembro. Por ejemplo:
string IEmployee.Name
{
get { return "Employee Name"; }
set { }
}
Por ejemplo, si la clase Employee implementa dos interfaces ICitizen y IEmployee, y
ambas tienen la propiedad Name, ser necesario implementar explcitamente el miembro
de interfaz. Es decir, la siguiente declaracin de propiedad:

71

string IEmployee.Name
{
get { return "Employee Name"; }
set { }
}
implementa la propiedad Name en la interfaz IEmployee, mientras que la declaracin:
string ICitizen.Name
{
get { return "Citizen Name"; }
set { }
}
implementa la propiedad Name en la interfaz ICitizen.
interface IEmployee
{
string Name
{
get;
set;
}
int Counter
{
get;
}
}
public class Employee : IEmployee
{
public static int numberOfEmployees;
private string name;
public string Name // read-write instance property
{
get
{
return name;
}
set
{
name = value;
}
}
private int counter;
public int Counter // read-only instance property
{
get

72

{
return counter;
}
}
public Employee() // constructor
{
counter = ++counter + numberOfEmployees;
}
}
class TestEmployee
{
static void Main()
{
System.Console.Write("Enter number of employees: ");
Employee.numberOfEmployees = int.Parse(System.Console.ReadLine());
Employee e1 = new Employee();
System.Console.Write("Enter the name of the new employee: ");
e1.Name = System.Console.ReadLine();
System.Console.WriteLine("The employee information:");
System.Console.WriteLine("Employee number: {0}", e1.Counter);
System.Console.WriteLine("Employee name: {0}", e1.Name);
}
}
Indizadores en interfaces
Los descriptores de acceso de los indizadores de interfaz se diferencian de los
descriptores de acceso de los indizadores de clase en los siguientes aspectos:
Los descriptores de acceso de interfaz no utilizan modificadores.
Un identificador de acceso de interfaz no tiene cuerpo.

As, el propsito del descriptor de acceso es indicar si el indizador es de lectura y


escritura, de slo lectura o de slo escritura.
A continuacin se muestra un ejemplo de descriptor de acceso de un indizador de
interfaz:
public interface ISomeInterface
{
//...
// Indexer declaration:
string this[int index]
{
get;
set;
}
}

73

La firma de un indizador debe ser diferente de las firmas de los dems indizadores
declarados en la misma interfaz.
Ejemplo
En el ejemplo siguiente se muestra cmo se implementan indizadores de interfaz:
// Indexer on an interface:
public interface ISomeInterface
{
// Indexer declaration:
int this[int index]
{
get;
set;
}
}
// Implementing the interface.
class IndexerClass : ISomeInterface
{
private int[] arr = new int[100];
public int this[int index] // indexer declaration
{
get
{
// Check the index limits.
if (index < 0 || index >= 100)
{
return 0;
}
else
{
return arr[index];
}
}
set
{
if (!(index < 0 || index >= 100))
{
arr[index] = value;
}
}
}
}
class MainClass
{
static void Main()
{
IndexerClass test = new IndexerClass();

74

// Call the indexer to initialize the elements #2 and #5.


test[2] = 4;
test[5] = 32;
for (int i = 0; i <= 10; i++)
{
System.Console.WriteLine("Element #{0} = {1}", i, test[i]);
}
}
}
Declarar un evento en una interfaz e implementarlo en una clase
Este ejemplo muestra que es posible declarar un evento en una interfaz e implementarlo
en una clase.
public delegate void TestDelegate(); // delegate declaration
public interface ITestInterface
{
event TestDelegate TestEvent;
void FireAway();
}
public class TestClass : ITestInterface
{
public event TestDelegate TestEvent;
public void FireAway()
{
if (TestEvent != null)
{
TestEvent();
}
}
}
public class MainClass
{
static private void F()
{
System.Console.WriteLine("This is called when the event fires.");
}
static void Main()
{
ITestInterface i = new TestClass();
i.TestEvent += new TestDelegate(F);
i.FireAway();
}
}

75

VII Excepciones
7.1 Definicin
7.1.1 Que son las excepciones
Las caractersticas de control de excepciones del lenguaje C# proporcionan una manera
de afrontar cualquier situacin inesperada o excepcional que se presente mientras se
ejecuta un programa. El control de excepciones utiliza las palabras clave try, catch y
finally para intentar acciones que podran no realizarse correctamente, controlar errores y
limpiar los recursos despus. Common Language Runtime (CLR), las bibliotecas de otro
fabricante o el cdigo de aplicacin que utiliza la palabra clave throw pueden generar
excepciones.
En este ejemplo, el mtodo hace una prueba para realizar una divisin por cero y detecta
el error. Sin el control de excepciones, este programa finalizara con un error
DivideByZeroException no controlado.
int SafeDivision(int x, int y)
{
try
{
return (x / y);
}
catch (System.DivideByZeroException dbz)

76

{
System.Console.WriteLine("Division by zero attempted!"); return 0;
}
}
Informacin general sobre excepciones
Las excepciones tienen las propiedades siguientes:
Cuando la aplicacin encuentra una circunstancia excepcional, como una

divisin por cero o una advertencia de que no hay suficiente memoria, se


genera una excepcin.
Cuando se produce una excepcin, el flujo de control salta inmediatamente a
un controlador de excepciones asociado, si hay alguno presente.
Si no hay un controlador de excepciones para una excepcin determinada, el
programa deja de ejecutarse y presenta un mensaje de error.
Las acciones que pueden producir una excepcin se ejecutan con la palabra
clave try.
Un controlador de excepciones es un bloque de cdigo que se ejecuta cuando
se produce una excepcin. En C#, la palabra clave catch se utiliza para definir
un controlador de excepciones.
Un programa que utiliza la palabra clave throw puede generar explcitamente
excepciones.
Los objetos de excepcin contienen informacin detallada sobre el error que
incluye el estado de la pila de llamadas y una descripcin de texto del error.
El cdigo se ejecuta en un bloque finally aunque se produzca una excepcin,
permitiendo as que el programa libere recursos.
7.1.2 Clases de excepciones
Excepciones predefinidas por el lenguaje.
El Common Language Runtime (CLR) de .NET Framework produce automticamente
algunas excepciones como resultado de operaciones bsicas en las que se produce un
error. A continuacin se muestran estas excepciones y sus condiciones de error.

Excepcin

Descripcin

ArithmeticException

Clase base de las excepciones producidas durante


operaciones aritmticas, como DivideByZeroException y
OverflowException.

ArrayTypeMismatchException Se produce cuando una matriz no puede almacenar un


elemento dado porque el tipo real del elemento es
incompatible con el tipo real de la matriz.
DivideByZeroException

Se produce cuando tiene lugar un intento de dividir un


valor integral por cero.

IndexOutOfRangeException

Se produce cuando tiene lugar un intento de indizar una


matriz cuando el ndice es menor que cero o se encuentra

77

fuera de los lmites de la matriz.


InvalidCastException

Se produce cuando tiene lugar un error en tiempo de


ejecucin en una conversin explcita de un tipo base a
una interfaz o a un tipo derivado.

NullReferenceException

Se produce cuando se utiliza una referencia null de


manera que hace obligatorio el objeto al que se hace
referencia.

OutOfMemoryException

Se produce cuando tiene lugar un error al intentar asignar


memoria mediante new.

OverflowException

Se produce cuando una operacin aritmtica en un


contexto checked produce un desbordamiento.

StackOverflowException

Se produce cuando se agota la pila de excepciones debido


a la existencia de demasiadas llamadas al mtodo
pendientes; normalmente, suele indicar un nivel de
recursividad muy profundo o infinito.

TypeInitializationException

Se produce cuando un constructor esttico produce una


excepcin sin que haya clusulas catch compatibles para
capturarla.

7.1.3 Propagacin
La instruccin throw (throw-statement) inicia una excepcin.
throw-statement:
throw expressionopt ;
Una instruccin throw con expresin inicia el valor resultante de evaluar la expresin. La
expresin debe denotar un valor del tipo de clase System.Exception o de un tipo de clase
que derive de System.Exception. Si la expresin evaluada devuelve null, se inicia
entonces System.NullReferenceException.
Una instruccin throw sin expresin slo se puede utilizar en un bloque catch, en cuyo
caso esta instruccin volver a iniciar la excepcin que est controlando en ese momento
el bloque catch.
Como una instruccin throw transfiere incondicionalmente el control a otra parte del
cdigo, el punto final de una instruccin throw nunca es alcanzable.
Cuando se inicia una excepcin, el control se transfiere a la primera clusula catch de una
instruccin try envolvente que pueda controlar la excepcin. El proceso que tiene lugar
desde el punto de inicio de la excepcin hasta el punto en que se transfiere el control a un
controlador de excepciones adecuado es conocido con el nombre de propagacin de
excepcin. La propagacin de una excepcin consiste en evaluar repetidamente los

78

siguientes pasos hasta encontrar una clusula catch que coincida con la excepcin. En
esta descripcin, el punto de inicio es la ubicacin desde la que se inicia la excepcin.

En el miembro de funcin actual, se examina cada instruccin try que


envuelve al punto de inicio. Se evalan los siguientes pasos para cada
instruccin S, comenzando con la instruccin try ms interna y terminando con la
ms externa:

Si el bloque try de S encierra al punto de inicio y S tiene una o varias


clusulas catch, se examinan las clusulas catch en orden de aparicin hasta
encontrar un controlador adecuado para la excepcin. La primera clusula catch
que especifique el tipo de excepcin o un tipo base del tipo de excepcin se
considera una coincidencia. Una clusula catch general es una coincidencia para
cualquier tipo de excepcin. Si se encuentra una clusula catch coincidente,
termina la propagacin de excepcin y se transfiere el control al bloque de la
clusula catch.

En caso contrario, si el bloque try o un bloque catch de S encierra el punto


de inicio y S tiene un bloque finally, el control se transfiere al bloque finally. Si el
bloque finally inicia otra excepcin, finaliza el procesamiento de la excepcin
actual. Si no, cuando el control alcanza el punto final del bloque finally, contina
con el procesamiento de la excepcin actual.

Si no se encontr un controlador de excepciones en la llamada del


miembro de funcin actual, finaliza la llamada al miembro de funcin. Los pasos
anteriores se repiten para el llamador del miembro de funcin con el punto de
inicio que corresponda a la instruccin desde la que se invoc al miembro de
funcin.

Si el procesamiento de la excepcin finaliza todas las llamadas a miembros


de funcin del subproceso actual indicando que el subproceso no ha encontrado
controlador para la excepcin, dicho subproceso finaliza. El impacto de esta
terminacin se define segn la implementacin.
// throw example using System;
public class ThrowTest
{
static void Main()
{
string s = null;
if (s == null)
{
throw new ArgumentNullException();
}
Console.Write("The string s is null"); // not executed
}
}
7.2 Gestin de excepciones
7.2.1 Manejo de excepciones
Manejo de excepciones estructuradas

79

Las excepciones en C# las podemos controlar usando las instrucciones try / catch / finally.
Estas instrucciones realmente son bloques de instrucciones, y por tanto estarn
delimitadas con un par de llaves.
Cuando queramos controlar una parte del cdigo que puede producir un error lo incluimos
dentro del bloque try, si se produce un error, ste lo podemos detectar en el bloque catch,
por ltimo, independientemente de que se produzca o no una excepcin, podemos
ejecutar el cdigo que incluyamos en el bloque finally.
Cuando creamos una estructura de control de excepciones no estamos obligados a usar
los tres bloques, aunque el primero: try si es necesario, ya que es el que le indica al
compilador que tenemos intencin de controlar los errores que se produzcan. Por tanto
podemos crear un "manejador" de excepciones usando los tres bloques, usando try y
catch o usando try y finally.
Veamos ahora con ms detalle cada uno de estos bloques y que es lo que podemos
hacer en cada uno de ellos.
Bloque try
En este bloque incluiremos el cdigo en el que queremos comprobar los errores. El cdigo
a usar ser un cdigo normal, es decir, no tenemos que hacer nada en especial, ya que
en el momento que se produzca el error se usar (si hay) el cdigo del bloque catch.
Bloque catch
Si se produce una excepcin, sta la capturamos en un bloque catch.
En el bloque catch podemos indicar que tipo de excepcin queremos capturar, para ello
usaremos una variable de tipo Exception, la cual puede ser del tipo de error especfico
que queremos controlar o de un tipo genrico.
Por ejemplo, si sabemos que nuestro cdigo puede producir un error al trabajar con
ficheros, podemos usar un cdigo como ste:

Nota
try
{
// cdigo para trabajar con ficheros, etc.
}
catch(System.IO.IOException ex)
{
// el cdigo a ejecutar cuando se produzca ese error
}
Si nuestra intencin es capturar todos los errores que se produzcan, es decir, no
queremos hacer un filtro con errores especficos, podemos usar la clase Exception como
tipo de excepcin a capturar. La clase Exception es la ms genrica de todas las clases
para manejo de excepciones, por tanto capturar todas las excepciones que se
produzcan.
Nota

80

try
{
// cdigo que queremos controlar
}
catch(System.Exception ex)
{
// el cdigo a ejecutar cuando se produzca cualquier error
}
Aunque si no vamos usar la variable indicada en el bloque Catch, pero queremos que no
se detenga la aplicacin cuando se produzca un error, podemos hacerlo de esta forma:
Nota
try
{
// cdigo que queremos controlar
}
catch
{
// el cdigo a ejecutar cuando se produzca cualquier error
}
La variable indicada en el bloque catch la podemos usar para mostrar un mensaje al
usuario o para obtener informacin extra sobre el error, pero no siempre vamos a hacer
uso de esa variable, en ese caso podemos utilizar el cdigo anterior, en el que no se usa
una variable y tampoco se indica el tipo de error que queremos interceptar. Pero es
posible que nuestra intencin sea capturar errores de un tipo concreto sin necesidad de
utilizar una variable, en ese caso podemos crear un bloque catch como el siguiente, en el
que solo se indica el tipo de excepcin:
Nota
try
{
// cdigo que queremos controlar
}
catch(FormatException)
{
// interceptar los errores del tipo FormatException
}
Varias capturas de errores en un mismo bloque try/catch
En un mismo try/catch podemos capturar diferentes tipos de errores, para ello podemos
incluir varios bloques catch, cada uno de ellos con un tipo de excepcin diferente.
Es importante tener en cuenta que cuando se produce un error y usamos varios bloques
catch, el CLR de .NET buscar la captura que mejor se adapte al error que se ha
producido, pero siempre lo har examinando los diferentes bloques catch que hayamos
indicado empezando por el indicado despus del bloque try, por tanto deberamos poner
las ms genricas al final, de forma que siempre nos aseguremos de que las capturas de

81

errores ms especficas se intercepten antes que las genricas. Aunque el propio


compilador de C# detectar si hay capturas de errores genricas antes que las ms
especficas, avisndonos de ese hecho.
En el siguiente cdigo capturamos un error especfico y tambin uno genrico, con idea
de que tengamos siempre controlado cualquier error que se produzca:
Nota
try
{
// cdigo que queremos controlar
}
catch(FormatException)
{
// captura de error de formato
}
catch(Exception ex)
{
// captura del resto de errores
Bloque finally
En este bloque podemos indicar las instrucciones que queremos que se ejecuten, se
produzca o no una excepcin. De esta forma nos aseguramos de que siempre se
ejecutar un cdigo, por ejemplo para liberar recursos, se haya producido un error o no.
En este cdigo tenemos tres capturas de errores diferentes y un bloque finally que
siempre se ejecutar, se produzca o no un error:

Nota
int i, j;
//
try
{
Console.Write("Un numero ");
i = Convert.ToInt32(Console.ReadLine());
Console.Write("Otro numero ");
j = Convert.ToInt32(Console.ReadLine());
int r = i / j;
Console.WriteLine("El resultado es: {0}", r);
}
catch (FormatException)
{
Console.WriteLine("No es un nmero vlido");
// Salimos de la funcin, pero se ejecutar el finally
return;
}
catch (DivideByZeroException)
{

82

Console.WriteLine("La divisin por cero no est permitida.");


}
catch (Exception ex)
{
// Captura del resto de excepciones
Console.WriteLine(ex.Message);
}
finally
{
// Este cdigo siempre se ejecutar
Console.WriteLine("Se acab");
}
Nota
Nota: Hay que tener en cuenta de que incluso si usamos return dentro de un bloque de
control de errores, se ejecutar el cdigo indicado en el bloque finally.
Captura de errores no controlados
Como es lgico, si no controlamos las excepciones que se puedan producir en nuestras
aplicaciones, stas sern inicialmente controladas por el propio runtime de .NET, en estos
casos la aplicacin se detiene y se muestra el error al usuario. Pero esto es algo que no
deberamos consentir, por tanto siempre deberamos detectar todos los errores que se
produzcan en nuestras aplicaciones, pero a pesar de que lo intentemos, es muy probable
que no siempre podamos conseguirlo.
Una forma de hacerlo es iniciando nuestra aplicacin dentro de un bloque try/catch, de
esta forma, cuando se produzca el error, se capturar en ese bloque catch, porque
cuando el runtime de .NET se encuentra con una excepcin, lo que hace es revisar "la
pila" de llamadas y buscar algn try/catch, si lo encuentra, lo utiliza, y si no lo encuentra,
se encarga de lanzar la excepcin deteniendo el programa.
Esto es importante saberlo, no ya por detectar esos errores que no hemos tenido la
previsin de controlar, sino porque es posible que si un error se produce dentro de un
mtodo en el que no hay captura de errores, pero antes de llamar a ese mtodo hemos
usado un try/catch, el error ser interceptado por ese catch, aunque posiblemente ni
siquiera lo pusimos pensando que poda capturar errores producidos en otros niveles ms
profundos de nuestra aplicacin.
7.2.2 Lanzamiento de excepciones
El propsito de una instruccin finally es asegurarse de que la limpieza necesaria de
objetos, por lo general objetos que contienen recursos externos, se produzca
inmediatamente, incluso cuando se produce una excepcin. Un ejemplo de esta limpieza
es llamar a Close en FileStream inmediatamente despus de su uso en lugar de esperar
que el objeto sea recolectado como elemento no utilizado por Common Language
Runtime, de la siguiente manera:
static void CodeWithoutCleanup()
{
System.IO.FileStream file = null;
System.IO.FileInfo fileInfo = new System.IO.FileInfo("C:\\file.txt");

83

file = fileInfo.OpenWrite();
file.WriteByte(0xF);
file.Close();
}
Ejemplo
Para convertir el cdigo anterior en una instruccin try-catch-finally, el cdigo de limpieza
est separado del cdigo activo como se muestra a continuacin.
static void CodeWithCleanup()
{
System.IO.FileStream file = null;
System.IO.FileInfo fileInfo = null;
try
{
fileInfo = new System.IO.FileInfo("C:\\file.txt");
file = fileInfo.OpenWrite();
file.WriteByte(0xF);
}
catch(System.Exception e)
{
System.Console.WriteLine(e.Message);
}
finally
{
if (file != null)
{
file.Close();
}
}
}
Como puede producirse una excepcin en cualquier momento dentro del bloque try antes
de la llamada a OpenWrite() o la propia llamada a OpenWrite() podra producir un error,
no se garantiza que el archivo est abierto cuando se intenta cerrarlo. El bloque finally
agrega una comprobacin para asegurarse de que el objeto FileStream no es null antes
de llamar al mtodo Close. Sin la comprobacin de null, el bloque finally podra iniciar su
propia excepcin NullReferenceException, pero se debera evitar producir excepciones en
bloques finally si es posible.
Una conexin de base de datos tambin es un elemento que debera cerrarse en un
bloque finally. Dado que el nmero de conexiones permitido a un servidor de base de
datos est limitado a veces, es importante cerrar las conexiones de base de datos tan
rpido como sea posible. Si se produce una excepcin antes de poder cerrar la conexin,
se trata de otro caso en el que es preferible utilizar el bloque finally a esperar a la
recoleccin de elementos no utilizados.

84

VIII Flujos y archivos


8.1 Definicin de Archivos de texto y archivos binarios
La clase base abstracta Stream es compatible con bytes de lectura y escritura. Stream
tiene compatibilidad asincrnica. Sus implementaciones predeterminadas definen lecturas
y escrituras asincrnicas segn sus correspondientes mtodos asincrnicos, y viceversa.
Todas las clases que representan secuencias se derivan de la clase Stream. La clase
Stream y sus clases derivadas proporcionan una visin genrica de los orgenes de datos
y los repositorios, aislando al programador de los detalles especficos del sistema
operativo y sus dispositivos subyacentes.
Las secuencias comprenden estas operaciones fundamentales:
En las secuencias se puede leer. La lectura es la transferencia de datos desde una

secuencia a una estructura de datos, como, por ejemplo, una matriz de bytes.

En las secuencias se puede escribir. La escritura consiste en la transferencia de

informacin desde un origen de datos a una secuencia.

Las secuencias pueden admitir operaciones de bsqueda. Las operaciones de

bsqueda consisten en la consulta y modificacin de la posicin actual en una


secuencia.

Segn el origen de datos o repositorio subyacente, las secuencias pueden admitir slo
algunas de estas caractersticas. Por ejemplo, NetworkStreams no admite operaciones de

85

bsqueda. Las propiedades CanRead, CanWrite y CanSeek de la clase Stream y sus


clases derivadas determinan qu operaciones admiten las diversas secuencias.
Clases usadas para E/S de archivos
FileObject proporciona una representacin de un archivo de texto. Para realizar las
operaciones ms comunes con archivos de texto, tales como leer, escribir, agregar,
copiar, suprimir, mover o cambiar el nombre, se debe usar la clase FileObject. Tambin se
puede usar esta clase para examinar y, en algunos casos, establecer atributos de archivo,
codificacin e informacin de ruta de acceso.
Directory proporciona mtodos estticos para crear, mover y enumerar archivos en
directorios y subdirectorios. La clase DirectoryInfo proporciona mtodos de instancia.
DirectoryInfo proporciona mtodos de instancia para crear, mover y enumerar archivos en
directorios y subdirectorios. La clase Directory proporciona mtodos estticos.
File proporciona mtodos estticos para crear, copiar, eliminar, mover y abrir archivos y
contribuye a la creacin de FileStream. La clase FileInfo proporciona mtodos de
instancia.
FileInfo proporciona mtodos de instancia para crear, copiar, eliminar, mover y abrir
archivos y contribuye a la creacin de FileStream. La clase File proporciona mtodos
estticos.
FileStream admite el acceso aleatorio a archivos mediante el mtodo Seek. FileStream
abre los archivos de forma sincrnica de manera predeterminada, pero tambin admite
operaciones asincrnicas. File contiene mtodos estticos y FileInfo contiene mtodos de
instancia.
FileSystemInfo es la clase base abstracta para FileInfo y DirectoryInfo.
Path proporciona mtodos y propiedades para procesar cadenas de directorio de una
plataforma a otra.
File, FileInfo, Path, Directory y DirectoryInfo son clases sealed (en Microsoft Visual Basic,
NotInheritable). Se pueden crear nuevas instancias de estas clases, pero no pueden tener
clases derivadas.
Clases usadas para leer y escribir en secuencias
BinaryReader y BinaryWriter leen y escriben cadenas codificadas y tipos de datos
primitivos desde y hacia las clases Stream.
StreamReader lee los caracteres de Streams, usando Encoding para convertir los
caracteres en bytes y a partir de bytes. StreamReader tiene un constructor que trata de
comprobar cul es el Encoding correcto para una Stream dada, basndose en la
presencia de un prembulo especfico de Encoding, por ejemplo una marca de orden de
bytes.
StreamWriter escribe los caracteres en Streams, usando Encoding para convertir los
caracteres en bytes.
StringReader lee los caracteres de Strings. StringReader permite tratar Strings con la
misma API, de modo que la salida puede ser una Stream en cualquier codificacin o una
String.

86

StringWriter escribe los caracteres en Strings. StringWriter permite tratar Strings con la
misma API, de modo que la salida puede ser una Stream en cualquier codificacin o una
String.
TextReader es la clase base abstracta para StreamReader y StringReader. Mientras que
las implementaciones de la clase abstracta Stream estn diseadas para la entrada y
salida de bytes, las de TextReader estn diseadas para la entrada de caracteres
Unicode.
TextWriter es la clase base abstracta para StreamWriter y StringWriter. Mientras que las
implementaciones de la clase abstracta Stream estn diseadas para la entrada y salida
de bytes, las de TextWriter estn diseadas para la salida de caracteres Unicode.
Clases comunes de secuencias de E/S
Una BufferedStream es una Stream que agrega almacenamiento en bfer a otra Stream,
por ejemplo una NetworkStream. FileStream tiene almacenamiento en bfer internamente,
y MemoryStream no necesita almacenamiento en bfer. BufferedStream se puede
componer en torno a ciertos tipos de secuencias para mejorar el rendimiento de lectura y
escritura. Un bfer es un bloque de bytes de la memoria utilizado para almacenar datos
en la memoria cach y, de este modo, reducir el nmero de llamadas al sistema operativo.
Una CryptoStream vincula secuencias de datos a transformaciones criptogrficas. Aunque
CryptoStream deriva de Stream, no forma parte del espacio de nombres System.IO, sino
del espacio de nombres System.Security.Cryptography.
Una MemoryStream es una secuencia no almacenada en bfer cuyos datos encapsulados
son accesibles directamente en la memoria. Esta secuencia no tiene almacn de respaldo
y puede resultar til como bfer temporal.
Una NetworkStream representa una Stream a travs de una conexin de red. Aunque
NetworkStream deriva de Stream, no forma parte del espacio de nombres System.IO, sino
de System.Net.Sockets.
E/S y seguridad
Cuando se utilizan las clases del espacio de nombres System.IO se deben satisfacer
requisitos de seguridad del sistema operativo como las listas de control de acceso (ACL)
para que se permita el acceso. Este requisito es adicional a cualquier requisito
FileIOPermission.
Precaucin La directiva de seguridad predeterminada de Internet y de una intranet no
permite el acceso a archivos. Por ello, no use las clases de E/S de almacenamiento no
aislado normales al escribir cdigo que se vaya a descargar a travs de Internet. En su
lugar, utilice Almacenamiento aislado.
Precaucin Al abrir un archivo o una secuencia de red, slo se realiza una comprobacin
de seguridad cuando se construye la secuencia. Por lo tanto, se debe tener cuidado al
entregar estas secuencias a dominios de aplicaciones o cdigo que no sean de confianza.
8.2 Operaciones bsicas en archivos texto y binario
8.2.1 Crear y 8.2.2 Abrir.
Los ejemplos de cdigo siguientes muestran cmo escribir texto en un archivo de texto.

87

El primer ejemplo muestra cmo agregar texto a un archivo existente. El segundo ejemplo
indica cmo crear un nuevo archivo de texto y escribir una cadena en l. Los mtodos
WriteAllText pueden proporcionar una funcionalidad parecida.
Ejemplo 1:
using System;
using System.IO;
class Test
{
public static void Main()
{
// Create an instance of StreamWriter to write text to a file.
// The using statement also closes the StreamWriter.
using (StreamWriter sw = new StreamWriter("TestFile.txt"))
{
// Add some text to the file.
sw.Write("This is the ");
sw.WriteLine("header for the file.");
sw.WriteLine("-------------------");
// Arbitrary objects can also be written to the file.
sw.Write("The date is: ");
sw.WriteLine(DateTime.Now);
}
}
}
Ejemplo 2:
using System;
using System.IO;
public class TextToFile
{
private const string FILE_NAME = "MyFile.txt";
public static void Main(String[] args)
{
if (File.Exists(FILE_NAME))
{
Console.WriteLine("{0} already exists.", FILE_NAME);
return;
}
using (StreamWriter sw = File.CreateText(FILE_NAME))
{
sw.WriteLine ("This is my file.");
sw.WriteLine ("I can write ints {0} or floats {1}, and so on.", 1, 4.2);
sw.Close();
}
}
}

88

8.2.4 Lectura y escritura, 8.2.5 Recorrer y 8.2.3 Cerrar


Texto
Los ejemplos de cdigo siguientes muestran cmo leer texto desde un archivo de texto. El
segundo ejemplo ofrece una notificacin cuando se detecta el fin del archivo. Esta
funcionalidad tambin se puede conseguir utilizando los mtodos ReadAllLines o
ReadAllText.
Ejemplo
using System;
using System.IO;
class Test
{
public static void Main()
{
try
{
// Create an instance of StreamReader to read from a file.
// The using statement also closes the StreamReader.
using (StreamReader sr = new StreamReader("TestFile.txt"))
{
String line;
// Read and display lines from the file until the end of
// the file is reached.
while ((line = sr.ReadLine()) != null)
{
Console.WriteLine(line);
}
}
}
catch (Exception e)
{
// Let the user know what went wrong.
Console.WriteLine("The file could not be read:");
Console.WriteLine(e.Message);
}
}
}
using System;
using System.IO;
public class TextFromFile
{
private const string FILE_NAME = "MyFile.txt";
public static void Main(String[] args)
{
if (!File.Exists(FILE_NAME))
{
Console.WriteLine("{0} does not exist.", FILE_NAME);
return;

89

}
using (StreamReader sr = File.OpenText(FILE_NAME))
{
String input;
while ((input=sr.ReadLine())!=null)
{
Console.WriteLine(input);
}
Console.WriteLine ("The end of the stream has been reached.");
sr.Close();
}
}
Binario
8.2.4 Lectura y escritura, 8.2.5 Recorrer y 8.2.3 Cerrar
Las clases BinaryWriter y BinaryReader se usan para escribir y leer datos, en lugar de
cadenas de caracteres. En el siguiente ejemplo de cdigo se muestra cmo se escriben y
se leen datos en una nueva secuencia de archivos vaca (Test.data). Despus de crear el
archivo de datos en el directorio actual, se crean las clases BinaryWriter y BinaryReader
asociadas, y BinaryWriter se usa para escribir los enteros de 0 a 10 en Test.data, que
deja el puntero de archivo al final del archivo. Despus de volver a establecer el puntero
de archivo en el origen, BinaryReader lee el contenido especificado.
using System;
using System.IO;
class MyStream
{
private const string FILE_NAME = "Test.data";
public static void Main(String[] args)
{
// Create the new, empty data file.
if (File.Exists(FILE_NAME))
{
Console.WriteLine("{0} already exists!", FILE_NAME);
return;
}
FileStream fs = new FileStream(FILE_NAME, FileMode.CreateNew);
// Create the writer for data.
BinaryWriter w = new BinaryWriter(fs);
// Write data to Test.data.
for (int i = 0; i < 11; i++)
{
w.Write( (int) i);
}
w.Close();
fs.Close();
// Create the reader for data.
fs = new FileStream(FILE_NAME, FileMode.Open, FileAccess.Read);
BinaryReader r = new BinaryReader(fs);
// Read data from Test.data.

90

for (int i = 0; i < 11; i++)


{
Console.WriteLine(r.ReadInt32());
}
w.Close();
}
}
Test.data ya existe en el directorio actual, se inicia una IOException. Use FileMode.Create
para crear siempre un archivo nuevo sin iniciar una IOException.

Bibliografa
Titulo: C# Manual de programacin
Autor: Joyanes Fernandez
Editorial: McGraw Hill
Titulo: Microsoft C# Lenguaje y aplicaciones
Autor: Francisco Javier Ceballos
Editorial: Ra-ma
WWW.MICROSOFT.COM.MX

91