TABLA DE CONTENIDO Pág. 1. INTERFACES ............................................................................................................ 1 1.1 DEFINICIÓN ..................................................................................................................1 1.2 1.3 INTERFAZ BANDERA.........................................................................................................4 EJEMPLOS DE SITUACIONES EN LAS CUALES SE EMPLEAN INTERFACES. ............................................4 1.1.1 1.1.2 2 AGRUPACIÓN DE CLASES POR FUNCIONALIDAD: NAMESPACES. ........................... 18 2.1 2.2 2.3 3 LANZAR UNA EXCEPCIÓN................................................................................................. 24 LOS BLOQUES TRY, CATCH Y FINALLY. ............................................................................... 26 CONSIDERACIONES RESPECTO AL MANEJO DE LAS EXCEPCIONES. ................................................ 27 MANEJO DE COLECCIONES EN .NET. ........................................................................ 29 4.1 4.2 4.3 4.4 4.5 5 ESPACIOS DE NOMBRES ANIDADOS. ................................................................................... 19 EL USING. .................................................................................................................. 21 EL ACCESO FÍSICO. ....................................................................................................... 21 MANEJO DE ERRORES EN .NET................................................................................. 24 3.1 3.2 3.3 4 Encabezado .........................................................................................................2 El cuerpo.............................................................................................................2 LA INTERFAZ SYSTEM.COLLECTIONS.ICOLLECTION................................................................. 29 LA INTERFAZ SYSTEM.COLLECTIONS.ICOMPARER................................................................... 31 LA INTERFAZ SYSTEM.COLLECTIONS.IENUMERATOR. .............................................................. 32 LA CLASE SYSTEM.COLLECTIONS.ARRAYLIST........................................................................ 32 ALGUNAS OBSERVACIONES ADICIONALES. ............................................................................ 37 FLUJOS Y SERIALIZACIÓN EN .NET ......................................................................... 39 5.1 5.2 LA DEFINICIÓN DE FLUJO. ............................................................................................... 39 CLASES PARA EL MANEJO DE FLUJOS EN .NET ....................................................................... 40 5.3 5.4 EL CONCEPTO DE SERIALIZACIÓN ...................................................................................... 49 CONCEPTOS BÁSICOS PARA EL MANEJO DE SERIALIZACIÓN EN .NET ............................................ 49 5.2.1 5.2.2 5.2.3 5.2.4 5.2.5 5.2.6 5.2.7 5.2.8 FileMode. .......................................................................................................... 40 FileAccess.......................................................................................................... 40 System.IO.FileShare ........................................................................................... 40 File. .................................................................................................................. 41 StreamReader.................................................................................................... 41 BinaryReader. .................................................................................................... 44 StreamWriter. .................................................................................................... 46 BinaryWriter. ..................................................................................................... 47 1. Interfaces Una interfaz es una forma de establecer un contrato respecto a ofrecer un determinado comportamiento, independiente de la forma en la cual este se preste. Para clarificar esto se puede citar el caso de una interfaz muy común, tanto en .NET como en Java, la interfaz IComparable. Esta interfaz puede ser empleada por cualquier clase que requiera otorgar a sus objetos la posibilidad de compararse entre ellos, sin importar a qué jerarquía de herencia pertenezcan. Un programador que esté desarrollando un programa de recursos humanos, bien puede requerir que los objetos Empleados puedan compararse, para así garantizar que no haya duplicados;y otro programdor que esté desarrollando la aplicación de Facturación puede requerir la habilidad de comparar objetos pertenecientes a la clase Factura con objetos pertenecientes a la clase Recibos de Pago. Es claro que los objetos pertenecen a jerarquias de herencia diferentes, e incluso en el segundo caso se requiere comparar objetos pertenecientes a clases diferentes. En este tipo de situaciones el poder establecer el compromiso de ofrecer un comportamiento toma particular valor. La solución a situaciones como la presentada en el párrafo anterior, no se puede plantear en términos de crear una nueva clase que contenga estos servicios en forma abstracta, y hacer que las clases mencionadas hereden de ella, ya que pertenecen a jerarquías de herencia diversas, y .NET no permite la herencia múltiple. ¿Así que se deben manejar en forma aislada? No, pues como una forma de dar respuesta a este tipo de situaciones se cuenta con una estructura denominada Interfaz, la cual sólo puede contener el encabezado de métodos y propiedades, es decir lo necesario para establecer el compromiso de prestar unos ciertos servicios, sin importar la forma en la cual ellos se implementen. Este mecanismo, como ya se dijo, sólo establece el compromiso de que ciertos servicios se prestarán, sin embargo no dice qué se hará para prestarlos, para ello se requiere que las clases implementen esta estructura y le den cuerpo a estos métodos, de manera que efectivamente presten el servicio. Por lo tanto las interfaces son estructuras que permiten ofrecer un cierto servicio, pero son las clases que la implementan las encargadas de dar cuerpo a estos servicios y por tantos son los objetos de estas clases, quienes están en capacidad de prestar dichos servicios. 1.1 Definición Al definir una interfaz sólo se trabaja con encabezados, definiendo el encabezado de ella misma y el encabezado de cada uno de los servicios que se deberán implementar. Material .NET Parte Dos -Algoritmos para Telemática Elaborado por Luz E. Jiménez - Universidad Icesi 1 1.1.1 Encabezado El encabezado contiene información sobre la interfaz, los detalles se presentan a continuación: • modificador de acceso: Indica qué clases conocerán la existencia de la interfaz que se está definiendo. Si se quiere que la conozcan todas las clases, se debe utilizar el modificador de acceso public, pero si se quiere que sólo la conozcan las clases que comparten el espacio de nombres con ella se debe emplear el modificador de acceso internal, este último se asume si se omite. • interface: Es una palabra reservada e indica que lo que se está definiendo es una interfaz. • nombre: Es el nombre que se le quiere dar a la interfaz, por convención se recomienda que empiece con la letra i en mayúscula (I), y su nombre de una idea del tipo de compromiso que se adquiere al implementar esta interfaz. • : NombreDeLaInterfazPadre: Esta sintaxis se emplea si la interfaz que se está definiendo va a heredar de una, o varias, interfaces existentes. A diferencia de las clases, las interfaces admiten la herencia múltiple así que cada interfaz puede heredar de tantas interfaces como se requiere, y basta separarlas con una coma ( , ). 1.1.2 El cuerpo. El cuerpo de la interfaz: después de crear el encabezado, se empieza a definir su contenido es decir el encabezado de los elementos que requiere que la interfaz contenga. Estos elementos, como ya se dijo, pueden ser de estos tipos: • Propiedades • Métodos Ahora se entrará en detalle respecto a los ítems de la lista anterior: Propiedades: Aquí se presenta el compromiso de ofrecer una propiedad determinada, y se específica si contendrá get, set o ambos. Su encabezado debe contener: • modificador de acceso: NO se puede asociar un modificador de acceso a la propiedad en su definición en la interfaz, pero las clases que la implementen tendrán que emplear el modificador de acceso public, de lo contrario se considerará que se está definiendo una nueva propiedad. • indicador de pertenencia: En una interfaz no se pueden definir propiedades pertenecientes a la clase, sólo a objetos. Material .NET Parte Dos -Algoritmos para Telemática Elaborado por Luz E. Jiménez - Universidad Icesi 2 • tipo: para cada propiedad debe indicarse qué tipo de información puede recibir o devolver, este tipo no puede ser modificado en la clase, o se considerará que se está definiendo una nueva propiedad. • nombre: las propiedades deben tener un nombre, el cual debe iniciar con mayúscula. • métodos asociados: como se mencionó anteriormente aquí se debe definir si la propiedad contará con el método get y el método set o ambos. Métodos: son los servicios que se comprometerán a prestar los objetos pertenecientes a las clases que implementen la interfaz que se está definiendo. Dado que en C# hay tres tipos de métodos, deben hacerse algunas precisiones al respecto: • constructores: estos son los métodos que construyen los objetos de la clase a la cual pertenecen, o sea que NO tiene ningún sentido definir el encabezado de un constructor en una interfaz, además porque el constructor tiene que llamarse igual a la clase. • Main(string args[]): este método pertenece a la clase, y en las interfaces no pueden darse encabezado a métodos que vayan a pertenecer a la clase, sólo a sus instancias. • métodos definidos por usuarios: Son los métodos que permiten definir qué servicios se comprometerán a prestar las clases que implementen esta interfaz. Al definir el encabezado de estos métodos deben tenerse en cuenta los siguientes elementos: ¾ modificador de acceso: Igual que a las propiedades, no puede asignarse ningún modificador de acceso a un método al definirlo en la interfaz, pero las clases que implementen dicha interfaz deben emplear el modificador public, de lo contrario se interpretará como la definición de un nuevo método. ¾ indicador de modificación: Tal como se ha mencionado, en las interfaces los métodos no pueden contener cuerpo sólo el encabezado, es decir todos los métodos definidos en interfaces son abstractos, sin necesidad de emplear la palabra reservada abstract. ¾ indicador de pertenencia: las interfaces sólo definen métodos asociados a los objetos, así que este indicador tiene que ser omitido, pues static no es admitido. ¾ tipo de retorno: debe indicarse el tipo a retornar. ¾ nombre: los métodos tienen que tener un nombre. ¾ parámetros: al definir la interfaz, debe indicarse el tipo y orden de los parámetros que el método recibirá. Material .NET Parte Dos -Algoritmos para Telemática Elaborado por Luz E. Jiménez - Universidad Icesi 3 1.2 Interfaz bandera. Al iniciar este tema, el de interfaces, se mencionó que se emplean para establecer el compromiso de prestar una serie de servicios, es decir tener un comportamiento previamente definido. Sin embargo hay otro caso en el cual las interfaces son muy empleadas, es cuando se requiere definir que un cierto método puede recibir objetos de diversas clase, pertenecientes aún a jerarquías de herencia diversas, en este caso la forma más sencilla de lograr esto es hacer que todas las clases implementen una interfaz denominada “bandera”, y en el encabezado del método indicar que se recibirá uno o varios objetos pertenecientes a clases que implementen esta interfaz. Es decir una interfaz bandera indica que esa clase requiere ser identificada como apta para algo, pero no necesariamente compromete un comportamiento. 1.3 Ejemplos de situaciones en las cuales se emplean interfaces. Para ilustrar la utilización de interfaces se presentará una situación, el análisis hecho y por último el código. Interfaces como una forma de herencia múltiple. Esta es una biblioteca pública, en la cual se manejan dos tipos de productos, obras literarias y películas. Aquí se consideran obras literarias tanto las revistas como los libros, de ellas requerimos el nombre, la editorial; sin son revistas también queremos conocer su número, el volumen y la fecha de edición, además de los artículos que se incluyen en ese número, de los cuales necesitamos el nombre, el tema, una breve descripción y su autor o autores. Pero si son libros lo que se requiere saber es a qué edición pertenecen, el autor o autores y la fecha en la cual fue publicado. De las películas requerimos conocer el nombre, el género, la clasificación, la fecha de lanzamiento y un indicador de si es estreno o no. Los formatos en los cuales las tenemos son cinta de VHS, en las cuales se guarda una sola película, a la cual de acuerdo al género y la clasificación se le asigna la edad para venta, pues las películas de VHS se venden, no se prestan. El otro formato es DVD, en el cual podemos guardar varias películas, y para este formato también se debe calcular la edad de préstamo de acuerdo al género y la clasificación de la película más fuerte que contenga este formato. Como ya se mencionó las películas en VHS están para la venta, y las películas en DVD, al igual que las obras literarias, están para préstamos. Para cada Material .NET Parte Dos -Algoritmos para Telemática Elaborado por Luz E. Jiménez - Universidad Icesi 4 préstamo debemos contar con una forma de registrarlo, guardar la devolución y calcular la multa, en caso de que se requiera. Diagrama de Clases: ObraLiteraría IPrestable string nombre string editorial bool prestar() bool recibir() double multa() ObraLiteraria(string, string) Nombre { set, get } Editorial { set, get } Revista int número int volumen string fechaEdición Libro int edición string fechaPublicación Revista(string,string,int,int,string) Número { set, get } Volumen { set, get } FechaEdición { set, get } bool incluirArtículo(Artículo) Artículo obtenerArtículo(string,string) Libro(string,string,int,string) Libro(string,string,int,string,int) Edición { set, get } FechaPublicación { set, get } bool incluirAutor(Autor) Autor buscarAutor(string) DVD string estado VHS string estado DVD(string) Estado { set, get} int edad() VHS(string) Estado { set, get} * Película string nombre string género string clasificación string fecha bool estreno * 1 * * Artículo string nombre string tema string descripción * Artículo(string,string,string) Artículo(string,string,string,int ) Nombre { set, get } Tema { set, get } Descripción { set, get } bool incluirAutor(Autor) * Autor string nombre string especialidad Autor(string,string) Nombre { set, get } Especialidad{ set, get * * Película(string,string,string, string,bool) Nombre { set, get } Género { set, get } Clasificación { set, get } Fecha { set, get } Estreno { set, get } 1 Código: // definición de la clase ObraLiteraria. using System; using interfaces; namespace ítems { public class ObraLiteraria : IPrestable { string nombre; string editorial; string estado; public string Nombre { set { nombre = value; } get { return nombre; } } public string Editorial { set { editorial = value; } get { Material .NET Parte Dos -Algoritmos para Telemática Elaborado por Luz E. Jiménez - Universidad Icesi 5 return editorial; } } public string Estado { set { estado = value; } get { return estado; } } public ObraLiteraria(string newNombre, string newEditorial) { nombre = newNombre; editorial = newEditorial; estado = "disponible"; } public bool prestar() { if (estado.Equals("disponible")){ estado = "prestado"; // . . . return true; } return false; } public bool recibir() { if (estado.Equals("prestado")) { estado = "disponible"; // . . . return true; } return false; } public double multa() { double valorMulta = 0; // . . . return valorMulta; } } } // definición de la clase Revista. using System; using System.Collections; namespace ítems { public class Revista : ObraLiteraria { int número; int volumen; string fechaEdición; ArrayList misArtículos; public int Número { get { return número; } } public int Volumen { get { return volumen; } } public string FechaEdición { get { return fechaEdición; } } public ArrayList MisArtículos { set { misArtículos = value; } get { return misArtículos; } } public bool incluirArtículo(Artículo elArtículo) { if (misArtículos.Contains(elArtículo)) return false; misArtículos.Add(elArtículo); Material .NET Parte Dos -Algoritmos para Telemática Elaborado por Luz E. Jiménez - Universidad Icesi 6 return true; } public Artículo obtenerArtículo(string elNombre, string elTema){ foreach (object uno in misArtículos) { Artículo dos = (Artículo)uno; if (dos.Nombre == elNombre && dos.Tema == elTema) return dos; } return null; } public Revista(string newNombre, string newEditorial, int newNúmero, int newVolumen, string newFecha) : base(newNombre, newEditorial) { número = newNúmero; volumen = newVolumen; fechaEdición = newFecha; } public override int GetHashCode() { return (this.Nombre.GetHashCode() + número * 2 + volumen * 3); } public override bool Equals(object obj) { return this.GetHashCode() == obj.GetHashCode(); } } } // definición de la clase Libro. using System; namespace ítems { public class Libro : ObraLiteraria { int edición; string fechaPublicación; Autor[] losAutores; public int Edición{ set{ edición = value; } get{ return edición; } } public string FechaPublicación { set { fechaPublicación = value; } get { return fechaPublicación; } } public Autor[] LosAutores { set { losAutores = value; } get { return losAutores; } } public Libro(string newNombre, string newEditorial, int newEdición, string newFecha) : base( newNombre, newEditorial) { edición = newEdición; fechaPublicación = newFecha; losAutores = new Autor[1]; } public Libro(string newNombre, string newEditorial, int newEdición, string newFecha,int cantidad) : base(newNombre, newEditorial){ edición = newEdición; fechaPublicación = newFecha; losAutores = new Autor[cantidad]; } public override int GetHashCode(){ return this.Nombre.GetHashCode(); } public override bool Equals(object obj){ return this.GetHashCode() == obj.GetHashCode(); } Material .NET Parte Dos -Algoritmos para Telemática Elaborado por Luz E. Jiménez - Universidad Icesi 7 public bool incluirAutor(Autor elNuevo) { // retornará false si ya está registrado o si el arreglo ya está lleno. foreach(Autor uno in losAutores) { if (uno.Equals(elNuevo)) { return false; } } for (int contador = 0; contador < losAutores.Length; contador++) { if (losAutores[contador] == null) { losAutores[contador] = elNuevo; return true; } } return false; } public Autor buscarAutor(string elNombre) { foreach(Autor uno in losAutores) { if (uno.Nombre.Equals(elNombre)) { return uno; } } return null; } } } // definición de la clase Artículo. using System; namespace ítems { public class Artículo { string nombre; string tema; string descripción; Autor[] losAutores; public string Nombre { set { nombre = value; } get { return nombre; } } public string Tema { set { tema = value; } get { return tema; } } public string Descripción { set { descripción = value; } get { return descripción; } } public Autor[] LosAutores { set { losAutores = value; } get { return losAutores; } } public Artículo(string newNombre, string newTema, string newDescripción) { nombre = newNombre; tema = newTema; descripción = newDescripción; losAutores = new Autor[1]; } public Artículo(string newNombre, string newTema, string newDescripción, int cantidad) { nombre = newNombre; tema = newTema; descripción = newDescripción; Material .NET Parte Dos -Algoritmos para Telemática Elaborado por Luz E. Jiménez - Universidad Icesi 8 losAutores = new Autor[cantidad]; } public override bool Equals(object unArtículo) { return this.GetHashCode() == unArtículo.GetHashCode(); } public override int GetHashCode() { return ( (this.tema.GetHashCode()*2) + (this.nombre.GetHashCode()) ); } public bool incluirAutor(Autor elNuevo) { // retornará false si ya está registrado o si el arreglo ya está lleno. foreach(Autor uno in losAutores) { if (uno.Equals(elNuevo)) { return false; } } for (int contador = 0; contador < losAutores.Length; contador++){ if (losAutores[contador] == null){ losAutores[contador] = elNuevo; return true; } } return false; } public Autor buscarAutor(string elNombre) { foreach(Autor uno in losAutores) { if (uno.Nombre.Equals(elNombre)) { return uno; } } return null; } } } // definición de la clase Autor. using System; namespace ítems { public class Autor { string nombre; string especialidad; public string Nombre{ set { nombre = value; } get{ return nombre; } } public string Especialidad { set { especialidad = value; } get { return especialidad; } } public Autor(string newNombre, string newEspecialidad) { this.nombre = newNombre; this.especialidad = newEspecialidad; } public override int GetHashCode(){ return Nombre.GetHashCode(); } public override bool Equals(object obj){ return this.GetHashCode() == obj.GetHashCode(); } } } // definición de la clase DVD. using System; using System.Collections; Material .NET Parte Dos -Algoritmos para Telemática Elaborado por Luz E. Jiménez - Universidad Icesi 9 using interfaces; namespace ítems { public class DVD : IPrestable { static int elConsecutivo; int consecutivo; string estado; ArrayList lasPelículas; public string Estado{ set{ estado = value; } get{ return estado; } } public ArrayList LasPelículas{ set{ lasPelículas = value; } get{ return lasPelículas; } } static DVD () { elConsecutivo = 0; } public DVD(ArrayList newPelículas) { consecutivo = DVD.elConsecutivo++; estado = "diponible"; lasPelículas = newPelículas; } public override int GetHashCode() { return this.consecutivo; } public override bool Equals(object obj){ return this.GetHashCode() == obj.GetHashCode(); } public bool incluirPelícula(Película laPelícula) { if (lasPelículas.Contains(laPelícula)) return false; lasPelículas.Add(laPelícula); return true; } public Película obtenerPelícula(string elNombre) { foreach (object uno in lasPelículas) { Película dos = (Película)uno; if (dos.Nombre == elNombre) return dos; } return null; } public bool prestar() { if (estado.Equals("disponible")) { estado = "prestado"; // . . . return true; } return false; } public bool recibir() { if (estado.Equals("prestado")) { estado = "disponible"; // . . . return true; } return false; } public double multa() { double valorMulta = 0; // . . . return valorMulta; } Material .NET Parte Dos -Algoritmos para Telemática Elaborado por Luz E. Jiménez - Universidad Icesi 10 } } // definición de la clase VHS. using System; namespace ítems { public class VHS { static int elConsecutivo; int consecutivo; string estado; Película laPelícula; public string Estado { set { estado = value; } get { return estado; } } public Película LaPelícula { set { laPelícula = value; } get { return laPelícula; } } static VHS() { elConsecutivo = 0; } public override int GetHashCode() { return this.consecutivo; } public override bool Equals(object obj) { return this.GetHashCode() == obj.GetHashCode(); } public VHS(Película newPelícula) { consecutivo = VHS.elConsecutivo++; estado = "en la tienda"; laPelícula = newPelícula; } } } // definición de la clase Película. using System; namespace ítems { public class Película { string nombre; string género; string clasificación; string fecha; bool estreno; public string Nombre { set{ nombre = value; } get{ return nombre; } } public string Género { set { género = value; } get { return género; } } public string Clasificación { set { clasificación = value; } get { return clasificación; } } Material .NET Parte Dos -Algoritmos para Telemática Elaborado por Luz E. Jiménez - Universidad Icesi 11 public string Fecha { set { fecha = value; } get { return fecha; } } public bool Estreno { set { estreno = value; } get { return estreno; } } public Película(string newNombre, string newGénero, string newClasificación, string newFecha, bool newEstreno){ nombre = newNombre; género = newGénero; clasificación = newClasificación; fecha = newFecha; estreno = newEstreno; } public override int GetHashCode(){ return this.nombre.GetHashCode(); } public override bool Equals(object obj){ return this.GetHashCode() == obj.GetHashCode(); } } } // definición de la interfaz IPrestable. using System; namespace interfaces { public interface IPrestable { bool prestar(); bool recibir(); double multa(); } } Interfaces como bandera. En este supermercado vendemos diversos productos, pero por ahora centrémonos en los alimentos y la ropa, para poder explicarle como es que se acomodan las cosas. Los alimentos se agrupan de acuerdo a si son carnes, frutas o verduras, lácteos o rancho y licores y tienen IVA sólo si son carnes frías o pertenecen a la sección de rancho y licores. La ropa se separa como ropa de hombre, mujer o niño y se le cobra IVA sólo si está catalogada como ropa de lujo. En la caja se registra el producto y si está marcado como producto con IVA se calcula, del precio de venta, cuánto corresponde al IVA. El porcentaje para calcular el IVA, por fortuna está unificado y corresponde al 10%. Diagrama de Clases: Material .NET Parte Dos -Algoritmos para Telemática Elaborado por Luz E. Jiménez - Universidad Icesi 12 Producto string código string nombre double precioVenta string proveedor Liquidador Producto(string,string,string) Código { set, get } Nombre { set, get } PrecioVenta { set, get} Proveedor { set, get} Ropa Alimentos string marca string talla string género int edad string calidad double peso double valorKilogramo Alimentos(string string,string, string,double,double) Calidad { set, get } Peso { set, get } ValorKilogramo { set, get } Lácteos string fechaEmpaque string fechaVencimiento bool requiereRefrigeración Lácteos (string, string, string, string, double, double, string, string, bool) FechaEmpaque { set, get } FechaVencimiento { set, get } RequiereRefrigeración{ set, get } Ropa (string, string, string, strin, string, string, int) Marca { set, get } Talla { set, get } Género { set, get } Edad { set, get } Carnes string tipo string fechaEmpaque string fechaVencimiento Carne (string, string, string, string, double, double, string, string, string) Tipo { set, get } FechaEmpaque { set, get } FechaVencimiento { set, get } RanchoLicores DeLujo string marca string tamaño string posiciónAlmacenamiento RanchoLicores (string, string, string, string, double, double, string, string, string) Marca { set, get } Tamaño { set, get } PosiciónAlamacenamiento { set, get } IIva Frías Código: // definición de la clase Producto. using System; namespace Supermercado { public class Producto { string código; string nombre; double precioVenta; string proveedor; public string Código { set { código = value; } get { return código; } } public string Nombre { set { nombre = value; } get { return nombre; } } Material .NET Parte Dos -Algoritmos para Telemática Elaborado por Luz E. Jiménez - Universidad Icesi 13 public double PrecioVenta { set { precioVenta = value; } get { return precioVenta; } } public string Proveedor { set { proveedor = value; } get { return proveedor; } } public Producto(){ } public Producto(string newCódigo, string newNombre, double newPrecio, string newProveedor) { código = newCódigo; nombre = newNombre; precioVenta = newPrecio; proveedor = newProveedor; } } } // definición de la interfaz IIva. using System; namespace interfaces { public interface IIva { // observe que NO define ningún método. } } // definición de la clase Alimentos. using System; namespace Supermercado { public class Alimentos : Producto { string calidad; double peso; double valorKilogramo; public string Calidad { set { calidad = value; } get { return calidad; } } public double Peso { set { peso = value; } get { return peso; } } public double ValorKilogramo{ set{ valorKilogramo = value; } get{ return valorKilogramo; } } public Alimentos(string newCódigo, string newNombre, double newPrecio, string newProveedor, string newCalidad, double newPeso, double newValor) : base(newNombre, newProveedor, newPrecio, newCalidad) { calidad = newCalidad; peso = newPeso; valorKilogramo = newValor; } public Alimentos() { } } } // definición de la clase Lácteos. using System; namespace Supermercado { public class Lácteos:Alimentos { Material .NET Parte Dos -Algoritmos para Telemática Elaborado por Luz E. Jiménez - Universidad Icesi 14 string fechaEmpaque; string fechaVencimiento; bool requiereRefrigeración; public string FechaEmpaque { set { fechaEmpaque = value; } get { return fechaEmpaque; } } public string FechaVencimiento { set { fechaVencimiento = value; } get { return fechaVencimiento; } } public bool RequiereRefrigeración { set { requiereRefrigeración = value; } get { return requiereRefrigeración; } } public Lácteos(string newCódigo, string newNombre, double newPrecio, string newProveedor, string newCalidad, double newPeso, double newValor, string newEmpaque, string newVencimiento, bool newRefrigeración) : base (newCódigo, newNombre, newPrecio, newProveedor, newCalidad, newPeso, newValor) { fechaEmpaque = newEmpaque; fechaVencimiento = newVencimiento; requiereRefrigeración = newRefrigeración; } } } // definición de la clase Carnes. using System; namespace Supermercado { public class Carnes : Alimentos { string tipo; string fechaEmpaque; string fechaVencimiento; public string Tipo { set { tipo = value; } get { return tipo; } } public string FechaEmpaque { set { fechaEmpaque = value; } get { return fechaEmpaque; } } public string FechaVencimiento { set { fechaVencimiento = value; } get { return fechaVencimiento; } } public Carnes(string newCódigo, string newNombre, double newPrecio, string newProveedor, string newCalidad, double newPeso, double newValor, string newTipo, string newEmpaque, string newVencimiento): base (newCódigo, newNombre, newPrecio, newProveedor, newCalidad, newPeso, newValor) { tipo = newTipo; fechaEmpaque = newEmpaque; fechaVencimiento = newVencimiento; } public Carnes(){} } } Material .NET Parte Dos -Algoritmos para Telemática Elaborado por Luz E. Jiménez - Universidad Icesi 15 // definición de la clase Frías. // observe cuidadosamente la definición de esta clase, ¿ en qué difiere de su clase padre ?. using System; using interfaces; namespace Supermercado { public class Frías : Carnes, IIva { public Frías (string newCódigo, string newNombre, double newPrecio, string newProveedor, string newCalidad, double newPeso, double newValor, string newTipo, string newEmpaque, string newVencimiento) : base (newCódigo, newNombre, newPrecio, newProveedor, newCalidad, newPeso, newValor, newTipo, newEmpaque, newVencimiento) { } public Frías(){} } } // definición de la clase RachoLicores. using System; namespace Supermercado { public class RanchoLicores : Alimentos { string marca; string tamaño; string posiciónAlmacenamiento; public string Marca { set { marca = value; } get { return marca; } } public string Tamaño { set { tamaño = value; } get { return tamaño; } } public string PosiciónAlmacenamiento { set { posiciónAlmacenamiento = value; } get { return posiciónAlmacenamiento; } } public RanchoLicores (string newCódigo, string newNombre, double newPrecio, string newProveedor, string newCalidad, double newPeso, double newValor, string newMarca, string newTamaño, string newPosición) : base (newCódigo, newNombre, newPrecio, newProveedor, newCalidad, newPeso, newValor) { marca = newMarca; tamaño = newTamaño; posiciónAlmacenamiento = newPosición; } } } // definición de la clase Ropa. using System; namespace Supermercado { public class Ropa : Producto { string marca; string talla; string género; int edad; public string Marca{ set { marca = value; } get { return marca; } } public string Talla { set { Material .NET Parte Dos -Algoritmos para Telemática Elaborado por Luz E. Jiménez - Universidad Icesi 16 talla = value; } get { return talla; } } public string Género { set { género = value; } get { return género; } } public int Edad { set { edad = value; } get { return edad; } } public Ropa(){} public Ropa (string newCódigo, string newNombre, double newPrecio, string newProveedor, string newMarca, string newTalla, string newGénero, int newEdad) : base(newCódigo, newNombre, newPrecio, newProveedor) { marca = newMarca; talla = newTalla; género = newGénero; edad = newEdad; } } } // definición de la clase Frías. // observe cuidadosamente la definición de esta clase, ¿ en qué difiere de su clase padre ?. using System; using interfaces; namespace Supermercado { public class DeLujo : Ropa, IIva { public DeLujo(string newCódigo, string newNombre, double newPrecio, string newProveedor, string newMarca, string newTalla, string newGénero, int newEdad) : base (newCódigo, newNombre, newPrecio, newProveedor, newMarca, newTalla, newGénero, newEdad) { } public DeLujo() { } } } // definición de la clase Liquidador. // observe cuidadosamente esta clase, ¿ encuentra alguna razón para la creación de la interfaz IIva ?. using System; using interfaces; namespace Supermercado.caja { public class Liquidador { public static double liquidaCompra(Producto elProducto, int laCantidad){ IIva conIva = elProducto as IIva; if (conIva == null){ return compraSinIva(elProducto, laCantidad); } return compraConIva(elProducto, laCantidad); } public static double compraSinIva(Producto elProducto, int laCantidad) { return elProducto.PrecioVenta * laCantidad; } public static double compraConIva(Producto elProducto, int laCantidad) { return (elProducto.PrecioVenta * laCantidad) * 1.10 ; } } } Material .NET Parte Dos -Algoritmos para Telemática Elaborado por Luz E. Jiménez - Universidad Icesi 17 2 Agrupación de clases por funcionalidad: namespaces. Tal como se ha mencionado antes .NET permite agrupar las clases mediante un mecanismo de organización denominado namespaces, lo que se ha presentado como espacio de nombres a lo largo de este material, el cual es completamente independiente de la herencia y muy similar a los directorios que emplea el sistema operativo. De hecho, cuando se define un espacio de nombres, .NET pide al sistema operativo que cree una carpeta para él, y todo lo que se crea en él quedará ubicado, físicamente, en esa carpeta. Así que los espacios de nombres no son más que estructuras que permiten organizar las clases de acuerdo a criterios definidos por quien programa, aunque generalmente se utilizan para agrupar por funcionalidad. Tal como se dijo al inicio de este punto, los espacios de nombres son una estructura de .NET, así que en un mismo espacio de nombres pueden ubicarse clases elaboradas desde C#.NET, C++.NET, ADO.NET o cualquiera de las otras herramientas de programación que proporciona el ambiente. Estas estructuras son completamente independientes unas de otras, y si al definir una clase se requiere emplear clases ubicadas en espacios de nombres diferentes, tiene que emplearse la palabra reservada using seguida del nombre de cada uno de los espacios de nombres en que se encuentran ubicadas dichas clases. De hecho hasta ahora se ha utilizado esta cláusula asociada a System, y a UtilidadesVarias, que son los espacios de nombres que contienen las clases Console y Lecturas que se han empleado para hacer operaciones a través de la consola. Además, y referente a lo mencionado al final del párrafo anterior, debe hacerse claridad respecto al hecho de que el namespace System, es fundamental en este ambiente, y de hecho nada podría hacerse sin él, pues es el que contiene la definición de los struct que hemos empleado como tipos básicos (int, double, bool, char, etc.), la definición de las clases Array y string, entre muchas otros tipos básicos para el trabajo en este entorno. Ejercicio Resuelto: Ilustre, mediante un gráfico, cómo quedaría el espacio de nombres figurasgeométricas, que contiene las clases que se presentaron en el ejemplo que se empleó durante la explicación referente a las clases. Respuesta: Material .NET Parte Dos -Algoritmos para Telemática Elaborado por Luz E. Jiménez - Universidad Icesi 18 System ... Triángulo Cuadrado TriánguloRectángulo ... Círculo FigurasGeométricas Y el nombre completo de cada una de las clases contenidas en este espacio de nombres sería: • • • • • FigurasGeométricas.FigurasGeométricas FigurasGeométricas.Triángulo FigurasGeométricas.Cuadrado FigurasGeométricas.TriánguloRectángulo FigurasGeométricas.Círculo. Nótese que la última parte de la respuesta no se había presentado hasta este momento, y es que el nombre completo de cada una de los tipos que admite .NET incluye el nombre del espacio de nombres que la contiene, siendo el nombre completo: NombreDelNamespace.NombreDelTipo razón por la cual es posible la creación de varios tipos con igual nombre, siempre y cuando estén en espacios de nombres diferentes. 2.1 Espacios de nombres anidados. Tal como se ha mencionado, los espacios de nombres se emplean con mucha frecuencia para agrupar clases que tienen una funcionalidad similar, pero bien puede ocurrir que en el proceso de especialización empiecen a surgir clases que se refieran a algo mucho más específico, de esta forma bien podría requerirse crear un nuevo espacio de nombres que esté contenido dentro del espacio original. De hecho .NET permite tantos anidamientos en los espacios de nombres como se quiera tener, sin ningún inconveniente en ello. Pero debe tenerse Material .NET Parte Dos -Algoritmos para Telemática Elaborado por Luz E. Jiménez - Universidad Icesi 19 presente que los espacios de nombre, sin importar que estén anidados o no, son completamente independientes unos de otros. Ejercicio Resuelto: Ilustre, mediante un gráfico, cómo quedaría el espacio de nombres FigurasGeométricas, si se le agrega un espacio de nombres que contenga todo lo relacionado con lo triángulos. Respuesta: FigurasGeométricas Cuadrado A FigurasGeométricas Círculo TriánguloRectángulo Triángulo TriánguloEquilátero TriánguloIsósceles Triángulos Y el nombre completo de cada una de las clases contenidas en este espacio de nombres sería: • • • • • • • FigurasGeométricas.FigurasGeométricas FigurasGeométricas.Cuadrado FigurasGeométricas.Círculo. FigurasGeométricas.Triángulos.Triángulo FigurasGeométricas.Triángulos.TriánguloRectángulo FigurasGeométricas.Triángulos.TriánguloEquilátero FigurasGeométricas.Triángulos.TriánguloIsósceles De manera que si alguna persona necesita emplear la clase Cuadrado y la clase Triángulo en la clase que va a construir, el encabezado de esta nueva clase debería incluir: using FigurasGeométricas; using FigurasGeométricas.Triángulos; Aquí se considera importante hacer énfasis en que los espacios de nombres son completamente independientes entre sí, incluso si un espacio de nombres contiene a otro. Por esta razón, en el ejemplo anterior, no es suficiente con emplear el using con FigurasGeométricas, para que estén disponibles las clases contenidas en el espacio de nombres Triángulos. Material .NET Parte Dos -Algoritmos para Telemática Elaborado por Luz E. Jiménez - Universidad Icesi 20 2.2 El using. Tal como se ha mencionado hasta el momento el using se emplea para indicar que en la clase que se está construyendo se emplearán clases contenidas en un espacio de nombres determinado. Ahora se quieren hacer algunas precisiones al respecto: • Tal como se ha dicho, los espacios de nombres son independientes unos de otros, de manera que si se requiere hacer referencias a clases ubicadas en diversos espacios de nombres, debe haber tantos using como espacios de nombres se deseen usar. • El using concede acceso a todo lo contenido en el espacio de nombres que se asocie a él, es decir si se requiere emplear una clase y un struc contenidos en un espacio de nombres determinado basta con dar el using con este nombre asociado y estos estarán disponibles. • El using no es indispensable, puesto que se puede omitir y cada vez que se requiera hacer referencia a una clase ubicada en otro espacio de nombres, se puede dar su nombre completo, es decir el nombre del espacio de nombres seguido del punto y el nombre de la clase. Dado que esto último puede resultar realmente incómodo se recomienda no hacerlo y emplear el using. 2.3 El acceso físico. Tal como se mencionó al empezar a tratar este tema, cada espacio de nombres está asociado a una carpeta manejada por el sistema operativo. Cuando se emplea el using se está solucionando un problema de tiempo de compilación; pero cuando se está en tiempo de ejecución esta información no es suficiente, pues .NET no sabe en qué ubicación física se encuentran las carpetas que se referencian. Por lo anterior cada proyecto tiene un componente que se denomina References, en el cual el proyecto contiene las direcciones físicas de aquellos componentes a los cuales hace referencia. Tal como se muestra en la siguiente gráfica: Material .NET Parte Dos -Algoritmos para Telemática Elaborado por Luz E. Jiménez - Universidad Icesi 21 Como puede verse en esta imagen, las referencias pertenecen a cada proyecto, y dicen a qué espacios de nombres puede hacer referencia él. En este caso el proyecto “ConsoleApplication2” no sabría como hacer referencia al proyecto “Utilidades Varias”, para solucionar esto se debe indicar que se desea agregar una nueva referencia, haciendo “clic” derecho sobre este ítem, ante lo cual se presentará la siguiente ventana: Material .NET Parte Dos -Algoritmos para Telemática Elaborado por Luz E. Jiménez - Universidad Icesi 22 En ella debe seleccionar el proyecto, y asegurarse de que aparezca en la ventana inferior. Una vez hecho esto debe seleccionarse el botón Aceptar, después de lo cual el componente “References” debe mostrar una referencia a “UtilidadesVarias”, tal como se muestra en la siguiente imagen. Otra revisión que debe hacerse es verificar que la solución tiene configurado como proyecto de inicio, aquel proyecto que contiene el método Main(), lo cual se verifica a través de las propiedades de la solución. Material .NET Parte Dos -Algoritmos para Telemática Elaborado por Luz E. Jiménez - Universidad Icesi 23 3 Manejo de errores en .NET. Dado que .NET es un ambiente completamente orientado a objetos, el manejo de situaciones de error, o situaciones no deseadas, se hace mediante objetos. Aunque realmente lo que se hace es lanzar un objeto, perteneciente a la clase Exception o a una de sus hijas, el cual contiene información referente al tipo de situación que se presentó. Este objeto tiene que ser “atrapado” dentro del método en que fue lanzado y las operaciones de corrección o finalización que deban llevarse, o sea que los errores no pueden propagarse por diversos métodos. Esto puede parecer confuso, pero se discutirá en detalle a lo largo de este numeral. 3.1 Lanzar una excepción. Cuando ocurre una situación excepcional y se decide lanzar una excepción, basta con indicar a .NET que se quiere lanzar (throw) un objeto perteneciente a una clase especial (Exception), el cual llevará información referente al tipo de error ocurrido. En este punto es muy importante hacer énfasis en que el objeto a lanzar tiene que pertenecer a la clase System.Exception o a alguna clase que descienda de ella, pues en caso contrario no se admitirá que se asocie a la palabra reservada throw, tal como se ilustra en la siguiente gráfica: Aquí es importante saber qué propiedades y métodos se puede emplear en asocio a los objetos pertenecientes a la clase Exception: Material .NET Parte Dos -Algoritmos para Telemática Elaborado por Luz E. Jiménez - Universidad Icesi 24 Propiedades: • • • HelpLink: Retorna una cadena que contiene una dirección (URL), del lugar en el cual se encuentra más información referente a la excepción, o un archivo que contiene mayor información (URN). InnerException: Devuelve el objeto Exception, que causó está Exception. Es importante resaltar que si se generó una cadena de excepciones esta propiedad retorna la última, pero si sólo se ha generado una excepción esta propiedad retornará null, por tanto deberá recurrirse al método GetBaseException(). Message: Retorna una cadena que contiene una explicación sobre el error ocurrido, esta cadena debe ser pasada al constructor como parámetro. Generalmente se construyen las excepciones, justo al momento de lanzarlas, pero esto no es necesario puesto que puede lanzarse una excepción previamente construida, de manera que el siguiente código es válido: public static void unGato(int dividendo, int divisor) { try { if (divisor == 0) { Exception miExcepción = new ArithmeticException("divisor no válido"); throw miExcepción; } } } • • • • Source: Retorna una cadena con el nombre del objeto o la aplicación que causó el error. StackTrace: Muestra el estado de la pila al momento de generarse la excepción. Es importante tener presente que esta pila sólo contiene los nombres de los métodos que se han ejecutado hasta ese momento. TargetSite: Retorna un objeto System.Reflection.MethodBase, el cual contiene una referencia al método desde el cual se lanzó la excepción. Hresult: Esta es una propiedad protegida, a diferencia de las anteriores que son públicas, y retorna un valor de entero conformado por tres dígitos, el primero indica la severidad del problema, es decir si es una advertencia o un error, el segundo indica el código del dispositivo con el cual se presentó el problema y el último corresponde al código del error. Métodos: • GetBaseException(): Retorna una referencia a la excepción principal, es decir a la primera excepción generada, y causante de la cadena de excepciones que generó la excepción a la cual se le pide este servicio. Material .NET Parte Dos -Algoritmos para Telemática Elaborado por Luz E. Jiménez - Universidad Icesi 25 3.2 Los bloques Try, Catch y Finally. Dado que el manejo de excepciones rompe el ciclo de ejecución del código, que el mecanismo ha sido ideado para manejar situaciones excepcionales y para el control de errores que no pudieron ser previstos por el programador, se han ideado tres bloques de control los cuales deben estudiarse, y cuya finalidad debe tenerse presente, cuando se trata de controlar las excepciones correctamente. Estos bloques son el bloque try, el boque catch y el bloque finally. El primero de estos bloque se denomina bloque try, ya que es el bloque en el cual va la sentencia throw asociada a la excepción que se lanzará. Ninguna excepción puede ser lanzada por fuera de un bloque de este tipo, es decir este bloque es obligatorio cuando se están manejando excepciones. Este tipo de bloque puede ser definido dentro de cualquiera de los tipos de método que se manejan en este ambiente, es decir constructores, método Main o métodos definidos por el usuario. La forma en la cual se define este método es muy sencilla, pues basta asociar la palabra reservada try, al bloque de código en el cual podrá presentarse la excepción, tal como se ha ilustrado en las imágenes que se han presentado con este tema. El segundo bloque se denomina bloque catch, y tiene como finalidad el indicar qué se debe hacer cuando ocurre una excepción. Este bloque, que en realidad pueden ser varios bloques catch, se puede manejar de dos formas, la primera consiste en indicar en el encabezado del mismo el tipo de excepción que se desea atrapar en este bloque y la segunda en obviando el tipo de excepción que se desea atrapar, en cuyo caso ese bloque atrapará todas las excepciones que se lancen. Este bloque, o bloques, de código no es requerido, pero si se emplea tiene que ir inmediatamente después del bloque try. Estas situaciones se ilustran en el siguiente porción de código: try { if (divisor == 0) { Exception miExcepción = new ArithmeticException("divisor no válido"); throw miExcepción; } if (dividendo / divisor > 100) { throw new Exception(); } return dividendo/divisor; } catch (ArithmeticException e) {// este bloque atrapará una excepción lanzada por división por cero. Console.WriteLine(e.Message); } catch {// este bloque atrapará cualquier otro tipo de excepción. Console.WriteLine("El resultado está por fuera del rango"); Material .NET Parte Dos -Algoritmos para Telemática Elaborado por Luz E. Jiménez - Universidad Icesi 26 } Aquí es importante dejar presente que el orden de los dos bloques catch no habría podido invertirse pues el último catch atrapa todas las excepciones, así que el bloque que atraparía la excepción aritmética se consideraría código no alcanzable, tal como se ilustra en la siguiente imagen: O sea que no pueden ir bloques catch generales antes de bloques catch más específicos, pues se tratará de código no alcanzable y ocasionará error en tiempo de compilación. El último bloque es el bloque finally, y tiene como finalidad el permitir que haya código de ejecución obligatoria; es decir una cierta porción de código que tiene que ejecutarse ocurra la excepción o no. La razón por la cual existe este tipo de bloque es que las instrucciones relacionadas con retornar recursos que el sistema ha asignado a un proceso determinado, no pueden dejar de ejecutarse porque una determinada instrucción no se concluyó. Este bloque no es obligatorio, pero si se emplea debe ir después del try, y del catch si lo hubo. Aquí se debe hacer claridad sobre la obligatoriedad de estos bloques, siempre que se requiera lanzar excepciones, esto tiene que hacerse dentro de un bloque try, si se desea controlar o hacer algo para su manejo, esto debe hacerse en un bloque catch, y si se requiere liberar recursos, lo que se requiera para ello deberá incluirse en un bloque finally; ninguno de estos dos últimos bloques es obligatorio, pero siempre que se crea un bloque try, es necesario incluir por lo menos uno de los dos. 3.3 Consideraciones respecto al manejo de las excepciones. Es importante dejar presente que el manejo de excepciones es una opción que se presenta para situaciones excepcionales, es decir situaciones que no pudieron ser previstas por quien elaboró el programa: una conexión a un dispositivo que no está disponible, o una lectura sobre un archivo que está corrupto, etc. Pero como este mecanismo puede interrumpir la ejecución normal del código, debe manejarse con cuidado, es decir debe tratarse de evitar como mecanismo de validación, y continuar tratando de hacer las validaciones de la manera Material .NET Parte Dos -Algoritmos para Telemática Elaborado por Luz E. Jiménez - Universidad Icesi 27 convencional. El ejemplo que ilustra el uso de varios bloques catch, en el cual se previene una división por cero, perfectamente pudo hacerse con la instrucción if, y no con excepciones, y puede considerarse un buen ejemplo de lo que no debe hacerse con excepciones, sino a través de validaciones normales. Otra consideración importante, en particular para aquellas personas que han trabajado con el lenguaje Java, es que .NET exige que la excepción sea capturada dentro del método en el cual se lanza, o sea que el mecanismo de propagación de excepciones que maneja Java no existe en este ambiente (la palabra reservada throws, que en Java es reservada, aquí no lo es). Material .NET Parte Dos -Algoritmos para Telemática Elaborado por Luz E. Jiménez - Universidad Icesi 28 4 Manejo de colecciones en .NET. Una colección es una agrupación, cuyo tamaño final es desconocido al momento de su creación, la cual contiene elementos que por alguna razón deben guardarse juntos. Dependiendo del tipo de estos elementos, o del orden en el cual se requiera que se guarden, o de algún otro elemento que deba ser tenido en cuenta al momento de definir la forma de agrupación, las colecciones reciben diversos nombres como listas, pilas, colas, árboles, conjuntos, grafos, etc. Dentro de la filosofía de Orientación a Objetos, las colecciones son elementos especiales en la medida en la que son un objeto que va a contener objetos. Al ser un objeto deben contar con una estructura y estar en capacidad de prestar unos servicios; al contener objetos deben ser capaces de contener cualquier tipo de objeto dentro de los existentes en el contexto en el cual se está trabajando. Dentro de .NET hay clases que implementan algunas de las colecciones más conocidas, pero también se cuenta con los elementos necesarios para la creación de nuevas colecciones. Estos elementos se encuentran agrupados en un namespace denominado System.Collections. Generalmente las colecciones se implementan partiendo de un arreglo de objetos de la clase object, dado que al ser object la clase padre de todas en .NET, se garantiza que la colección podrá contener objetos de cualquier clase. Para resolver el que puedan aumentar su tamaño sin restricciones, lo que suele hacerse es que el método que adiciona elementos verifica si el arreglo ya está lleno, en cuyo caso crea un arreglo más grande y traslada los elementos del “viejo arreglo” al “nuevo arreglo” sin que el usuario sufra ningún tipo de pérdida de información, además sí la colección es apuntada por varias referencias este método debe asegurarse de que todas se actualicen. 4.1 La interfaz System.Collections.ICollection. Además de lo mencionado en el párrafo anterior, las colecciones en .NET también deben implementar la interfaz System.Collections.ICollection, la cual hereda de la interfaz System.Collections.IEnumerable, y cuenta con los métodos y propiedades mínimas que debe tener una estructura para ser considerada una colección en este ambiente. Dichos métodos se presentan a continuación: • System.Collections.IEnumerable.GetEnumerator: Este método pertenece a la interfaz IEnumerable, pero al ser esta la interfaz padre de ICollection también debe ser definido por las clases que implementen esta última. Este método está definido para retornar un objeto perteneciente a una de las clases que implementan la interfaz System.Collections.IEnumerator. Retorna un objeto Material .NET Parte Dos -Algoritmos para Telemática Elaborado por Luz E. Jiménez - Universidad Icesi 29 capaz de recorrer la colección de principio a fin, avanzando de uno en uno y estando es capacidad de referenciar al objeto que se encuentra enfrente. El comportamiento, lógico, del enumerador se ilustra en la siguiente gráfica: laColección Posición del enumerador al momento de pedirlo. laColección Material .NET Parte Dos -Algoritmos para Telemática Elaborado por Luz E. Jiménez - Universidad Icesi 30 laColección Este enumerador es particularmente valioso en este ambiente pues es el que permite la utilización de la instrucción foreach, la cual es muy útil al momento de recorrer estructuras de datos. • int System.Collections.ICollection.Count: Esta propiedad, al igual que los elementos que se mencionan a continuación, pertenecen directamente a la interfaz ICollection. De los dos métodos permitidos para las propiedades, get y set, esta propiedad sólo trabaja el método get, y al implementarlo debe retornar la cantidad de elementos contenidos por la colección. • bool System.Collections.ICollection.IsSynchronized: Es una propiedad, e igual que la anterior sólo requiere la implementación del método get. Retorna un valor booleano, el cual indica si el acceso a la colección está sincronizado o no. • object System.Collections.ICollection.SyncRoot: También es una propiedad que sólo requiere la implementación del método get, debe retornar un objeto capaz de manejar la sincronización del acceso a la colección. • void System.Collections.ICollection.CopyTo( System.Array, int ): A diferencia de los elementos mencionados antes, este es un método que debe guardar nuevas referencias a los elementos de la colección, en el arreglo que recibe como primer parámetro y a partir de la posición dada por el índice que recibe como segundo parámetro. 4.2 La interfaz System.Collections.IComparer. Esta interfaz debe ser implementada por las colecciones que requieren conservarse ordenadas. Sólo contiene un método: • int System.Collections.IComparer.Compare( object, object ): Retorna un valor entero que debe ser cero ( 0 ) si los objetos a comparar son iguales, un valor negativo si el primero es menor que el segundo y un valor positivo si el primero es mayor que el segundo. Esta es una convención, es decir no hay Material .NET Parte Dos -Algoritmos para Telemática Elaborado por Luz E. Jiménez - Universidad Icesi 31 nada que fuerce este comportamiento, sin embargo cuando alguien invoque este método, esperará que este sea el comportamiento a obtener. 4.3 La interfaz System.Collections.IEnumerator. Las clases que implementen esta interfaz se comprometen a proporcionar los elementos necesarios para contar con un enumerador, es decir un objeto capaz de recorrer una colección de principio a fin, desplazándose elemento por elemento. Para ellos cuenta con los siguientes métodos y propiedades: • bool System.Collections.IEnumerator.MoveNext: Proporciona el método que permite, al enumerador, desplazarse al siguiente elemento de la colección. Retorna true, si el desplazamiento puedo llevarse a cabo y false si el enumerado ya se encuentra al final de la colección. • void System.Collections.IEnumerator.Reset: Lleva al enumerador a su posición inicial, es decir “mirando” al primer elemento de la colección. • object System.Collections.IEnumerator.Current: Esta propiedad sólo se compromete con la implementación del método get, y retorna una referencia al elemento que actualmente está siendo señalado por el enumerador. 4.4 La clase System.Collections.ArrayList. Dentro del espacio destinado a las colecciones no sólo existen interfaces, también hay algunas clases, aunque esto es relativamente pobre. Dentro de las clases existentes se encuentra la clase ArrayList. La clase ArrayList se comporta como un arreglo que está en capacidad de crecer, de manera completamente transparente para quien lo usa, una vez se llena. Esto tiene ventajas muy grandes cuando el número de elementos a contener es grande pero no está determinado por una cantidad exacta. Para mostrar la estructura del esta clase se empleará una herramienta , la cual es un visor del API y se encuentra ubicada en denominada Microsoft Visual Studio .NET\FrameworkSDK\Bin. Este visor emplea las siguientes convenciones para presentar los elementos del API: Símbolo Significado Indica que hay más información disponible sobre el ítem. En algunos casos basta hacer doble clic sobre el ítem para verla. Se asocia a los namespace Se asocia a las clases Se asocia a los struct Material .NET Parte Dos -Algoritmos para Telemática Elaborado por Luz E. Jiménez - Universidad Icesi 32 Se asocia a las interfaces Se asocia a los campos Se asocia a los campos static Se asocia a los métodos Se asocia a los métodos static Se asocia a las propiedades La información que ildasm nos proporciona sobre la clase ArrayList es la que se presenta a continuación: Material .NET Parte Dos -Algoritmos para Telemática Elaborado por Luz E. Jiménez - Universidad Icesi 33 Material .NET Parte Dos -Algoritmos para Telemática Elaborado por Luz E. Jiménez - Universidad Icesi 34 De los elementos mostrados se presentarán, en este material, sólo algunos que se consideran relevantes al tema que se viene tratando. • Interfaces que implementa: System.Collections.IList: Interfaz que hereda de System.Collections.ICollection y System.Collections.IEnumerable. Por lo tanto la clase ArrayList deberá implementar, no sólo los métodos de la interfaz IList, sino que también deberá implementar los métodos de estas dos interfaces. ¾ System.ICloneable: Esta interfaz establece el compromiso de implementar el método necesario para crear un clon1 del objeto sobre el cual se invoca dicho método. ¾ • Atributos con que cuenta: ¾ _defaultCapacity: Es un atributo static, y contiene el tamaño que se dará a la ¾ colección si al momento de su creación no se indica otro tamaño. _items: Es el arreglo, como se puede ver en la gráfica anterior se define para contener objetos de la clase object. 1 En este ambiente se considera clon a un nuevo objeto que contiene exactamente lo mismo que el objeto original. Sí el objeto a clonar contiene dentro de sus atributos referencias a otros objetos, el método que crea el clon debe decidir si el nuevo objeto contendrá referencias a estos mismos objetos, o sí estos objetos también se deben clonar. Material .NET Parte Dos -Algoritmos para Telemática Elaborado por Luz E. Jiménez - Universidad Icesi 35 • Métodos y propiedades que implementa: ¾ int System.Collections.ArrayList.Add( object ): adiciona el objeto que recibe como parámetro al final de la lista, retorna el valor de índice al momento de la adición. ¾ void System.Collections.ArrayList.AddRange( ICollection ): agrega la colección que recibe como parámetro, al final de la lista. ¾ ¾ ¾ int System.Collections.ArrayList.BinarySearch( object ) int System.Collections.ArrayList.BinarySearch( object, IComparer ) int System.Collections.ArrayList.BinarySearch( int, int, object, IComparer ): realiza una búsqueda binaria del objeto que recibe como parámetro, puede recibir un objeto comparador, el cual se empleará para realizar la comparación, y puede realizar la búsqueda sobre una porción del arreglo, definida por los índices de la posición en la cual debe empezar la búsqueda y la posición en la cual debe terminar. Retorna la posición en la cual se encuentra el objeto, en caso de no encontrarlo retorna un valor negativo. ¾ void System.Collections.ArrayList.Clear: limpia la lista. ¾ bool System.Collections.ArrayList.Contains( object ): retorna un valor booleano que indica si el objeto pasado como parámetro se encuentra en la lista o no. ¾ ¾ ¾ System.Collections.ArrayList.IndexOf( object ) System.Collections.ArrayList.IndexOf( object, int ) System.Collections.ArrayList.IndexOf( object, int, int ): retorna la posición en la cual encuentra por primera vez, pues esta colección puede contener duplicados, el objeto pasado como parámetro. La búsqueda se puede hacer sobre toda la colección, o sobre una parte de ella, lo cual se indica dando un valor entero si se requiere que busque a partir de él y hasta el final o dos valores enteros para dar tanto la posición de inicio como la posición de finalización de la búsqueda. Si el objeto no es encontrado se retorna -1. ¾ ¾ void System.Collections.ArrayList.Insert( int, object ) void System.Collections.ArrayList.InsertRange( int, ICollection ): ¾ ¾ ¾ int System.Collections.ArrayList.LastIndexOf( object ) int System.Collections.ArrayList.LastIndexOf( object, int ) int System.Collections.ArrayList.LastIndexOf( object, int, int ): retorna la posición en la estos métodos permiten la adición de un objeto, o una colección de ellos, en una posición determinada de la colección. Recuérdese que si se emplea el método Add, la adición se hace siempre al final de la colección. cual encuentra por última vez el objeto pasado como parámetro. La búsqueda se puede hacer sobre toda la colección, o sobre una parte de ella, lo cual se indica dando un valor entero si se requiere que busque a partir de él y hasta el final o dos valores enteros para dar tanto la posición de inicio como la posición de finalización de la búsqueda. Si el objeto no es encontrado se retorna -1. ¾ ¾ void System.Collections.ArrayList.Remove( object ) void System.Collections.ArrayList.RemoveAt( int ) void System.Collections.ArrayList.RemoveRange( int, int ): estos métodos permiten la eliminación de un objeto o grupo de ellos, bien sea indicando el objeto, su posición o el rango a eliminar. Material .NET Parte Dos -Algoritmos para Telemática Elaborado por Luz E. Jiménez - Universidad Icesi 36 ¾ static ArrayList System.Collections.ArrayList.Repeat( object, int ): este método guarda en un objeto ArrayList una referencia al objeto pasado como primer parámetro y lo repite tantas veces como lo indique el segundo parámetro. ¾ void System.Collections.ArrayList.Sort( ) ¾ void System.Collections.ArrayList.Sort( IComparer ) ¾ void System.Collections.ArrayList.Sort( int, int, IComparer ): estos métodos ordenan la ¾ ¾ ¾ ¾ ¾ ¾ ¾ 4.5 ArrayList, se puede especificar un orden especial a través de un objeto comparador, y también se puede ordenar sólo una parte de la lista. int System.Collections.ArrayList.Capacity: esta propiedad permite obtener o modificar la capacidad, es decir el número de elementos que está en capacidad de contener en ese momento, del ArrayList. int System.Collections.ArrayList.Count: esta propiedad permite obtener la cantidad de elementos que contiene el ArrayList. bool System.Collections.ArrayList.IsFixedSize: esta propiedad permite saber si el tamaño del ArrayList se ha determinado como fijo. bool System.Collections.ArrayList.IsReadOnly: esta propiedad permite saber si el ArrayList se ha marcado como de sólo lectura. bool System.Collections.ArrayList.IsSynchronized: esta propiedad permite saber si el ArrayList se ha marcado como sincronizado. object System.Collections.ArrayList.SyncRoot: esta propiedad permite obtener un objeto capaz de sincronizar el ArrayList. object System.Collections.ArrayList.this[int]: esta propiedad permite obtener o modificar el objeto ubicado en la posición indicada, por el subíndice, del ArrayList. Algunas observaciones adicionales. Dado que este es un tema importante y sobre el cual se suele trabajar mucho, no es poco probable que en muy poco tiempo las personas que proponen la estructura central de .NET hayan incorporado nuevas colecciones al namespace System.Collections, por ello es recomendable que antes de tomar una decisión respecto a utilizar alguna de las conocidas se de una mirada al contenido de este namespace. A continuación se presentan las clases e interfaces contenidas en el Framework 1.0 versión 1.0.3705 de Microsoft .NET. Puede ser un ejercicio interesante el revisar los servicios y la filosofía que tienen clases como System.Collections.Queue, System.Collections.SortedList y System.Collections.Stack. Material .NET Parte Dos -Algoritmos para Telemática Elaborado por Luz E. Jiménez - Universidad Icesi 37 Material .NET Parte Dos -Algoritmos para Telemática Elaborado por Luz E. Jiménez - Universidad Icesi 38 5 Flujos y Serialización en .NET Cuando se construyen aplicaciones es muy frecuente que se necesite leer o escribir datos que se requiere persistan una vez la aplicación termine su ejecución; es decir es frecuente que se busque un mecanismo que permita que la información asociada a una aplicación pueda seguir existiendo aún si la aplicación no está en ejecución. Para ello, habitualmente, se emplean archivos que son almacenados en un disco duro; y esto puede hacerse de diversas formas ya que la información puede ser escrita tal y como se encuentra en memoria, o puede requerirse que la información tenga un formato especial. Para responder a esta necesidad .NET trabaja los conceptos de Flujos y Serialización, que se empezarán a discutir en esta parte del material, y cuyos elementos se encuentran en los namespace System.IO y System.Runtime.Serialization. Dentro de esto se tratarán diversos aspectos que cubren temas como identificar el tipo de flujo que se debe establecer, que clases provee .NET para esto, qué es serializar objetos y qué mecanismos de serialización provee .NET. 5.1 La definición de flujo. Se denomina flujo al canal que se establece entre dos “dispositivos” que necesitan comunicarse; para cada flujo se deben definir varias cosas como son dispositivos a comunicar, la dirección en la cual fluirá la información y el formato de en el cual viajará la información entre ellos. Por ejemplo, se requiere establecer una conexión de lectura entre una aplicación y un archivo de texto existente en el disco duro: • dispositivos a conectar: la aplicación y el disco duro. • dirección: como la aplicación va a leer del archivo la dirección será del archivo a la aplicación, es decir la aplicación verá el flujo como una entrada. • por último se debe indicar el formato en el cual está la información, cadenas de caracteres, en este caso. Los dispositivos a conectar se pueden agrupar en dispositivo físicos, tales como un archivo en el disco duro, un puerto de red, el teclado o el monitor; y en dispositivos lógicos como, citando el caso más común, los flujos que se acaban de mencionar, es decir los que establecen conexiones con los dispositivos físicos. La dirección se determina a partir del código en el cual se está definiendo el flujo, si se requiere recibir información a través del flujo, este se considerará un flujo de entrada, y por tanto su nombre contendrá “in”, “input” o “read”; pero si se trata de enviar información a través de él, se considerará como un flujo de salida y su nombre contendra “out”, “output” o “write”. Material .NET Parte Dos -Algoritmos para Telemática Elaborado por Luz E. Jiménez - Universidad Icesi 39 Respecto al formato en el cual viajará la información a través del flujo, se consideran tres grandes formatos: • bytes, • texto, se considera formato texto el que viaje como caracter o como cadena, y • objetos, lo cual se conoce como el proceso de serialización. 5.2 Clases para el manejo de flujos en .NET Las clases, y enumeraciones, que ofrece .NET para el manejo de los flujos se encuentran agrupadas en un espacio de nombre denominado System.IO. En este material no se presentarán todos estos elementos, sólo los necesarios para ilustrar el manejo de los flujos. 5.2.1 FileMode. Se trata de una enumeración que contiene los diversos modos con que se puede pedir al Sistema Operativo que abra un archivo: • Append: debe abrir el archivo y ubicarse al final de él. El archivo se abre en modo escritura y lanzará una excepción si se intenta leer. • Create: debe crear un archivo, sobreescribiéndolo si ya existe. • CreateNew: debe crear un archivo, lanzará una excepción si ya existe. • Open: debe abrirse un archivo existente, lanzará una excepción si no existe. • OpenOrCreate: debe abrirse un archivo existente, si no existe debe crearse. • Truncate: debe abrir un archivo existente e ignorar y sobreescribir su contenido. 5.2.2 FileAccess. Se trata de una enumeración que contiene los diversos modos con que se puede establecer el acceso a un archivo: • Read. • ReadWrite. Este es el valor que se asume por omisión. • Write. 5.2.3 System.IO.FileShare Se trata de una enumeración que indica si el archivo puede ser accesado, simultáneamente, por varios flujos. Material .NET Parte Dos -Algoritmos para Telemática Elaborado por Luz E. Jiménez - Universidad Icesi 40 • Inheritable: sólo los procesos hijos del proceso que establece la conexión podrán tener acceso al archivo. • None: no se admite que ningún otro proceso establezca conexión con el archivo. Este valor se asume por omisión. • Read: sólo permite que se establezcan conexiones adicionales que indiquen harán operaciones de lectura. • ReadWrite: permite que se establezcan otras conexiones con fines de lectura o escritura. • Write: sólo permite que se establezcan conexiones adicionales que indiquen harán operaciones de escritura. 5.2.4 File. Esta clase fue diseñada para ofrecer servicios a objetos de otras clases que deban manipular archivos, de hecho su constructor es privado y todos los métodos que ofrece han sido definidos como static. A continuación se presentarán algunos de los métodos que ofrece: • System.IO.FileStream Open(string,FileMode,FileAccess,FileShared ) System.IO.FileStream Open(string,FileMode,FileAccess ) System.IO.FileStream Open(string,FileMode ) Retorna un objeto FileStream, asociado al archivo especificado en el primer parámetro, respetando los parámetros suministrados. • void Copy(string,string,bool ) void Copy(string,string) Copia el archivo fuente, primer parámetro, en el archivo destino, segundo parámetro, respetando el permiso de sobreescritura si este último ya existe. Si se omite este último permiso se asume que está denegado. • System.IO.StreamWriter AppendText(string ) Retorna un objeto StreamWriter, asociado al archivo especificado en el parámetro, en modo adición de texto al final del mismo. • void Delete(string) Elimina el archivo indicado en el parámetro, si este no existe no lanza excepción. Esta clase ofrece algunos servicios más, que pueden ser revisados con cualquiera de las herramientas que se han presentado para explorar el API de .NET. 5.2.5 StreamReader. Esta clase permite establecer una conexión para lectura con un dispositivo, y lo lee como una secuencia de bytes que representan caracteres en una codificación Material .NET Parte Dos -Algoritmos para Telemática Elaborado por Luz E. Jiménez - Universidad Icesi 41 determinada. Para trabajar con esta clase, deben crearse objetos pertenecientes a ella pues, a diferencia de la clase anterior, sus métodos no son static. • StreamReader (string, System.Text.Encoding, bool, int ) StreamReader (string, System.Text.Encoding, bool) StreamReader (string, System.Text.Encoding) StreamReader (string ,bool) StreamReader (string) StreamReader (System.IO.Stream, System.Text.Encoding, bool, int) StreamReader (System.IO.Stream, System.Text.Encoding, bool) StreamReader (System.IO.Stream, System.Text.Encoding) StreamReader (System.IO.Stream, bool) StreamReader (System.IO.Stream) Para crear un objeto perteneciente a esta clase existen, como se puede ver en la lista anterior, varias opciones. El primer grupo de ellas, recibe una cadena de caracteres que contiene la ruta y el nombre del archivo, el segundo grupo recibe un objeto Stream, el cual contiene la dirección de la cual se leerá la secuencia de caracteres. Además de esto puede recibir algunos parámetros adicionales como son, un objeto de la clase Encoding, o alguna de sus clases hijas, el cual contendrá información sobre le tipo de codificación que se empleó para escribir, y por tanto para leer, el archivo; una variable boleana que indica si debe buscarse algún tipo de señal especial al inicio del archivo; y una variable entera que especifica el tamaño mínimo del buffer a emplear durante la lectura. • string ReadLine ( ) Retorna la línea del archivo apuntada por el cursor, avanza el cursor una línea. Al terminarse el archivo, retorna null. • string ReadToEnd ( ) Retorna, en una línea, el contenido del archivo desde donde se encuentra el apuntador hasta el final del mismo. Si no hay nada que leer, retorna una cadena vacía. • void Close ( ) Cierra la conexión del flujo. Ejemplo: Se ha escrito el siguiente código que lee un texto desde un archivo cuyo nombre y ubicación se piden al usuario, una vez leido se muestra por pantalla. Código: using System; using System.Collections; using System.IO; using System.Text; namespace AplicaciónConFlujos { class ClaseUno { [STAThread] static void Main(string[] args) { ArrayList unArreglo = new ArrayList(); string elPath = Console.ReadLine(); StreamReader unArchivo = new StreamReader(elPath); string unaCadena = unArchivo.ReadLine(); while (unaCadena != null){ unArreglo.Add(unaCadena); unaCadena = unArchivo.ReadLine(); } Material .NET Parte Dos -Algoritmos para Telemática Elaborado por Luz E. Jiménez - Universidad Icesi 42 unArchivo.Close(); foreach (object unObjeto in unArreglo){ unaCadena = (string) unObjeto; Console.WriteLine(unaCadena); } Console.Read(); } } } Al dar como cadena “LosCamello.txt”, busca en el directorio de trabajo actual, y muestra por pantalla: (realmente aparece la poesía completa…) Otra, interesante, opción para el código: using System; using System.Collections; using System.IO; using System.Text; namespace AplicaciónConFlujos { public class ClaseDos { public static void Main(string[] args) { string elPath = Console.ReadLine(); StreamReader unArchivo = new StreamReader(elPath); string unaCadena = unArchivo.ReadToEnd(); unArchivo.Close(); Console.WriteLine(unaCadena); Console.Read(); } } } Material .NET Parte Dos -Algoritmos para Telemática Elaborado por Luz E. Jiménez - Universidad Icesi 43 Al dar la misma cadena, se obtiene: (realmente aparece la poesía completa…) Es decir con ambos códigos, ¡ el resultado es exactamente el mismo ! 5.2.6 BinaryReader. Esta clase permite establecer una conexión para lectura con un archivo existente en el disco duro, y establece que los datos a leer serán bytes. Esta clase también requiere que se creen objetos pertenecientes a ella, y tampoco ofrece métodos static. • BinaryReader (Stream, System.Text.Encoding ) BinaryReader (Stream) Para crear un objeto perteneciente a esta clase existen dos opciones: ambas requieren un objeto Stream, el cual establece la conexión con el archivo, y una de ellas puede especificar el tipo de codificación que debe ser empleado para la lectura. Los objetos de la clase Stream, no pueden ser creados directamente, pues esta clase no ofrece un constructor público, así que puede emplearse el método OpenRead(string), de la clase File, el cual retorna un objeto Stream asociado al archivo cuyo nombre completo es pasado como parámetro. • int ReadInt32() char ReadChar() Material .NET Parte Dos -Algoritmos para Telemática Elaborado por Luz E. Jiménez - Universidad Icesi 44 double ReadDouble() byte ReadByte() string ReadString() bool ReadBoolean() Leen los bytes correspondientes a cada tipo de datos, dependiendo de la codificación establecida. Esto implica que al leer el archivo, se conoce la estructura que se empleó para escribir los datos y por tanto se leen en el orden correcto. • bytes[] ReadBytes(int) char[] ReadChar(int) Leen tantos bytes como se requieran para cubrir el número de datos indicados en el parámetro. Guarda el resultado de la lectura en un arreglo de caracteres. Ejemplo: Asuma que se tiene un archivo denominado “valores.dat”, el cual contiene valores escritos como bytes, y se quiere leerlos. using System; using System.Collections; using System.IO; using System.Text; namespace AplicaciónConFlujos { class ClaseUno { [STAThread] static void Main(string[] args) { BinaryReader otroArchivo = new BinaryReader(File.OpenRead("E:/Mis documentos/Cursos/NET/valores.dat")); int nuevoValorUno = otroArchivo.ReadInt32(); double nuevoValorDos = otroArchivo.ReadDouble(); char[] nuevoValorTres = otroArchivo.ReadChars(2); string nuevoValorCuatro = otroArchivo.ReadString(); otroArchivo.Close(); Console.WriteLine(nuevoValorUno); Console.WriteLine(nuevoValorDos); Console.WriteLine(nuevoValorTres[0]+" "+nuevoValorTres[1]); Console.WriteLine(nuevoValorCuatro); Console.Read(); } } } Al ejecutar este código se obtiene, por pantalla: Pero al revisar el archivo “valores.dat”, con una herramienta como el Bloc de notas de Windows, se encuentra: Material .NET Parte Dos -Algoritmos para Telemática Elaborado por Luz E. Jiménez - Universidad Icesi 45 5.2.7 StreamWriter. Esta clase permite establecer una conexión para escritura con un dispositivo, y escribe una secuencia de bytes que representan los caracteres enviados, en una codificación determinada. Esta clase también permite la creación de objetos, y de hecho es la forma correcta de trabajar con ella, pues no ofrece métodos static. • StreamWriter (string, System.Text.Encoding, bool, int ) StreamWriter (string, System.Text.Encoding, bool) StreamWriter (string, System.Text.Encoding) StreamWriter (string ,bool) StreamWriter (string) StreamWriter (System.IO.Stream, System.Text.Encoding, bool, int) StreamWriter (System.IO.Stream, System.Text.Encoding, bool) StreamWriter (System.IO.Stream, System.Text.Encoding) StreamWriter (System.IO.Stream, bool) StreamWriter (System.IO.Stream) Para crear un objeto perteneciente a esta clase existen, tantos constructores como para la clase StreamReader, y funcionan igual que en ella, pero establecen la conexión de escritura. • void Write(string ) Escribe la cadena que recibe como parámetro en el dispositivo al cual se está conectado. • void Flush( ) Obliga a que todos los buffer de escritura sean vaciado a sus depósitos finales. • void Close() Cierra la conexión con el dispositivo. Ejemplo: Asuma que se debe tomar los valores guardados en el archivo “valores.dat”, con el cual se trabajó en un ejemplo anterior, y guardarlos en un archivo “valores.txt", cuidando que queden en formato texto. Código: using System; using System.Collections; using System.IO; using System.Text; namespace AplicaciónConFlujos { class ClaseUno { [STAThread] Material .NET Parte Dos -Algoritmos para Telemática Elaborado por Luz E. Jiménez - Universidad Icesi 46 static void Main(string[] args) { BinaryReader otroArchivo = new BinaryReader(File.OpenRead("E:/Mis documentos/Cursos/NET/valores.dat")); int nuevoValorUno = otroArchivo.ReadInt32(); double nuevoValorDos = otroArchivo.ReadDouble(); char[] nuevoValorTres = otroArchivo.ReadChars(2); string nuevoValorCuatro = otroArchivo.ReadString(); otroArchivo.Close(); StreamWriter enTexto = new StreamWriter("E:/Mis documentos/Cursos/NET/valores.txt"); enTexto.WriteLine(nuevoValorUno); enTexto.WriteLine(nuevoValorDos); enTexto.WriteLine(nuevoValorTres); enTexto.WriteLine(nuevoValorCuatro); enTexto.Flush(); enTexto.Close(); } } } Ahora al mirar el archivo “valores.txt”, con el Bloc de notas, se encuentra: 5.2.8 BinaryWriter. Esta clase permite establecer una conexión para escritura con un dispositivo, y establece que los datos a escribir serán bytes. Esta clase también requiere que se creen objetos pertenecientes a ella, y tampoco ofrece métodos static. • BinaryWriter (Stream, System.Text.Encoding ) BinaryWriter (Stream) Para crear un objeto perteneciente a esta clase existen también existen dos opciones, y se comportan igual que con los BinaryReader. • void Write(int) void Wirte(char) void Write (double) void Write(byte) void Write(string) void Write (bool) Escribe los bytes correspondientes a cada tipo de datos, dependiendo de la codificación establecida. • void Flush( ) Obliga a que todos los buffer de escritura sean vaciado a sus depósitos finales. • void Close() Cierra la conexión con el dispositivo. Material .NET Parte Dos -Algoritmos para Telemática Elaborado por Luz E. Jiménez - Universidad Icesi 47 Ejemplo: Asuma que se debe tomar el texto guardado en el archivo “LosCamello.txt”, con el cual se trabajó en un ejemplo anterior, y guardarlo como bytes, en un archivo “LosCamellos.dat”. Código: using System; using System.Collections; using System.IO; using System.Text; namespace AplicaciónConFlujos { class ClaseUno { [STAThread] static void Main(string[] args) { string elPath = "E:/Mis documentos/Cursos/NET/LosCamellos.txt"; StreamReader unArchivo = new StreamReader(elPath); String unaCadena = unArchivo.ReadToEnd(); unArchivo.Close(); BinaryWriter otroArchivo = new BinaryWriter(File.OpenWrite("E:/Mis documentos/Cursos/NET/LosCamellos.dat")); otroArchivo.Write(unaCadena); otroArchivo.Flush(); otroArchivo.Close(); } } } En esta caso al mirar el archivo “LosCamellos.dat”, con el Bloc de notas, se encuentra: Sin embargo si la escritura del texto en bytes se hace empleando un objeto UnicodeEncoding, es decir: using System; using System.Collections; using System.IO; using System.Text; namespace AplicaciónConFlujos { class ClaseUno { [STAThread] static void Main(string[] args) { string elPath = "E:/Mis documentos/Cursos/NET/LosCamellos.txt"; StreamReader unArchivo = new StreamReader(elPath); String unaCadena = unArchivo.ReadToEnd(); unArchivo.Close(); BinaryWriter otroArchivo = new BinaryWriter(File.OpenWrite("E:/Mis documentos/Cursos/NET/LosCamellos.dat"), new UnicodeEncoding()); otroArchivo.Write(unaCadena); Material .NET Parte Dos -Algoritmos para Telemática Elaborado por Luz E. Jiménez - Universidad Icesi 48 otroArchivo.Flush(); otroArchivo.Close(); } } } Al mirar el archivo “LosCamellos.dat”, con el Bloc de notas, se encuentra: 5.3 El concepto de serialización Con lo presentado en este capítulo, hasta el momento, se tienen herramientas que permiten a un programador dar persistencia a los datos que está manejando en dos formatos diferentes. Pero ¿cómo dar persistencia a los objetos con que se trabaje?, ¿debe escribirse, uno a uno, cada uno de los atributos que conforman la estructura del objeto, para garantizar que la información podrá ser recupearada al reiniciar el trabajo?, ¿cómo saber a qué clase pertenecia el objeto?, ¿se debe crear un nuevo objeto, de esa clase, y asignar a sus atributos los valores recuperados?, si uno de los atributos es una referencia a otro objeto ¿qué hacer?, si es una colección y contiene varias referencias a un mismo objeto ¿cuántas veces se escribe el objeto? Por fortuna existe el concepto de serialización que es, básicamente, la forma de dar persistencia a objetos, permitiendo a .NET que encargarse del manejo de todos los detalles pertinentes para escribir los objetos y para leerlos. 5.4 Conceptos básicos para el manejo de serialización en .NET En .NET la serialización es un proceso bien sencillo; además es recursivo, es decir si usted serializa un objeto que contiene referencias a otros estos serán automáticamente serializados. Las pautas para serializar objetos en .NET están claramente establecidas: Material .NET Parte Dos -Algoritmos para Telemática Elaborado por Luz E. Jiménez - Universidad Icesi 49 • cada clase debe indicar si permite que los objetos que se creen a partir de ella sean serializados o no, este último valor es el que se asume por defecto. • se debe establecer un flujo de comunicación con el archivo en el cual se guardarán los objetos serializados. • se debe seleccionar el formato de serialización que desea emplearse. • se escriben los objetos a serializar. • se cierra el flujo. Cuando una clase desea indicar que permite que los objetos creados a partir de ella puedan ser serializados basta con que, antes de su nombre, maneje el atributo Serializable entre corchetes, tal como se ilustra a continuación: [Serializable] public class Estudiantes { Pero puede tenerse clases cuyos objetos no requiere serializar todos sus atributos, en cuyo caso basta emplear el atributo NonSerialized, también entre corchetes, justo antes de la definición del atributo, así: [Serializable] public class Estudiantes { string nombres; string apellidos; string código; int edad; [NonSerialized] double horaLlegadaHoy; string[] cursosQueToma; En el caso presentado la clase Estudiantes, indica que los objetos creados a partir de ella serán seriliazados, pero que el atributo horaLlegadaHoy no debe ser sometido a este proceso. Una vez hecho lo anterior, debe indicarse qué tipo de flujo desea establecerse con el archivo en el cual se guardarán los objetos serializados, para lo cual hay una restricción y es que los métodos Serialize que ofrece .NET sólo reciben objetos de la clase Stream, así que el flujo a establecer debe ser de este tipo. Stream otroArchivo = File.OpenWrite("E:/Mis documentos/Cursos/NET/objetos.dat"); Ahora debe seleccionarse el formato de serialización a emplear, respecto a esto existen tres opciones, la primera es serializar a formato binario, que es la opción a emplear si se quiere dar persistencia, es decir si la serialización se hace para que los objetos sean guardados en un archivo en el disco duro; la segunda opción es serializar a formato SOAP, y es la opción que debe emplearse si la serialización se hace para transmitir por la red la información concerniente a los objetos; por último .NET permite crear formatos de serialización especiales, que pueden responder a necesidades específicas de un programador. Material .NET Parte Dos -Algoritmos para Telemática Elaborado por Luz E. Jiménez - Universidad Icesi 50 Dado que por ahora se trabaja la serialización como un mecanismo para dar persistencia a los objetos se especificará como formato el binario, para lo cual debe incluirse el espacio de nombre apropiado: using System.Runtime.Serialization.Formatters.Binary; Después debe crearse el objeto encargado de escribir los objetos a serializar: BinaryFormatter unFormato = new BinaryFormatter(); Ahora ya se le puede pedir que escriba el objeto en el flujo creado para ello: unFormato.Serialize(otroArchivo,unEstudiante); Por último debe cerrarse el flujo: otroArchivo.Close(); Si al conluir la ejecución de la clase que está realizando las mencionadas instrucciones, se revisa el archivo creado, ”objetos.dat” en este caso, se verá una secuencia como la que se presenta a continuación: Una vez los objetos estén serializados, debe existir una opción que permita leerlos, y permitirles que recuperen su condición de objetos, para ello debe: • se debe establecer un flujo de comunicación con el archivo desde el cual se leerán los objetos previamente serializados. Material .NET Parte Dos -Algoritmos para Telemática Elaborado por Luz E. Jiménez - Universidad Icesi 51 • se debe seleccionar el formato de deserialización con el cual deben leerse. • se leen los objetos. • se cierra el flujo. A continuación se presenta un código que permite leer los objetos escritos en el archivo”objetos.dat” using System; using System.Collections; using System.IO; using System.Text; using System.Runtime.Serialization.Formatters.Binary; namespace AplicaciónConFlujos { class ClaseUno { [STAThread] static void Main(string[] args) { Stream otroArchivo = File.OpenRead("E:/Mis documentos/Cursos/NET/objetos.dat"); Estudiantes unEstudiante = new Estudiantes(); BinaryFormatter unFormato = new BinaryFormatter(); unEstudiante = (Estudiantes)unFormato.Deserialize(otroArchivo); otroArchivo.Close(); Console.WriteLine("Nombres: "+unEstudiante.nombres); Console.WriteLine("Apellidos: "+unEstudiante.apellidos); Console.WriteLine("Código: "+unEstudiante.código); Console.WriteLine("Edad: "+unEstudiante.edad); Console.Write("Cursos: "); foreach (string unCurso in unEstudiante.cursosQueToma){ Console.Write(unCurso+" "); } Console.WriteLine("\nHora Llegada: "+unEstudiante.horaLlegadaHoy); Console.Read(); } } } Lo cual produce como salida: El atributo marcado como no serializable, queda con el valor por defecto para los atributos numéricos, es decir cero (0). Material .NET Parte Dos -Algoritmos para Telemática Elaborado por Luz E. Jiménez - Universidad Icesi 52