Professional Documents
Culture Documents
1
EJEMPLO................................................................................................................56
BLOQUE TRY..........................................................................................................80
BLOQUE CATCH....................................................................................................80
BLOQUE FINALLY.................................................................................................82
E/S y seguridad.................................................................................................................................................87
Un arreglo (matriz, vector, lista) es un tipo especial de objeto compuesto por una
colección 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 subíndices.
2
Figura 1.1. El arreglo lista de 10 elementos, con índices de 0 a 8.
Los arreglos pueden ser unidimensionales (Figuras 1.1 y 1.2) conocidos también como
listas o vectores y multidimensionales conocidos también como tablas o matrices, que
pueden tener dos o más dimensiones.
Ejemplo
temperaturas [0]
temperaturas [1]
temperaturas [2]
temperaturas [3]
temperaturas [4]
temperaturas [5]
temperaturas [6]
temperaturas [7]
Conceptos basicos
El proceso que se puede realizar con arreglos abarca las siguientes operaciones:
declaración, creación, inicialización y utilización. Las operaciones de declaración, creación
e inicialización son necesarias para poder utilizar un arreglo.
Declaración
tipoDato[ ] nombreArreglo
Ejemplo
3
double[ ] miLista; Se declara un arreglo miLista de tipo double.
float[ ] temperatura; Se declara un arreglo temperatura de tipo float
Creación
Ejemplo
Ejemplo
Inicialización y utilización
nombreArreglo[índice];
4
Ejemplo
Regla: En C#, un índice del arreglo es siempre un entero que comienza en cero y termina
en tamaño-1.
esta sentencia crea el arreglo miLista que consta de cinco elementos. También podrían
haberse utilizado
El tamaño de un arreglo se obtiene con la propiedad Length. Así, por ejemplo, si se crea
un arreglo miLista, la sentencia miLista.Length devuelve el tamaño del arreglo miLista
(10, por ejemplo).
Las variables que representan elementos de un arreglo se utilizan de igual forma que
cualquier otra variable. Por ejemplo:
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 razón, se procesan de igual forma y repetidamente utilizando
un bucle.
5
• El tamaño 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
instrucción foreach.
Ejemplos
1. El bucle for siguiente introduce valores en los elementos del arreglo. El tamaño del arreglo
se obtiene en miLista.Length.
for (int i = i; i < miLista.Length; i++)
miLista[i] = i;
COPIA DE ARREGLOS
Con frecuencia se necesita duplicar un arreglo o bien una parte de un arreglo. Existen dos
métodos para copiar arreglos: copiar elementos individuales utilizando un bucle y utilizar
el método Arreglo.Copy.
Método 1
Un método para copiar arreglos es escribir un bucle que copia cada elemento desde el
arreglo origen al elemento correspondiente del arreglo destino.
Ejemplo
Nota: Crear un proyecto, poner un botón en la forma, hacer doble clic en el botón,
declarar e inicializar los arreglos, escribir el siguiente código.
Método 2
Otra forma de copiar arreglos es usar el método Arreglo.Copy que tiene los siguientes
formatos:
6
public static void Copy (Array arregloOrigen, int pos_ini, Array arregloDestino, int pos_fin,
int longitud)
Ejemplo
Los arreglos pueden ser pasados como parámetros por valor y también como parámetros
por variable, mediante el uso de los modificadores ref o out. Cuando un arreglo se pasa a
un método como parámetro por valor es posible cambiar los valores de los elementos del
arreglo en el interior del método 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 efectúa sólo es una copia de la referencia que señalaba al
arreglo.
Ejemplo
7
public void Principal( )
{
int[ ] arr = {1,4,5};
Cambiar(arr);
for (int i = 0; i < arr.Length; i++)
MessageBox.Show(“{0} “+arr[i]);
}
}
Cuando se pasa un arreglo por referencia a un método, no sólo los cambios en los valores
de los elementos del arreglo, sino todos los cambios que tengan lugar dentro del método
afectan la matriz original.
Ejemplo
class ArrComoParametros2
{
static void Añadir (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];
}
En C# los arreglos pueden tener más de una dimensión. Así, las tablas o matrices se
representan mediante arreglos bidimensionales. Un arreglo bidimensional se declara:
tipo [ , ] nombre;
8
y se crea o instancia mediante:
0 1 2 3 4 5
0 1 2 3
0
1
2
3
(b) Arreglo bidimensional
La Figura 1.4 ilustra un arreglo de doble subíndice, 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.
9
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 subíndices que identifican unívocamente la fila y la
columna de cada elemento de a; obsérvese que todos los elementos de la primera fila
comienzan por un primer subíndice de 0 y los de la columna cuarta tienen un segundo
subíndice de 3 (4 -1).
La inicialización puede efectuarse en el momento de la declaración especificando los
valores iniciales entre { }.
Ejemplos
b [0, 0] b [0,1]
(5) (6)
b [1, 0] b [1,1]
(7) (8)
y también con
o con
2. La declaración
crea un arreglo de dos filas y tres columnas, en el que la primera fila contiene 4 , 5, 6 .
Un medio rápido para asignar valores a los elementos de un arreglo de dos dimensiones
es utilizar bucles for
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 continuación arranca
dicho bucle interior, bucle y, fijando y a 0. Todo esto lleva al programa a la línea que
10
inicializa el elemento del arreglo tabla[0, 0] al valor 5. A continuación 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 habrán tomado el valor 5.
Ejemplo
0 1 2 3
0 1 2 3
4 5 6 7
8 9 10 11
12 13 14 15
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;
Análogamente, también 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 información almacenada en él, también se efectúa
utilizando bucles for anidados, ya que los bucles facilitan la manipulación de cada uno de
los elementos de un arreglo.
Ejercicio
11
static int[ , ] tabla;
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();
}
12
Text = "2ª aplicación Windows";
Size = new Size(400, 400);
CenterToScreen();
}
}
}
//Instanciación
nombre = new tipo[valor1, valor2, valor3];
13
Ejemplo
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 número diferente de columnas y
se pueden asignar dinámicamente, como sigue:
int[ ] [ ] b;
b = new int[3] [ ]; // asigna filas
b[0] = new int[5]; // asigna 5 columnas a la fila 0
b[1] = new int[4]; // asigna 4 columnas a la fila 1
b[2] = new int[3]; // asigna 3 columnas a la fila 2
MessageBox.Show(b.Length);
MessageBox.Show(b[1].Length);
//Resultado: 3 //Resultado: 4
Ejemplo
Se puede utilizar también una notación abreviada para declarar e inicializar un arreglo de
arreglos:
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}
};
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
int[ ] [ ] a;
a = new int[3] [ ]; // asigna filas
a[0] = new int[5];' // asigna 5 columnas a la fila 0
a[1] = new int[7]; // asigna 7 columnas a la fila 1
a[2] = new int[3]; // asigna 3 columnas a la fila 2
for (int i = 0; i < a.Length; i++)
for (int j = 0; j < a[i] .Length; j++)
a[i][j]=j+1;
15
II Métodos y Mensajes
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 sólo
lectura o variables), declaraciones de tipos anidadas, eventos, indexadores, operadores,
constructores, destructores, propiedades y métodos; y, habitualmente, una clase de un
objeto contiene una colección de métodos y definiciones de datos. Si se diseña 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 creación de una variable específica de un tipo
particular de clase se conoce como instanciación (creación de instancias) de esa clase.
Una clase describe la constitución de un objeto y sirve como plantilla para construir
objetos, especificando la interfaz pública 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:
16
El siguiente ejemplo representa una clase Circulo que se utilizará para construir objetos
del tipo Círculo:
class Circulo
{
public double radio =5.0;
public double CaleularSuperficie()
{
return radio*radio*3.141592;
}
}
Esta clase Circulo es, simplemente, una definición que se utiliza para declarar y crear
objetos Circulo.
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 colección de datos y una serie de rutinas miembros, entre las que
destacan los métodos. Los objetos representan cosas físicas o abstractas, pero que
tienen un estado y un comportamiento. Por ejemplo, una mesa, un estudiante, un círculo,
una cuenta corriente, un préstamo, un automóvil,... 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 efectúen, se
define como métodos.
NombreClase NombreObjeto;
Circulo myCirculo;
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 métodos, propiedades, o indexadores se denominan variables locales. Las variables
locales sólo existirán y se podrá hacer referencia a ellas dentro del cuerpo del método,
propiedad o indexador donde han sido declaradas. Además, en C# es posible agrupar
sentencias simples, encerrándolas 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 podrán 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 continuación comienza un bloque
{
int aux = i;
i= (int) (j) ; //es necesaria una conversión explícita
j= aux; //se efectúa una conversión implícita
MessageBox.Show(i+" "+j+" "+aux);
}
//Fin del bloque
MessageBox.Show(i+" "+j); // aux aquí no está definida
CONSTANTES
Las constantes son datos cuyo valor se establece en tiempo de compilación y no puede
variar durante la ejecución de un programa. En un programa pueden aparecer constantes
de dos tipos: literales y simbólicas. Las constantes simbólicas o con nombre representan
datos permanentes que nunca cambian y se declaran como las variables, pero
inicializándose en el momento de la declaración y comenzando dicha declaración 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, sólo se le podrá asignar el valor null.
Ejemplos
2. class Prueba
//Constructores
public ConstSLect()
{
// se establece su valor en el constructor
z = 6;
}
19
}
Constantes enteras
Las constantes enteras representan números enteros con y sin signo. La escritura de
constantes enteras en un programa debe seguir unas determinadas reglas:
Constantes reales
Una constante flotante representa un número 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 también pueden ir seguidas
por una da D que especifique su pertenencia a dicho tipo.
Cuando se les añade 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;
Constantes lógicas
Las constantes literales de tipo lógico disponibles en C# son true (verdadero) y false
(falso).
Una constante de tipo carácter es un carácter Unicode válido encerrado entre comillas
simples. Los caracteres que pueden considerarse válidos son:
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
21
Los métodos son los miembros de un tipo clase donde se especifican las acciones que se
realizan por un objeto. Una invocación a un método es una petición al método para que
ejecute su acción y lo haga con el objeto mencionado. La invocación de un método se
denominaría también llamar a un método y pasar un mensaje a un objeto.
Nota: Existen dos tipos de métodos: aquellos que devuelven un único valor y aquellos
que realizan alguna acción distinta de devolver un valor. Los métodos que realizan alguna
acción distinta de devolver un valor se denominan métodos void.
Cantidad = myCuentaCorriente.ObtenerSaldo();
2. Si el método devuelve void, una llamada al método debe ser una sentencia. Por
ejemplo.
myCuentaCorriente.Depositar(5000)
22
2.5 Tipos de métodos.
Métodos Static:
Utilice el modificador static para declarar un miembro estático, que pertenece al propio
tipo en vez de a un objeto específico. El modificador static puede utilizarse con clases,
campos, métodos, 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 métodos static:
static class CompanyEmployee
{
public static string GetCompanyName(string name) { ... }
public static string GetCompanyAddress(string address) { ... }
}
Comentario
• Una declaración de constante o tipo constituye, implícitamente, un miembro
estático.
Para referirse al miembro estático 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, sólo existe una copia de cada campo
estático.
• Si la palabra clave static se aplica a una clase, todos los miembros de la clase
deben ser estáticos.
• Las clases, incluidas las clases estáticas, pueden tener constructores estáticos. Se
llama a los constructores estáticos en algún momento comprendido entre el inicio
del programa y la creación de instancias de la clase.
Para comprender el uso de miembros estáticos, considere una clase que representa al
empleado de una compañía. Suponga que la clase contiene un método que cuenta
empleados y un campo que almacena el número de empleados. Ni el método ni el campo
pertenecen a ninguna instancia de empleado. En vez de ello, pertenecen a la clase
compañía. Por tanto, se deberían declarar como miembros estáticos 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 información del nuevo empleado, así como el nuevo
número de empleados. Por motivos de simplicidad, el programa lee el número actual de
empleados desde el teclado. En una aplicación real, esta información se leería desde un
archivo.
En el siguiente ejemplo vamos a usar el modo consola solo para uso de explicación en
clase.
// cs_static_keyword.cs
using System;
public class Employee
{
public string id;
public string name;
public Employee() { }
24
}
Entrada
Tara Strahan
AF643G
15
25
• Pasar un objeto como parámetro a otros métodos, por ejemplo, para:
CalcTax(this);
Debido a que las funciones miembro estáticas 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 método
estático.
Ejemplo
En este ejemplo, this se utiliza para calificar los miembros de la clase Employee, name y
alias, que presentan ambigüedad con nombres similares. También se utiliza para pasar un
objeto al método 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));
}
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
27
argumentos (parámetros) reales o simplemente argumentos. La llamada a un método
exige proporcionarle parámetros reales (actuales) que se deben dar en el mismo orden
que la lista de parámetros formales en la especificación del método. Esta regla se conoce
como asociación del orden de los parámetros. C# soporta llamadas a un método con un
número variable de argumentos cuando el parámetro formal correspondiente es de tipo
array, va precedido por el modificador params y es el último especificado en la cabecera
del método.
Ejemplos
a. Invocación correcta
b. Invocación incorrecta
class Prueba
{
// conversión implícita
static void ImprimirNX(int n, params ushort[ ] x)
28
}
Salida
HoLAHoLAHOLA
A DIO S
En C#, los parámetros pueden ser pasados por valor o por referencia. Cuando un
parámetro se pasa por valor, sus valores se copian en nuevas posiciones, que se pasan a
la subrutina; como consecuencia de esto, si el parámetro cambia su valor dentro del
método, 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 parámetros 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 parámetro formal, los datos se copian y su cambio en el interior del método 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.
Ejemplo
29
En el siguiente programa objetos de la clase Quebrado se pasan como argumentos a los
métodos Simplificar, Intercambiar y Mostrar. El programa consta de dos partes:
using System;
class Quebrado
{
int num, den;
// Constructor
public Quebrado(int x, int y)
{
num = x;
den = y;
}
while (b > 0)
{
int r = a % b;
a = b;
b = r;
}
/* Al terminar el bucle el máximo común divisor se encuentra
almacenado en a.
Para simplificar el quebrado se divide numerador y denominador
por su máximo común 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;
}
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 parámetro se reflejen en su variable asociada. La diferencia
entre ambos está en que un parámetro ref necesita que el parámetro actual
correspondiente sea previamente inicializado, mientras que esto no es necesario cuando
se trata de un parámetro out.
Ejemplo
El programa siguiente ordena 3 números leídos desde teclado, utilizando dos métodos
auxiliares: Leer3 e Intercambiar. El método Leer3 devuelve al método Main los 3 números
introducidos desde teclado y permite mostrar el funcionamiento del modificador out. El
31
método 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 efectúa su lanzamiento (throw e), para que en Main
vuelvan a ser consideradas. En Main la excepción se captura de nuevo y no se establece
ningún tratamiento para la misma, pero así, si se produce una excepción 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;
}
32
Intercambiar(ref a, ref b);
Console.WriteLine("Ordenados: {0} {l} {2}",a,b ,e);
}
catch { }
}
}
Los métodos pueden devolver un valor al llamador. Si el tipo de valor devuelto (el que
aparece antes del nombre de método) no es void, el método puede devolver el valor
mediante la palabra clave return. Una instrucción con la palabra clave return, seguida de
un valor que coincida con el tipo de valor devuelto, devolverá ese valor al llamador del
método. La palabra clave return también detiene la ejecución del método. Si el tipo de
valor devuelto es void, una instrucción return sin ningún valor sigue siendo útil para
detener la ejecución del método. Sin la palabra clave return, el método detendrá la
ejecución cuando llegue al fin del bloque de código. Es necesario que los métodos con un
tipo de valor devuelto no nulo utilicen la palabra clave return para devolver un valor. Por
ejemplo, estos dos métodos utilizan la palabra clave return para devolver enteros:
class SimpleMath
{
public int AddTwoNumbers(int number1, int number2)
{
return number1 + number2;
}
Para emplear un valor devuelto por un método, el método de llamada puede utilizar la
propia llamada del método en cualquier parte donde un valor del mismo tipo sea
suficiente. El valor devuelto también se puede asignar a una variable. Por ejemplo, los dos
ejemplos de código siguientes logran el mismo objetivo:
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 código puede ser útil o puede ser necesaria si el valor se va a
utilizar más de una vez.
Una clase puede tener varios constructores que toman argumentos diferentes. Los
constructores permiten al programador establecer valores predeterminados, limitar la
creación de instancias y escribir código flexible y fácil 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 0
char '\0'
decimal 0,0M
double 0,0D
enum El valor producido por la expresión (E)0, donde E es el identificador de la
enumeración.
float 0,0F
int 0
long 0L
sbyte 0
short 0
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 0
ulong 0
ushort 0
Los constructores son métodos 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 después de asignar la memoria al nuevo objeto.
35
class TestTaxi
{
static void Main()
{
Taxi t = new Taxi();
System.Console.WriteLine(t.isInitialized);
}
}
Destructores
class Car
{
~ Car() // destructor
{
// cleanup statements...
}
}
El destructor llama implícitamente al método Finalize en la case base del objeto. Por lo
tanto, el código de destructor anterior se traduce implícitamente a:
36
Esto significa que se llama al método Finalize de forma recursiva para todas las instancias
de la cadena de herencia, desde la más derivada hasta la menos derivada.
El programador no puede controlar cuándo 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 están siendo utilizados por ninguna
aplicación. Si considera un objeto elegible para su destrucción, llama al destructor (si
existe) y reclama la memoria utilizada para almacenar el objeto. También se llama a los
destructores cuando se cierra el programa.
Es posible forzar la recolección de elementos no utilizados llamando al método Collect,
pero en la mayoría de los casos debe evitarse su uso por razones de rendimiento.
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 más derivada. Cuando
ejecute el programa, observe que se llama a los destructores de las tres clases
automáticamente y en orden, desde la más derivada hasta la menos derivada.
class First
{
~First()
{
System.Console.WriteLine("First's destructor is called");
}
}
37
~Third()
{
System.Console.WriteLine("Third's destructor is called");
}
}
class TestDestructors
{
static void Main()
{
Third t = new Third();
}
}
Constructores de instancias
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 ningún 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:
Esto permite crear objetos CoOrd con valores iniciales concretos o predeterminados, del
modo siguiente:
38
Si una clase no tiene un constructor predeterminado, se genera uno automáticamente 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 sólo miembros estáticos. Si una clase tiene uno o
más constructores privados y ningún constructor público, 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 declaración de un constructor vacío evita la generación automática 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 explícita para aclarar que no es posible crear una instancia
de la clase.
Los constructores privados se utilizan para evitar la creación de instancias de una clase
cuando no hay campos o métodos de instancia, por ejemplo la clase Math, o cuando se
llama a un método para obtener una instancia de una clase. Si todos los métodos de la
clase son estáticos, considere convertir la clase completa en estática.
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 estáticos
Un constructor estático se utiliza para inicializar cualquier dato estático o realizar una
acción determinada que sólo debe realizarse una vez. Es llamado automáticamente antes
de crear la primera instancia o de hacer referencia a cualquier miembro estático.
class SimpleClass
{
// Static constructor
static SimpleClass()
{
//...
}
}
Los constructores estáticos tienen las propiedades siguientes:
Un constructor estático no permite modificadores de acceso ni tiene parámetros.
Se le llama automáticamente para inicializar la clase antes de crear la primera instancia o
de hacer referencia a cualquier miembro estático.
El constructor estático no puede ser llamado directamente.
El usuario no puede controlar cuando se ejecuta el constructor estático en el programa.
Los constructores estáticos se utilizan normalmente cuando la clase hace uso de un
archivo de registro y el constructor escribe entradas en dicho archivo.
Los constructores estáticos también son útiles al crear clases contenedoras para código
no administrado, cuando el constructor puede llamar al método LoadLibrary.
Ejemplo
En este ejemplo, la clase Bus tiene un constructor estático y un miembro estático, Drive().
Cuando se llama a Drive(), se invoca el constructor estático para inicializar la clase.
class TestBus
{
static void Main()
{
Bus.Drive();
}
}
40
IV Sobrecarga
La conversión entre tipos de datos se puede hacer de forma explícita utilizando una
conversión de tipos; en algunos casos, se permiten conversiones implícitas. Por ejemplo:
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)
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 método de conversión para convertir todos y cada uno de los tipos base en los
demás tipos base. Sin embargo, la operación de conversión real efectuada queda incluida
en tres categorías:
• La conversión de un tipo en sí mismo devuelve dicho tipo. No se lleva a cabo
realmente ninguna conversión.
• La conversión que no puede producir un resultado significativo produce una
excepción InvalidCastException. No se lleva a cabo realmente ninguna
conversión. Las conversiones de Char en Boolean, Single, Double, Decimal o
DateTime, y de estos tipos en Char producen una excepción. Las conversiones
de DateTime en cualquier tipo excepto String, y de cualquier tipo excepto String
en DateTime producen una excepción.
• Los tipos base no descritos anteriormente pueden ser objeto de conversiones a
y desde cualquier otro tipo base.
42
Algunos de los métodos de esta clase toman un objeto de parámetro que implementa la
interfaz IFormatProvider. Este parámetro puede proporcionar información de formato
específica de la referencia cultural para ayudar en el proceso de conversión. Los tipos de
valor base pasan por alto este parámetro, pero los tipos definidos por el usuario que
implementan IConvertible pueden tenerlo en cuenta.
Para obtener más información sobre los tipos de valor base, vea el tema correspondiente
que aparece en la sección Vea también.
Ejemplo
En el siguiente ejemplo de código, se muestran algunos de los métodos de conversión de
la clase Convert, entre los que se incluyen ToInt32, ToBoolean y ToString.
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.");
}
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.");
}
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;
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:
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.
UInt16: 120
UInt32: 121
UInt64: 122
SByte: 123
*/
• La sobrecarga de los métodos permite que una clase, estructura o interfaz declare
varios métodos con el mismo nombre, siempre que sus firmas sean únicas dentro de
esa clase, estructura o interfaz.
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.
Se debe tener en cuenta que los modificadores de parámetro 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 basándose exclusivamente en el tipo de valor devuelto o en la inclusión o
exclusión del modificador params. Como tales, las declaraciones de los métodos F(int) y
F(params string[]) anteriormente identificadas producen un error en tiempo de
compilación.
Sobrecarga de operadores
Todos los operadores unarios y binarios tienen implementaciones predefinidas que están
disponibles automáticamente en cualquier expresión. Además 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: sólo se consideran las implementaciones de operador predefinidas
cuando no existen implementaciones de operador definidas por el usuario que puedan
aplicarse.
+ - ! ~ ++ -- true false
46
Aunque true y false no se utilizan explícitamente en las expresiones, se consideran
operadores porque se invocan en varios contextos de expresión: expresiones de tipo
Boolean, expresiones con condicionales y operadores lógicos condicionales.
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 parámetros sea del tipo de la clase o estructura que contiene la
declaración del operador. Por lo tanto, no es posible que un operador definido por el
usuario tenga la misma firma que un operador predefinido.
47
Aunque es posible que un operador definido por el usuario realice cualquier cálculo que le
interese, no se recomiendan las implementaciones que generan resultados distintos de
los que intuitivamente pueden esperarse. Por ejemplo, una implementación de operator
== debe comparar la igualdad de los dos operandos y devolver un resultado bool
apropiado.
Ejemplo 1
Este ejemplo muestra cómo utilizar la sobrecarga de operadores para crear una clase de
números complejos Complex que define la suma compleja. El programa muestra las
partes real e imaginaria de los números y el resultado de la suma mediante un método
sustituto del método ToString.
// complex.cs
using System;
// 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));
}
48
Complex num2 = new Complex(3,4);
// 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 cómo utilizar la sobrecarga de operadores para implementar un tipo
lógico 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í sólo 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;
49
public static implicit operator DBBool(bool x)
{
return x? dbTrue: dbFalse;
}
50
// Definitely true operator. Returns true if the operand is
// dbTrue, false otherwise:
public static bool operator true(DBBool x)
{
return x.value > 0;
}
51
case 1:
return "DBBool.True";
default:
throw new InvalidOperationException();
}
}
}
class Test
{
static void Main()
{
DBBool a, b;
a = DBBool.dbTrue;
b = DBBool.dbNull;
• Defina los operadores en tipos de valor que sean tipos de lenguajes lógicos integrados,
como la Estructura System.Decimal.
52
obtener un TimeSpan. No obstante, no es adecuado utilizar el operador or para crear la
unión de dos consultas a la base de datos, o utilizar shift para escribir una secuencia.
V Herencia
Las clases pueden heredar de otra clase. Para conseguir esto, se coloca un signo de dos
puntos después del nombre de la clase al declarar la clase y se denomina la clase de la
cual se hereda (la clase base) después 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, además de todos los demás 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 añade nuevos miembros específicos. En esencia, una subclase hereda
las variables y métodos de su clase base y de todos sus ascendientes. La subclase puede
utilizar estos miembros, puede ocultar las variables miembro o anular (redefinir) los
métodos. 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 métodos de la misma
(aunque estén redefinidos). Ninguna de estas palabras reservadas pueden utilizarse
desde métodos estáticos.
En C# sólo se permite la herencia simple. En otras palabras, una clase puede heredar la
implementación de una sola clase base. Sin embargo, una clase puede implementar más
de una interfaz. La siguiente tabla muestra ejemplos de herencia de clases e
implementación de interfaces:
Herencia Ejemplo
• Constructores
• Destructores
• Constantes
• Campos
• Métodos
• Propiedades
• Indizadores
• Operadores
• Eventos
• Delegados
• Clases
• Interfaces
54
• Estructuras
Ejemplo
En el siguiente ejemplo se muestra la declaración de campos de clase, constructores y
métodos. También muestra la creación de instancias de objetos y la impresión de datos
de instancia. En este ejemplo, se declaran dos clases, la clase Kid, que contiene dos
campos privados (name y age) y dos métodos públicos. 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();
}
}
55
5.4 Clase base y clase derivada
Ejemplo
En el ejemplo siguiente, se define una clase pública que contiene un campo único, un
método y un método especial denominado constructor. Luego se crean instancias de la
clase con la palabra clave new.
// 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);
}
}
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;
}
}
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;
}
Resultados
57
x = 10, y = 15
Comentarios
Cuando se invoca un método virtual, el tipo en tiempo de ejecución 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 derivación, el cual puede ser el
miembro original, si no existe ninguna clase derivada que haya reemplazado el miembro.
De forma predeterminada, los métodos son no virtuales. No se puede reemplazar un
método no virtual.
No puede utilizar el modificador virtual con los modificadores static, abstract y override.
Las propiedades virtuales funcionan como los métodos 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 estática.
• Una propiedad virtual heredada se puede reemplazar en una clase derivada si
se incluye una declaración de propiedad que use el modificador override.
Ejemplo
En este ejemplo, la clase Dimensions contiene las dos coordenadas x, y, y el método
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 implementación de Area() (método de
reemplazo). El programa calcula y muestra el área apropiada para cada implementación
del método Area() según el objeto asociado al método.
Observe que todas las clases heredadas Circle, Sphere y Cylinder utilizan constructores
que inicializan la clase base, por ejemplo:
// cs_virtual_keyword.cs
using System;
class TestClass {
58
public override double Area() { return PI * x * x; }
}
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 implementación
abstracta o virtual de un método, propiedad, indizador o evento heredado.
En este ejemplo, la clase Square debe proporcionar una implementación de reemplazo de
Area porque ésta se hereda de la clase abstracta ShapesClass:
59
Comentarios
El método override proporciona una nueva implementación de un miembro heredado de
una clase base. El método reemplazado por una declaración override se conoce como
método base reemplazado. El método base reemplazado debe tener la misma firma que
el método override. No se puede reemplazar un método estático o no virtual. El método
base reemplazado debe ser virtual, abstract u override.
Una declaración override no puede cambiar la accesibilidad del método virtual. El método
override y el método virtual deben tener el mismo modificador de nivel de acceso.
No se pueden utilizar los modificadores new, static, virtual o abstract para modificar un
método override.
Una declaración 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 método 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; }
}
60
public override decimal CalculatePay()
{
return basepay + salesbonus;
}
}
Resultados
Employee Alice earned: 1500 Employee Bob earned: 1200
61
El ámbito de los parámetros dados en la lista-de-parámetros-formales de una declaración
de constructor de instancia incluye el inicializador de constructor de dicha declaración. Por
lo tanto, un inicializador de constructor puede tener acceso a los parámetros 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) {}
}
Ejecución de constructor
Los inicializadores de variables se transforman en instrucciones de asignación, que se
ejecutan antes de la invocación 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 instrucción 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 asignación 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 automáticamente antes del cuerpo-del-
constructor. 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;
}
}
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; // Variable initializer
object(); // Invoke object() constructor
count = n;
}
}
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 declaración-de-destructor
(destructor-declaration):
destructor-declaration:
attributesopt externopt ~ identifier ( ) destructor-body
destructor-body:
block
;
Una declaración-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
compilación.
Cuando una declaración de destructor incluye un modificador extern, se dice que es un
destructor externo. Debido a que la declaración de destructor externo no proporciona una
implementación 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 cuerpo-
de-destructor corresponde exactamente al cuerpo-de-un-método de un método de
instancia con un tipo de valor devuelto void.
Los destructores no se heredan. Por lo tanto, una clase sólo tiene el destructor que se
puede declarar en la propia clase.
Como un destructor no puede tener parámetros, no puede ser sobrecargado; por lo tanto,
una clase sólo puede tener como máximo un destructor.
Los destructores se invocan automáticamente y no se pueden invocar explícitamente. Una
instancia se convierte en candidata para destrucción cuando ya ninguna parte de código
64
puede utilizarla. La ejecución del destructor de la instancia puede ocurrir en cualquier
momento una vez que la instancia se convierta en candidata para destrucción. Cuando se
destruye una instancia, se llama a los destructores de su cadena de herencia en orden, de
la más derivada a la menos derivada. Un destructor puede ejecutarse en cualquier
subproceso. Para leer una explicación más detallada de las reglas que controlan cómo y
cuándo 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
class A
{
override protected void Finalize() {} // error
public void F() {
this.Finalize(); // error
}
}
65
class A
{
void Finalize() {} // permitted
}
VI Polimorfismo y reutilización
66
Las clases abstractas están estrechamente relacionadas con las interfaces. Son clases de
las que no es posible crear instancias; frecuentemente, están implementadas sólo
parcialmente o no están implementadas. Una de las principales diferencias entre las
clases abstractas y las interfaces es que una clase puede implementar un número
ilimitado de interfaces, pero sólo 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 métodos y aplazar
la implementación de otros hasta que se necesite una implementación específica de la
clase. También 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 código.
67
}
Cuando se implementa una clase abstracta, debe implementarse cada método abstracto
(MustOverride) de esta clase; cada método implementado debe recibir el mismo número y
tipo de argumentos y devolver el mismo valor que el método especificado en la clase
abstracta.
6.3 Definición de una interfaz y 6.4 Implementación de la definición de una interfaz.
interface IComparable
{
int CompareTo(object obj);
}
68
Para implementar un miembro de interfaz, el miembro correspondiente de la clase debe
ser público, no estático 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 implementación explícita,
los descriptores de acceso deben coincidir.
Las interfaces y los miembros de interfaz son abstractos; las interfaces no proporcionan
una implementación 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 cómo se
implementa.
Las interfaces pueden heredar otras interfaces. Es posible que una clase herede una
interfaz varias veces, a través de las clases base o interfaces que hereda. En ese caso, la
clase sólo 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 implementación. Es posible que una
clase base implemente miembros de interfaz a través de miembros virtuales. En ese caso,
la clase que hereda la interfaz puede cambiar el comportamiento de la interfaz
reemplazando los miembros virtuales.
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 función, esto puede
llevar a una implementación incorrecta de una o ambas interfaces. Es posible implementar
un miembro de interfaz explícitamente, creando un miembro de clase que sólo se llama a
través de la interfaz y es específico de ésta. Esto se puede llevar a cabo asignando al
miembro de clase el nombre de la interfaz y un punto. Por ejemplo:
IControl c = (IControl)obj;
c.Paint(); // Calls IControl.Paint on SampleClass.
ISurface s = (ISurface)obj;
s.Paint(); // Calls ISurface.Paint on SampleClass.
La implementación explícita también se usa para resolver casos donde cada una de las
dos interfaces declara miembros diferentes del mismo nombre como propiedad y método:
interface ILeft
{
int P { get;}
}
interface IRight
{
70
int P();
}
Para implementar ambas interfaces, una clase tiene que utilizar implementación explícita,
ya sea para la propiedad P, el método P o ambos, con el fin de evitar un error del
compilador. Por ejemplo:
Propiedades de interfaces
A continuación se muestra un ejemplo de descriptor de acceso de un indizador de
interfaz:
Ejemplo
En este ejemplo, la interfaz IEmployee tiene una propiedad de lectura y escritura, Name, y
una propiedad de sólo 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 número actual de empleados, y muestra como resultado el nombre del
empleado y el nuevo número de empleados calculado.
Se podría 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 { }
}
71
string IEmployee.Name
{
get { return "Employee Name"; }
set { }
}
string ICitizen.Name
{
get { return "Citizen Name"; }
set { }
}
interface IEmployee
{
string Name
{
get;
set;
}
int Counter
{
get;
}
}
72
{
return counter;
}
}
class TestEmployee
{
static void Main()
{
System.Console.Write("Enter number of employees: ");
Employee.numberOfEmployees = int.Parse(System.Console.ReadLine());
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 propósito del descriptor de acceso es indicar si el indizador es de lectura y
escritura, de sólo lectura o de sólo escritura.
A continuación se muestra un ejemplo de descriptor de acceso de un indizador de
interfaz:
// Indexer declaration:
string this[int index]
{
get;
set;
}
}
73
La firma de un indizador debe ser diferente de las firmas de los demás indizadores
declarados en la misma interfaz.
Ejemplo
En el ejemplo siguiente se muestra cómo se implementan indizadores de interfaz:
// Indexer on an interface:
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]);
}
}
}
75
VII Excepciones
7.1 Definición
7.1.1 Que son las excepciones
Las características de control de excepciones del lenguaje C# proporcionan una manera
de afrontar cualquier situación 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 podrían no realizarse correctamente, controlar errores y
limpiar los recursos después. Common Language Runtime (CLR), las bibliotecas de otro
fabricante o el código de aplicación que utiliza la palabra clave throw pueden generar
excepciones.
En este ejemplo, el método hace una prueba para realizar una división por cero y detecta
el error. Sin el control de excepciones, este programa finalizaría con un error
DivideByZeroException no controlado.
76
{
System.Console.WriteLine("Division by zero attempted!"); return 0;
}
}
Excepción Descripción
77
fuera de los límites de la matriz.
InvalidCastException Se produce cuando tiene lugar un error en tiempo de
ejecución en una conversión explícita 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 operación aritmética 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 método
pendientes; normalmente, suele indicar un nivel de
recursividad muy profundo o infinito.
TypeInitializationException Se produce cuando un constructor estático produce una
excepción sin que haya cláusulas catch compatibles para
capturarla.
7.1.3 Propagación
La instrucción throw (throw-statement) inicia una excepción.
throw-statement:
throw expressionopt ;
Una instrucción throw con expresión inicia el valor resultante de evaluar la expresión. La
expresión debe denotar un valor del tipo de clase System.Exception o de un tipo de clase
que derive de System.Exception. Si la expresión evaluada devuelve null, se inicia
entonces System.NullReferenceException.
Una instrucción throw sin expresión sólo se puede utilizar en un bloque catch, en cuyo
caso esta instrucción volverá a iniciar la excepción que esté controlando en ese momento
el bloque catch.
Como una instrucción throw transfiere incondicionalmente el control a otra parte del
código, el punto final de una instrucción throw nunca es alcanzable.
Cuando se inicia una excepción, el control se transfiere a la primera cláusula catch de una
instrucción try envolvente que pueda controlar la excepción. El proceso que tiene lugar
desde el punto de inicio de la excepción hasta el punto en que se transfiere el control a un
controlador de excepciones adecuado es conocido con el nombre de propagación de
excepción. La propagación de una excepción consiste en evaluar repetidamente los
78
siguientes pasos hasta encontrar una cláusula catch que coincida con la excepción. En
esta descripción, el punto de inicio es la ubicación desde la que se inicia la excepción.
• En el miembro de función actual, se examina cada instrucción try que
envuelve al punto de inicio. Se evalúan los siguientes pasos para cada
instrucción S, comenzando con la instrucción try más interna y terminando con la
más externa:
• Si el bloque try de S encierra al punto de inicio y S tiene una o varias
cláusulas catch, se examinan las cláusulas catch en orden de aparición hasta
encontrar un controlador adecuado para la excepción. La primera cláusula catch
que especifique el tipo de excepción o un tipo base del tipo de excepción se
considera una coincidencia. Una cláusula catch general es una coincidencia para
cualquier tipo de excepción. Si se encuentra una cláusula catch coincidente,
termina la propagación de excepción y se transfiere el control al bloque de la
cláusula 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 excepción, finaliza el procesamiento de la excepción
actual. Si no, cuando el control alcanza el punto final del bloque finally, continúa
con el procesamiento de la excepción actual.
• Si no se encontró un controlador de excepciones en la llamada del
miembro de función actual, finaliza la llamada al miembro de función. Los pasos
anteriores se repiten para el llamador del miembro de función con el punto de
inicio que corresponda a la instrucción desde la que se invocó al miembro de
función.
• Si el procesamiento de la excepción finaliza todas las llamadas a miembros
de función del subproceso actual indicando que el subproceso no ha encontrado
controlador para la excepción, dicho subproceso finaliza. El impacto de esta
terminación se define según la implementación.
79
Las excepciones en C# las podemos controlar usando las instrucciones try / catch / finally.
Estas instrucciones realmente son bloques de instrucciones, y por tanto estarán
delimitadas con un par de llaves.
Cuando queramos controlar una parte del código 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 excepción, podemos
ejecutar el código 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 intención 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 más 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 código en el que queremos comprobar los errores. El código
a usar será un código 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 código del bloque catch.
Bloque catch
Si se produce una excepción, ésta la capturamos en un bloque catch.
En el bloque catch podemos indicar que tipo de excepción queremos capturar, para ello
usaremos una variable de tipo Exception, la cual puede ser del tipo de error específico
que queremos controlar o de un tipo genérico.
Por ejemplo, si sabemos que nuestro código puede producir un error al trabajar con
ficheros, podemos usar un código como éste:
Nota
try
{
// código para trabajar con ficheros, etc.
}
catch(System.IO.IOException ex)
{
// el código a ejecutar cuando se produzca ese error
}
Si nuestra intención es capturar todos los errores que se produzcan, es decir, no
queremos hacer un filtro con errores específicos, podemos usar la clase Exception como
tipo de excepción a capturar. La clase Exception es la más genérica de todas las clases
para manejo de excepciones, por tanto capturará todas las excepciones que se
produzcan.
Nota
80
try
{
// código que queremos controlar
}
catch(System.Exception ex)
{
// el código 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 aplicación cuando se produzca un error, podemos hacerlo de esta forma:
Nota
try
{
// código que queremos controlar
}
catch
{
// el código 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 información extra sobre el error, pero no siempre vamos a hacer
uso de esa variable, en ese caso podemos utilizar el código 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 intención 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 excepción:
Nota
try
{
// código 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 excepción 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 después del bloque try, por tanto deberíamos poner
las más genéricas al final, de forma que siempre nos aseguremos de que las capturas de
81
errores más específicas se intercepten antes que las genéricas. Aunque el propio
compilador de C# detectará si hay capturas de errores genéricas antes que las más
específicas, avisándonos de ese hecho.
En el siguiente código capturamos un error específico y también uno genérico, con idea
de que tengamos siempre controlado cualquier error que se produzca:
Nota
try
{
// código 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 excepción. De esta forma nos aseguramos de que siempre se
ejecutará un código, por ejemplo para liberar recursos, se haya producido un error o no.
En este código 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 número válido");
// Salimos de la función, pero se ejecutará el finally
return;
}
catch (DivideByZeroException)
{
82
Console.WriteLine("La división por cero no está permitida.");
}
catch (Exception ex)
{
// Captura del resto de excepciones
Console.WriteLine(ex.Message);
}
finally
{
// Este código 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 código indicado en el bloque finally.
Captura de errores no controlados
Como es lógico, si no controlamos las excepciones que se puedan producir en nuestras
aplicaciones, éstas serán inicialmente controladas por el propio runtime de .NET, en estos
casos la aplicación se detiene y se muestra el error al usuario. Pero esto es algo que no
deberíamos consentir, por tanto siempre deberíamos 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 aplicación 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 excepción, lo que hace es revisar "la
pila" de llamadas y buscar algún try/catch, si lo encuentra, lo utiliza, y si no lo encuentra,
se encarga de lanzar la excepción deteniendo el programa.
Esto es importante saberlo, no ya por detectar esos errores que no hemos tenido la
previsión de controlar, sino porque es posible que si un error se produce dentro de un
método en el que no hay captura de errores, pero antes de llamar a ese método hemos
usado un try/catch, el error será interceptado por ese catch, aunque posiblemente ni
siquiera lo pusimos pensando que podía capturar errores producidos en otros niveles más
profundos de nuestra aplicación.
83
file = fileInfo.OpenWrite();
file.WriteByte(0xF);
file.Close();
}
Ejemplo
Para convertir el código anterior en una instrucción try-catch-finally, el código de limpieza
está separado del código activo como se muestra a continuación.
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 excepción en cualquier momento dentro del bloque try antes
de la llamada a OpenWrite() o la propia llamada a OpenWrite() podría producir un error,
no se garantiza que el archivo esté abierto cuando se intenta cerrarlo. El bloque finally
agrega una comprobación para asegurarse de que el objeto FileStream no es null antes
de llamar al método Close. Sin la comprobación de null, el bloque finally podría iniciar su
propia excepción NullReferenceException, pero se debería evitar producir excepciones en
bloques finally si es posible.
Una conexión de base de datos también es un elemento que debería cerrarse en un
bloque finally. Dado que el número de conexiones permitido a un servidor de base de
datos está limitado a veces, es importante cerrar las conexiones de base de datos tan
rápido como sea posible. Si se produce una excepción antes de poder cerrar la conexión,
se trata de otro caso en el que es preferible utilizar el bloque finally a esperar a la
recolección de elementos no utilizados.
84
VIII Flujos y archivos
85
búsqueda. Las propiedades CanRead, CanWrite y CanSeek de la clase Stream y sus
clases derivadas determinan qué operaciones admiten las diversas secuencias.
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 codificación o una
String.
TextReader es la clase base abstracta para StreamReader y StringReader. Mientras que
las implementaciones de la clase abstracta Stream están diseñadas para la entrada y
salida de bytes, las de TextReader están diseñadas 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 están diseñadas para la entrada y salida
de bytes, las de TextWriter están diseñadas para la salida de caracteres Unicode.
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.
Precaución 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 código que se vaya a descargar a través de Internet. En su
lugar, utilice Almacenamiento aislado.
Precaución Al abrir un archivo o una secuencia de red, sólo se realiza una comprobación
de seguridad cuando se construye la secuencia. Por lo tanto, se debe tener cuidado al
entregar estas secuencias a dominios de aplicaciones o código que no sean de confianza.
87
El primer ejemplo muestra cómo agregar texto a un archivo existente. El segundo ejemplo
indica cómo crear un nuevo archivo de texto y escribir una cadena en él. Los métodos
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 código siguientes muestran cómo leer texto desde un archivo de texto. El
segundo ejemplo ofrece una notificación cuando se detecta el fin del archivo. Esta
funcionalidad también se puede conseguir utilizando los métodos 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 código se muestra cómo se escriben y
se leen datos en una nueva secuencia de archivos vacía (Test.data). Después 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. Después 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();
}
}
Bibliografía
WWW.MICROSOFT.COM.MX
91