Archivos en C#

La manera de almacenar y recuperar información que perdure en el tiempo se basa en el uso de “memoria secundaria”, compuesta esencialmente por discos (diskettes, discos duros, CD, DVD, etc.) y ocasionalmente cintas. En cualquiera de estos medios, la unidad de almacenamiento de información se denomina archivo.

Streams

La lectura y escritura a un archivo son hechas usando un concepto genérico llamado stream. La idea detrás del stream existe hace tiempo, cuando los datos son pensados como una transferencia de un punto a otro, es decir, como un flujo de datos. En el ambiente .NET se puede encontrar muchas clases que representan este concepto que trabaja con archivos o con datos de memoria (como se muestra en la figura de abajo).

                                   Clases del Framework .NET para el uso de Streams.

Un stream es como se denomina a un objeto utilizado para transferir datos. Estos datos pueden ser transferidos en dos posibles direcciones:
  • Si los datos son transferidos desde una fuente externa al programa, entonces se habla de “leer desde el stream”.
  • Si los datos son transferidos desde el programa a alguna fuente externa, entonces se habla de “escribir al stream”.
Frecuentemente, la fuente externa será un archivo, pero eso no necesariamente es el caso, por lo que el concepto es utilizado ampliamente con fuentes de información externas de diversos tipos. Algunas otras posibilidades fuera de los archivos incluyen:
  • Leer o escribir datos a una red utilizando algún protocolo de red, donde la intención es que estos datos sean recibidos o enviados por otro computador.
  • Lectura o escritura a un área de memoria.
  • La Consola
  • La Impresora
  • Otros 
Algunas clases que C# provee para resolver este acceso a fuentes diversas incluyen las clases de tipo: Reader y Writer.

BufferedStream

Esta clase se utiliza para leer y para escribir a otro stream. Se utiliza por razones del performance, cuando el caché de los datos del archivo es utilizado por el sistema operativo subyacente. 

El uso de streams para la lectura y escritura de archivo es directa pero lenta con bajo performance. Por esta razón la clase BufferedStream existe y es más eficiente. Puede ser utilizado por cualquier clase de stream. Para operaciones de archivo es posible utilizar FileStream, donde el buffering está ya incluido. 

Leyendo desde un archivo usando BufferedStream

using System;
using System.Text;
using System.IO;

static void Main(string[] args)
{
    string path = "c:\\sample\\sample.xml";
    Stream instream = File.OpenRead(path);

    // crear buffer para abrir stream
    BufferedStream bufin = new BufferedStream(instream);
    byte[] bytes = new byte[128];

    // leer los primeros 128 bytes del archivo
    bufin.Read(bytes, 0, 128);
    Console.WriteLine("Allocated bytes: "+Encoding.ASCII.GetString(bytes));
}

Leyendo desde un archivo de texto

using System;
using System.IO;

static void Main(string[] args)
{
    string fileName = "temp.txt";
    FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read);
    StreamReader reader = new StreamReader(stream);

    while (reader.Peek() > -1) Console.WriteLine(reader.ReadLine());
    reader.Close();
}

Escribiendo en un archive de texto

using System;
using System.IO;

static void Main(string[] args)
{
    string fileName = "temp.txt";
    FileStream stream = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.Write);
    StreamWriter writer = new StreamWriter(stream);

    writer.WriteLine("Esta es la primera línea del archivo.");
    writer.Close();
}

Creando un archivo y escribiendo en este

Este ejemplo usa el método CreateText() el cual crea un Nuevo archive y retorna un objeto treamWriter que escribe a un archivo usando formato UTF-8.

using System;
using System.IO;

static void Main(string[] args)
{
    string fileName = "temp.txt";
    StreamWriter writer = File.CreateText(fileName);

    writer.WriteLine("Este es mi Nuevo archivo creado.");
    writer.Close();
}

Insertando texto en un archivo

using System;
using System.IO;

static void Main(string[] args)
{
    try
    {
        string fileName = "temp.txt";
        // esto inserta texto en un archivo existente, si el archivo no existe lo crea
        StreamWriter writer = File.AppendText(fileName);
        writer.WriteLine("Este es el texto adicionado.");
        writer.Close();
    }
    catch
    {
        Console.WriteLine("Error");
    }
}

