Colabora
 

Trabajando con ficheros desde C Sharp 2005

Manipulación de ficheros binarios y texto

 

Fecha: 16/Jun/2007 (14 junio 2007)
Revisado: 21/Jun/2007
Autor: José Alejandro Lugo García - jalugo@uci.cu

 


Introducción

¿Quién no ha necesitado el uso de ficheros al redactar un programa? ¿Quién a su vez no se ha complicado porque este y mas cual otro lenguaje de programación le hace la vida difícil para manejar los ficheros?

El trabajo con ficheros siempre ha sido una cuestión vital dentro de todos los Entornos de Desarrollo Integrados (IDEs). Para esto, cada lenguaje implementa de una forma u otra mediante el uso de librerías, el acceso desde nuestra aplicación a los recursos del sistema. Las operaciones básicas sobre el uso de ficheros se consideran de lectura y escritura, aunque también se encuentran el manejo de los atributos del fichero hasta la creación y/o eliminación de carpetas de nuestro disco duro.

Un poco de historia

Desde que surgieron los ordenadores han sido necesarios mecanismos para guardar la información de forma persistente en el disco duro. Los programas informáticos pueden almacenar la información en la RAM de su computadora mas al cerrar el programa los datos que tiene en su poder se pierden si no son almacenados previamente. De aquí surge la importancia del trabajo con ficheros, una funcionalidad que cada lenguaje de programación implementa para brindarles a los programadores la posibilidad de manejar la información de forma más eficiente y garantizar su durabilidad.

Para quieres han programado con C++ o C, el trabajo con ficheros les traerá a la cabeza malos recuerdos (como es el caso de quien les escribe). Demasiadas declaraciones para conformar un fichero de entrada o salida, el uso de structs o ingenios algorítmicos, hacen del mismo una característica común para el trabajo con archivos.

Con el surgimiento de la plataforma .NET llega a nosotros la caballería pesada, una serie de novedosas funcionalidades incluidas en un espacio de nombre que luego se hará famoso entre quienes aún, por desconocimiento o falta de apetito en nuevos lenguajes, no conoce, me refiero a System.IO.

Como ya veremos más adelante el Framework 2.0 de .NET no se ha quedado atrás y ha propuesto para Visual Studio 2005 una mejor concepción del trabajo con ficheros tanto binarios como de texto. Pero antes, algunas definiciones referente a estos últimos para los novatos en el asunto.

Fichero texto: Son aquellos que guardan la información en formato claro, es decir, si ud guarda la cadena string "Hola mundo" hacia un fichero de tipo texto llamado datos.txt, cuando lo abra, verá este mismo mensaje en su interior.

Fichero binario: Son aquellos que guardan la información en formato no legible para los humanos, es decir, si ud guarda la cadena string "Hola mundo" hacia un fichero de tipo binario llamado datos.txt, cuando lo abra, su contenido no podrá ser leído por la persona en cuestión.

Trabajando con la información

Muchos programadores se encuentran con la necesidad de guardar largas colecciones de objetos hacia fichero y también se enfrentan con la variante de poder trabajar con ficheros de texto o binario. Visto el epígrafe anterior se desprende el uso que se le dé a uno u otro tipo, está claro que si no queremos que nadie pueda ver por fuera de nuestra aplicación qué datos contiene una colección determinada, pues debemos utilizar los ficheros binarios y en caso contrario emplearíamos los de texto. A continuación veremos cómo trabajar tanto con ficheros binarios como de texto en conjunto con las listas de datos que podamos querer guardar en ellos, todo esto utilizando el lenguaje de programación C# desde el IDE Visual Studio 2005 y de paso, veremos las facilidades a las que hacía mención previamente.

Ficheros binarios & Listas de Objetos

Para serializar/deserializar listas de objetos (guardarlos o abrirlos hacia/desde fichero) (Visual Studio 2005)

Debes poner arriba de tu clase la etiqueta [Serializable], o sea, si quieres guardar/cargar una lista de trabajadores, pues debes ponerle esta etiqueta encima, quedaría así:

[Serializable]
public class Trabajador
{
   private int codigo;
   private string nombre;

public int Codigo
{
   get
   {
     return codigo;
   }
   set
   {
     this.codigo = value;
   }
}

public string Nombre
{
    get
     {
        return nombre;
     }
     set
     {
        this.nombre= value;
     }
   }
}

Esto le indica al compilador que tu clase está lista para ser usada como tal y con eso pues puedes realizar la serializacion de la propia clase o de Listas con tipo de esta clase. Si no le pones la etiqueta verás que no funcionará bien a la hora de aplicarle el código que te pongo a continuación.

Para guardar una lista de objetos de este tipo de clase hacia fichero.

using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

//Guardar una lista de Objetos a Fichero

List<Trabajador> listTrab = (Método que devuelve datos de este tipo)

IFormatter formatter = new BinaryFormatter();

FileStream fs = new FileStream(saveFileDialog1.FileName,FileMode.Create);

formatter.Serialize(fs, listTrab);

fs.Close();

Para cargarlos:

using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

//Cargar una lista de Objetos desde Fichero

IFormatter formatter = new BinaryFormatter();

FileStream fs = new FileStream(openFileDialog1.FileName, FileMode.Open);

//Puedes hacerlo así
List<Trabajador> listTrab = (List<Trabajador>)formatter.Deserialize(fs);

//O puedes hacerlo así
List<Trabajador> listTrab = formatter.Deserialize(fs) as List<Trabajador>;

fs.Close();

Ficheros Texto & Listas de Objetos

Para el caso del trabajo con ficheros texto y listas de objetos, nos pudieran dar un fichero ya creado para nosotros construir una lista de algun tipo de dato con este.

Para el caso de un fichero texto generado con este método:

StreamWriter fw = new StreamWriter(path + "Listado de Clientes.txt");

foreach (ClientePersistente CP in list)
{
   fw.WriteLine(CP.NumeroCoche + "-" + CP.Nombre + "-" + 
                CP.Direccion + "-" + CP.Modelo);
}
fw.Close();

Cuya salida sería esta:

1-Enrique Peraza-Avenida 1ra Rpto Esperanza-Mercedes Benz
10-Mercedez Concepción-Calle 5ta, Matanzas-Audi
11-Pedro Espinosa Pérez-Santiago de las Vegas, Habana-Ferrari
2-Rosa María del Rosario-San Jose Lajas Habana-Audi
3-Mirtha Menéses-Santa Clara, Cuba-Alpha Romeo
4-Angel de la Guarda-Sueño, Santiago de Cuba, Cuba-Peugeot
5-Miguel Angel Martínez-Camajuani, VC-Audi
6-Iliana Hdez-San Juan y Martínez, Habana-Peugeot 205
7-Yumilka Diaz-Ciego de Avila-Peugeot 206
8-Yoandy Echevarría-Habana-Mercedes Benz
9-Marcheco Pérez-Habana-Niva

Como ven cada línea del fichero es un objeto, los atributos están separados en este caso por el carácter char '-'. Para construir una lista con estos clientes donde podemos identificar como atributos del objeto el identificador, el nombre, la dirección y el modelo de auto, podríamos hacer algo como esto:

StreamReader fr = new StreamReader(path + "Listado de Clientes.txt");

List<ClientePersistente> listCliente = new List<ClientePersistente>();

while(!fr.EndOfStream)
{
   string linea= fr.ReadLine();
   string[] datos = linea.Split('-');
   listCliente.Add(new ClientePersistente(datos[0], datos[1],
   datos[2], datos[3]));
}
fr.Close();

El split "picaría" en pedacitos una línea string. Estos pedacitos estarían delimitados por el carácter char '-' Estos pedacitos se guardan en un arreglo de tipo string. Esto es por supuesto para el caso donde la entrada fuera de la manera de que en cada línea sus atributos estuvieran separados por '-'. Si en lugar de estar separados por '-' estuvieran separados por dos espacios en blanco, o cualquier cadena mas compleja que un char, o sea un string, se debe sustituir la línea anterior del Split por esta:

//Ejemplo para atributos separados por dos espacios en blanco.
string[] datos = var.Split(new string[] {"  "}, StringSplitOptions.RemoveEmptyEntries);

Para el caso de un fichero texto generado con este método:

StreamWriter fw = new StreamWriter(path + "Listado de Clientes.txt");

foreach (ClientePersistente CP in list)
{
   fw.WriteLine(CP.NumeroCoche);
   fw.WriteLine(CP.Nombre);
   fw.WriteLine(CP.Direccion);
   fw.WriteLine(CP.Modelo);
   fw.WriteLine();
}
fw.Close();

cuya salida sería esta:

1
Enrique Peraza
Avenida 1ra Rpto Esperanza
Mercedes Benz

10
Mercedez Concepción
Calle 5ta, Matanzas
Audi

11
Pedro Espinosa Pérez
Santiago de las Vegas, Habana
Ferrari

2
Rosa María del Rosario
San Jose Lajas Habana
Audi

3
Mirtha Menéses
Santa Clara, Cuba
Alpha Romeo

4
Angel de la Guarda
Sueño, Santiago de Cuba, Cuba
Peugeot

5
Miguel Angel Martínez
Camajuani, VC
Audi

6
Iliana Hdez
San Juan y Martínez, Habana
Peugeot 205

7
Yumilka Diaz
Ciego de Avila
Peugeot 206

8
Yoandy Echevarría
Habana
Mercedes  Benz

9
Marcheco Pérez
Habana
Niva

Haríamos nuestra lista de una manera un poco más sencilla pues no habría que usar el Split. Quedaría así:

StreamReader fr = new StreamReader(path + "Listado de Clientes.txt");

List<ClientePersistente> listCliente = new List<ClientePersistente>
();

while (!fr.EndOfStream)
{
   string id = fr.ReadLine();
   string nombre = fr.ReadLine();
   string direccion = fr.ReadLine();
   string modelo = fr.ReadLine();
   fr.ReadLine();

   listCliente.Add(new ClientePersistente(id,nombre,direccion,modelo));
}
fr.Close();

Posibles dudas

A continuación se exponen una serie de preguntas que pudieran convertirse en la duda de ud, amigo programador.

Pregunta 1:
En el ejemplo pusiste la variable saveFileDialog1 si yo la pongo como string me crea el archivo con ese nombre pero le tengo que quitar el .FileName. Ahora bien mi pregunta es: ¿Que tipo de dato es el que lleva esa variable?
Respuesta:
SaveDialog es el componente que te abre la ventana Windows de Salvar un fichero y escoger la dirección donde deseas hacerlo.
saveDialog1.FileName es la forma de obtener en un string la dirección donde guardaste el fichero con su nombre incluido, ejemplo: C:\Documents and Settings\jalugo\Desktop\archivo.txt

Otra forma de pasarle a FileStream fs = new FileStream("paramValue", FileMode.Create) el parámetro que te puse en negrita, por ejemplo, sería:

FileStream fs =
    new FileStream("C:\Documents and Settings\jalugo\ Desktop\archivo.txt",
                   FileMode.Create)

pero puedes darte cuenta que para múltiples propósitos tendrías que cambiarle manualmente al código, el camino para llegar al archivo, por tanto he utilizado la property FileName del componente SaveDialog que te devuelve en un string todo lo necesario para guardar el archivo.

Pregunta 2:
Estoy tratando de aprender a trabajar con ficheros en c#. La cosa está en que me da un error cuando lo escribo de esta manera:

FileStream fs =
    new FileStream("C:\Documents and Settings\docencia\Desktop\archivo.txt",
                   FileMode.Create);

este es el error que me pone: Unrecognized escape sequence

Respuesta:
Para que no te de error eso que publicas tendrías que ponerlo así:

FileStream fs =
    new FileStream("C:\\Documents and Settings\\docencia\\Desktop\\archivo.txt",
                   FileMode.Create);

o de esta manera:

FileStream fs =
    new FileStream(@"C:\Documents and Settings\docencia\ Desktop\archivo.txt",
                   FileMode.Create);

Fíjate en el arroba (@) para evitar tener que poner las barras dobles... Lo que pasa con el \ es que todos los \<algo> son "caracteres de escape", que es la manera de expresar saltos de linea, tabulador etc. con @ se le dice al compilador que entienda esa cadena de manera literal tal y como esta, la manera de hacer que se muestre \ es el caracter de escape \\.

Pregunta 3:
Díganme si se puede guardar un formulario completo y de poderse, por favor díganme cómo. El drama es que tengo varias cosas en el formulario, entre los que está una matriz de PictureBox, y a la hora de serializar me dice algo como que la clase PictureBox no puede ser serializada. Si alguien tiene una idea de cómo puedo hacerlo...

Respuesta:
La solución está en poner todos los posibles tipos de datos que contenga tu formulario hacia un objeto y le pongas encima la etiqueta Serializable tal y como se explicó anteriormente. Para el caso del PictureBox la imagen que contiene es de tipo Image y la misma es un tipo de dato serializable por tanto no hay problema en que la puedas guardar conjuntamente con los otros tipos de datos que puedan haber (string, datetime, etc). Tu clase quedaría así:

[Serializable]
public class Datos
{
   string text1, text2;
   DateTime dt;
   Image img;
   public Datos(string t1, string t2, DateTime dt, Image img)
   {
      this.text1 = t1;
      this.text2 = t2;
      this.dt = dt;
      this.img = img;
   }

   public string Text2
   {
      get { return text2; }
      set { text2 = value; }
   }

   public string Text1
   {
      get { return text1; }
      set { text1 = value; }
   }

   public DateTime Dt
   {
      get { return dt; }
      set { dt = value; }
   }

   public Image Img
   {
      get { return img; }
      set { img = value; }
   }
}

Si me preguntas como sé que Image es serializable, con el proyecto abierto desde el Visual Studio, da clic derecho encima de Image y selecciona "Go To Definition", verás que la clase implementa la interfaz "ISerializable". Resumiendo, utilizando la función Serialize y Deserialize se pueden guardar tipos de datos (con la etiqueta Serializable) que engloben otros tipos de datos serializables como es el caso de Image.

Pregunta 4:

Tengo un problema con mi fichero. Yo al inicio de mi programa le pido al usuario que entre su nick y contraseña los cuales estan guardados en un fichero y lo que hago es comprobar si estos datos son correctos. Hasta aqui no tengo problemas. El problema surge cuando al yo adicionar usuarios a mi fichero se me borran los anteriores que tenia. ¿Qué puedo hacer?

Respuesta:

Debes comprobar si estás abriendo el fichero en modo Create (FileMode.Create), caso con el cual te sobreescribe el fichero y por supuesto te borra lo que tenías anteriormente. Para solucionar esto lo debes poner en FileMode.Append, para que te ponga los nuevos datos al final del fichero que ya tienes creado...

Conclusiones

El trabajo con ficheros texto al igual que con binarios es una tarea que resulta relativamente fácil para Visual C# 2005. Hemos visto cómo construir tanto ficheros textos como binarios que representen listas de objetos al igual que hemos aprendido a revertir el proceso, leyendo de los mismos y volviendo a construir dichas listas.

 


Espacios de nombres usados en el código de este artículo:

 

System.Runtime.Serialization - para el IFormatter
System.Runtime.Serialization.Formatters.Binary - para el BinaryFormatter

 


Código de ejemplo (comprimido):

 

Fichero con el código de ejemplo: jalugo_trabajo_ficheros_CSharp.zip - 14.20 KB

(MD5 checksum: FA99DFE045AA56CD875F120162D4F043)

 


Ir al índice principal de el Guille