Leyendo un archivo binario

using System;
using System.IO;

static void Main(string[] args)
{
    try
    {
        string fileName = "temp.txt";
        int letter = 0;
        FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read);
        BinaryReader reader = new BinaryReader(stream);

        while (letter != -1)
        {
            letter = reader.Read();
            if (letter != -1) Console.Write((char)letter);
        }
        reader.Close();
        stream.Close();
    }
    catch
    {
        Console.WriteLine("Error");
    }
}

Escribiendo en un archivo binario

static void Main(string[] args)
{
    try
    {
        string fileName = "temp.txt";
        // data a ser guardada
        int[] data = {0, 1, 2, 3, 4, 5};
        FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Write);
        BinaryWriter writer = new BinaryWriter(stream);

        for(int i=0; i<data.Length; i++)
        {
            // números son guardados en formáto UTF-8 format (4 bytes)
            writer.Write(data[i]);
        }

        writer.Close();
        stream.Close();
    }
    catch
    {
        Console.WriteLine("Error");
}

Ver cambios en el file system

.NET Framework provee la clase System.IO.FileSystemWatcher la cual permite ver si hay cambios en el file system.

using System;
using System.IO;
class WatcherSample
{
    static void Main(string[] args)
    {
        // ver los cambios en el directorio de la aplicación y sobre todos los archivos
        FileSystemWatcher watcher = new FileSystemWatcher(System.Windows.Forms.Application.StartupPath, "*.*");

        // ver el nombre del archivo y tamaño cambiado
        watcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.Size;
        watcher.Changed += new FileSystemEventHandler(OnChange);
        watcher.Created += new FileSystemEventHandler(OnChange);
        watcher.Deleted += new FileSystemEventHandler(OnChange);
        watcher.Renamed += new RenamedEventHandler(OnChange);
        watcher.EnableRaisingEvents = true;

        // espera por una tecla para terminar la aplicación
        Console.ReadLine();
    }
    private static void OnChange(object sender, FileSystemEventArgs e)
    {
        Console.WriteLine("File: {0} - change type: {1}", e.FullPath, e.ChangeType);
    }
    private static void OnChange(object sender, RenamedEventArgs e)
    {
        Console.WriteLine("File: {0} renamed to {1}", e.OldName, e.Name);



Clase Parcial (Partial Class) en C#

El lenguaje C# permite la implementación de una clase en dos o más archivos. Para esto hay que agregarle el modificador partial cuando declaramos la clase.
Este concepto es ámpliamente utilizado por el entorno del Visual Studio .Net en la generación de interfaces visuales.
Como veremos en conceptos futuros es necesario presentar "partial class" para su entendimiento.
Una clase parcial no es más ni menos que crear una clase completa y luego agrupar métodos y propiedades en dos o más archivos.

Ejercicios de Clases en C#

Ejemplo 1

Plantear una clase Rectángulo, definir dos propiedades: Lado1 y Lado2. Definir dos métodos RetornarSuperficie y RetornarPerimetro. Dividir la clase en dos archivos utilizando el concepto de "partial class".

Programa.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ClaseParcial
{
    class Program
    {
        static void Main(string[] args)
        {
            Rectangulo rectangulo1 = new Rectangulo();
            rectangulo1.Lado1 = 5;
            rectangulo1.Lado2 = 10;
            Console.WriteLine("La superficie del rectángulo es:" +
                                rectangulo1.RetornarSuperficie());
            Console.WriteLine("El perímetro del rectángulo es:" +
                                rectangulo1.RetornarPerimetro());
            Console.ReadKey();
        }
    }
}


Clase1.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ClaseParcial
{
    partial class Rectangulo
    {
        private int lado1;
        public int Lado1
        {
            set
            {
                lado1 = value;
            }
            get
            {
                return lado1;
            }
        }
        private int lado2;
        public int Lado2
        {
            set
            {
                lado2 = value;
            }
            get
            {
                return lado2;
            }
        }
    }
}

Clase2.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ClaseParcial
{
    partial class Rectangulo
    {
        public int RetornarSuperficie()
        {
            int sup = Lado1 * Lado2;
            return sup;
        }

        public int RetornarPerimetro()
        {
            int per = Lado1 * 2 + Lado2 * 2;
            return per;
        }
    }
}

El resultado al ejecutar el programa es:

Herencia en C#

El mecanismo de herencia es uno de los pilares fundamentales en los que se basa la programación orientada a objetos. Es un mecanismo que permite definir nuevas clases a partir de otras ya definidas de modo que si en la definición de una clase indicamos que ésta deriva de otra, entonces la primera -a la que se le suele llamar clase hija- será tratada por el compilador automáticamente como si su definición incluyese la definición de la segunda –a la que se le suele llamar clase padre o clase base. 

Las clases que derivan de otras se definen usando la siguiente sintaxis:

            class <nombreHija>:<nombrePadre>
            {
                 <miembrosHija>
            } 
A los miembros definidos en <miembrosHijas> se le añadirán los que hubiésemos definido en la clase padre. Por ejemplo, a partir de la clase Persona puede crearse una clase Trabajador así:

          class Trabajador:Persona
          {
                 public int Sueldo;
                 public Trabajador (string nombre, int edad, string nif, int sueldo)
                :base(nombre, edad, nif)
                {
                      Sueldo = sueldo;
                }
          } 

Los objetos de esta clase Trabajador contarán con los mismos miembros que los objetos Persona y además incorporarán un nuevo campo llamado Sueldo que almacenará el dinero que cada trabajador gane. Nótese además que a la hora de escribir el constructor de esta clase ha sido necesario escribirlo con una sintaxis especial consistente en preceder la llave de apertura del cuerpo del método de una estructura de la forma:

        : base(<parametrosBase>) 

A esta estructura se le llama inicializador base y se utiliza para indicar cómo deseamos inicializar los campos heredados de la clase padre. No es más que una llamada al constructor de la misma con los parámetros adecuados, y si no se incluye el compilador consideraría por defecto que vale :base(), lo que sería incorrecto en este ejemplo debido a que Persona carece de constructor sin parámetros.

Un ejemplo: 

using System;

class Persona
{
    // Campo de cada objeto Persona que almacena su nombre

    public string Nombre;
    // Campo de cada objeto Persona que almacena su edad
    public int Edad;
    // Campo de cada objeto Persona que almacena su NIF

    public string NIF;

    void Cumpleaños()   // Incrementa en uno de edad del objeto Persona
    {
        Edad++;
    }

    // Constructor de Persona
    public Persona(string nombre, int edad, string nif)
    {
        Nombre = nombre;
        Edad = edad;
        NIF = nif;
    }
}

class Trabajador : Persona
{

    // Campo de cada objeto Trabajador que almacena cuánto gana
    public int Sueldo;

    Trabajador(string nombre, int edad, string nif, int sueldo)
        : base(nombre, edad, nif)
    {  // Inicializamos cada Trabajador en base al constructor de Persona
        Sueldo = sueldo;
    }

    public static void Main()
    {
        Trabajador p = new Trabajador("Josan", 22, "77588260-Z", 100000);
        Console.WriteLine("Nombre=" + p.Nombre);
        Console.WriteLine("Edad=" + p.Edad);
        Console.WriteLine("NIF=" + p.NIF);
        Console.WriteLine("Sueldo=" + p.Sueldo);
        Console.ReadKey();
    }
}


El Resultado al Ejecutar seria lo siguiente:


Ejercicios con Herencia en C#

La herencia significa que se pueden crear nuevas clases partiendo de clases existentes, que tendrá todas los atributos, propiedades y los métodos de su 'superclase' o 'clase padre' y además se le podrán añadir otros atributos, propiedades y métodos propios.

clase padre en c#

Clase de la que desciende o deriva una clase. Las clases hijas (descendientes) heredan (incorporan) automáticamente los atributos, propiedades y métodos de la la clase padre.

Subclase en c#

Clase descendiente de otra. Hereda automáticamente los atributos, propiedades y métodos de su superclase. Es una especialización de otra clase. Admiten la definición de nuevos atributos y métodos para aumentar la especialización de la clase.

Veamos un ejemplo de herencia en la siguiente figura: Tenemos una clase Profesor que hereda de la clase Personal. 



A continuación veremos algunos ejemplos para entender mejor sobre herencia en c#

Ejercicios Resueltos de Herencia en C# 

Ejemplo 1

Ahora plantearemos el primer problema utilizando herencia. Supongamos que necesitamos implementar dos clases que llamaremos Suma y Resta. Cada clase tiene como atributo valor1, valor2 y resultado. Las propiedades a definir son Valor1, Valor2 y Resultado, el método Operar (que en el caso de la clase "Suma" suma los dos Valores y en el caso de la clase "Resta" hace la diferencia entre Valor1 y Valor2.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Herencia
{

    public class Operacion
    {
        protected int valor1;
        protected int valor2;
        protected int resultado;

        public int Valor1
        {
            set
            {
                valor1 = value;
            }
            get
            {
                return valor1;
            }
        }

        public int Valor2
        {
            set
            {
                valor2 = value;
            }
            get
            {
                return valor2;
            }
        }

        public int Resultado
        {
            protected set
            {
                resultado = value;
            }
            get
            {
                return resultado;
            }
        }
    }


    public class Suma : Operacion
    {
        public void Operar()
        {
            Resultado = Valor1 + Valor2;
        }
    }


    public class Resta : Operacion
    {
        public void Operar()
        {
            Resultado = Valor1 - Valor2;
        }
    }

    class Prueba
    {
        static void Main(string[] args)
        {
            Suma suma1 = new Suma();
            suma1.Valor1 = 10;
            suma1.Valor2 = 7;
            suma1.Operar();
            Console.WriteLine("La suma de " + suma1.Valor1 + " y " +
              suma1.Valor2 + " es " + suma1.Resultado);
            Resta resta1 = new Resta();
            resta1.Valor1 = 8;
            resta1.Valor2 = 4;
            resta1.Operar();
            Console.WriteLine("La diferencia de " + resta1.Valor1 +
              " y " + resta1.Valor2 + " es " + resta1.Resultado);
            Console.ReadKey();
        }
    }
}

Al ejecutar el código muestra el siguiente resultado


Ejemplo 2

Crear una clase Persona que tenga como atributos el nombre y la edad (definir las propiedades para poder acceder a dichos atributos). Definir como responsabilidad un método para imprimir.
Plantear una segunda clase Empleado que herede de la clase Persona. Añadir un atributo sueldo ( y su propiedad) y el método para imprimir su sueldo.
Definir un objeto de la clase Persona y llamar a sus métodos y propiedades. También crear un objeto de la clase Empleado y llamar a sus métodos y propiedades.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Herencia
{
    public class Persona
    {
        protected string nombre;
        protected int edad;

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

        public int Edad
        {
            set
            {
                edad = value;
            }
            get
            {
                return edad;
            }
        }

        public void Imprimir()
        {
            Console.WriteLine("Nombre: " + Nombre);
            Console.WriteLine("Edad: " + Edad);
        }
    }

    public class Empleado : Persona
    {
        protected float sueldo;

        public float Sueldo
        {
            set
            {
                sueldo = value;
            }
            get
            {
                return sueldo;
            }
        }

        new public void Imprimir()
        {
            base.Imprimir();
            Console.WriteLine("Sueldo: " + Sueldo);
        }
    }

    class Prueba
    {
        static void Main(string[] args)
        {
            Persona persona1 = new Persona();
            persona1.Nombre = "Juan";
            persona1.Edad = 25;
            Console.WriteLine("Los datos de la persona son: ");
            persona1.Imprimir();

            Empleado empleado1 = new Empleado();
            empleado1.Nombre = "Maria";
            empleado1.Edad = 42;
            empleado1.Sueldo = 2524;
            Console.WriteLine("Los datos del empleado son: ");
            empleado1.Imprimir();

            Console.ReadKey();
        }
    }
}

Al ejecutar el código muestra el siguiente resultado

Ejercicios con Clases en C#

Normalmente un problema resuelto con la metodología de programación orientada a objetos no interviene una sola clase, sino que hay muchas clases que interactúan y se comunican. Plantearemos un problema separando las actividades en dos clases.

Ejemplo 1

Un banco tiene 3 clientes que pueden hacer depósitos y extracciones. También el banco requiere que al final del día calcule la cantidad de dinero que hay depositada.

La Solución tendrá el siguiente esquema: Debemos definir los atributos y los métodos de cada clase:

Cliente
    atributos
        nombre
        monto
    métodos
        constructor
        Depositar
        Extraer
        RetornarMonto

Banco
    atributos
        3 Cliente (3 objetos de la clase Cliente)
    métodos
        constructor
        Operar
        DepositosTotales

Solución en C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace EjerciciosconClases
{
    class Cliente
    {
        private string nombre;
        private int monto;

        public Cliente(string nom)
        {
            nombre = nom;
            monto = 0;
        }

        public void Depositar(int m)
        {
            monto = monto + m;
        }

        public void Extraer(int m)
        {
            monto = monto - m;
        }

        public int RetornarMonto()
        {
            return monto;
        }

        public void Imprimir()
        {
            Console.WriteLine(nombre + " tiene depositado la suma de " + monto);
        }
    }

    class Banco
    {
        private Cliente cliente1, cliente2, cliente3;

        public Banco()
        {
            cliente1 = new Cliente("Yhonas");
            cliente2 = new Cliente("Ana");
            cliente3 = new Cliente("Pedro");
        }

        public void Operar()
        {
            cliente1.Depositar(100);
            cliente2.Depositar(150);
            cliente3.Depositar(200);
            cliente3.Extraer(150);
        }

        public void DepositosTotales()
        {
            int t = cliente1.RetornarMonto() +
                    cliente2.RetornarMonto() +
                    cliente3.RetornarMonto();
            Console.WriteLine("El total de dinero en el banco es:" + t);
            cliente1.Imprimir();
            cliente2.Imprimir();
            cliente3.Imprimir();
        }

        static void Main(string[] args)
        {
            Banco banco1 = new Banco();
            banco1.Operar();
            banco1.DepositosTotales();
            Console.ReadKey();
        }
    }
}

Al ejecutar el código muestra el siguiente resultado



Ejemplo 2

Plantear un programa que permita jugar a los dados. Las reglas de juego son: se tiran tres dados si los tres salen con el mismo valor mostrar un mensaje que "gano", sino "perdió".
Lo primero que hacemos es identificar las clases:
Luego los atributos y los métodos de cada clase:

Dado
    atributos
        valor
    métodos
        constructor
        Tirar
        Imprimir
        RetornarValor

JuegoDeDados
    atributos
        3 Dado (3 objetos de la clase Dado)
    métodos
        constructor
        Jugar

Solución en C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace EjerciciosconClases
{
    class Dado
    {
        private int valor;
        private static Random aleatorio;

        public Dado()
        {
            aleatorio = new Random();
        }

        public void Tirar()
        {
            valor = aleatorio.Next(1, 7);
        }

        public void Imprimir()
        {
            Console.WriteLine("El valor del dado es:" + valor);
        }

        public int RetornarValor()
        {
            return valor;
        }
    }

    class JuegoDeDados
    {
        private Dado dado1, dado2, dado3;

        public JuegoDeDados()
        {
            dado1 = new Dado();
            dado2 = new Dado();
            dado3 = new Dado();
        }

        public void Jugar()
        {
            dado1.Tirar();
            dado1.Imprimir();
            dado2.Tirar();
            dado2.Imprimir();
            dado3.Tirar();
            dado3.Imprimir();
            if (dado1.RetornarValor() == dado2.RetornarValor() &&
                dado1.RetornarValor() == dado3.RetornarValor())
            {
                Console.WriteLine("Ganó");
            }
            else
            {
                Console.WriteLine("Perdió");
            }
            Console.ReadKey();
        }

        static void Main(string[] args)
        {
            JuegoDeDados j = new JuegoDeDados();
            j.Jugar();
        }
    }
}

Al ejecutar el código muestra el siguiente resultado