martes, 22 de abril de 2014

Utilizando Extension Methods en .NET con C#

En programación orientada a objetos, la herencia es uno de los mecanismos más eficientes para extender la funcionalidad de los objetos que componen un sistema, este mecanismo permite que un objeto herede atributos y comportamientos de otro objeto reduciendo así la cantidad de código que tendrá que mantenerse una vez desarrollado el sistema.

Sin embargo agregar funcionalidad a un objeto que no fue diseñado para ser heredado puede ser una tarea no muy recomendable, ya que implica cambiar el diseño de ese objeto y entonces afectar todos los objetos que dependan de él y sus operaciones que lleven a cabo dentro del sistema.

En .NET existe una característica llamada extension methods (métodos de extension) que permite agregar funcionalidad (métodos) a un objeto o tipo ya existente en el framework o en los ensamblados sin la necesidad de crear subclases, recompilar o modificar el código fuente. Esta técnica incluso permite agregar métodos a objetos que provengan de clases selladas (sealed) sin violar el principio de encapsulamiento.

Como ejemplo de esta técnica, agregaré un par de métodos a un tipo incorporado dentro del framework .NET, utilizaré la estructura DateTime que representa el tiempo (fecha y la hora).

A esta estructura voy a agregarle dos métodos uno llamado LastDay que regresa el último día del mes de un objeto DateTime y otro método llamado After que regresa true si el objeto DateTime que lo invoca es mayor a la fecha que recibe como parámetro.

Aquí el código de la clase donde agrego los extension methods

Aquí el código con el programa que prueba los métodos anteriores.

Al ejecutar el programa se ve la siguiente salida.

Propiedades de los extension methods

Pueden aplicarse a cualquiera de los siguientes objetos: Clases, Estructuras, Interfaces, Delegados, Genéricos y arreglos. Deben ser declarados dentro de una clase estática, y tener la palabra reservada this antes de su primer parámetro, que es el tipo que el método extiende, los demás parámetros son los argumentos del método. Son públicos por que se llaman desde fuera de la clase donde ellos son declarados. Pueden aplicarse en clases selladas (sealed) y tipos intrínsecos porque automáticamente soportan boxing y unboxing, es decir cuando se aplican a un tipo por valor .NET los envuelve como si se tratara de una objeto por referencia.

miércoles, 9 de abril de 2014

Compresión y descompresión de flujos(streams) en .NET

Una capacidad muy útil en cualquier Framework es la de comprimir y descomprimir archivos o flujos de datos, ejemplos aplicados de esta capacidad los tenemos en las aplicaciones que intercambian datos a través de la red como el caso de comprimir el viewstate en las peticiones y respuestas del servidor al cliente en una aplicación ASP.NET o si necesitamos reducir el tamaño de los objetos JSON que viajan de una aplicación hacia un servicio web y viceversa.

.NET proporciona las clases GzipStream y DeflatStream que implementan el algoritmo de compresión estándar gzip que está libre de patente. De hecho la diferencia entre ambas clases es que la clase GzipStream utiliza la especificación gzip descrita en el RFC 1952 que reduce el tamaño de los datos utilizando un código Lempel-Ziv (LZ77) con un CRC de 32 bits, lo que significa que se puede incluir encabezados (headers) con información extra que puede ser utilizada por cualquier herramienta de descompresión o por el comando gunzip de Linux o UNIX.

En resumen si los datos comprimidos van a hacer descomprimidos por una aplicación .NET lo recomendable es usar la clase DeflatStream, de lo contrario si van a hacer descomprimidos por otra aplicación usar la clase GzipStream.

Para mostrar la utilización de la clase DeflatStream escribí el siguiente programa que ejecuta la acción de comprimir o descomprimir dependiendo de la extensión del archivo que le pasemos como parámetro. (Este código sirve para ambos flujos solo hay que reemplazar DeflatStream por GzipStream)

using System;
using System.IO;
using System.IO.Compression;

namespace GzipGunzip
{
class MainClass
{
public static void Main (string[] args)
{
if (args.Length == 1)
{
 try{
 FileInfo fis = new FileInfo(args[0]);
 Console.WriteLine("Input file {0}",fis.FullName);
 if(string.Compare(fis.Extension,".gz") == 0)
  Uncompress(fis.FullName);
 else
  Compress(fis.FullName);
 }catch(Exception ex){
  Console.WriteLine(ex.Message);
 }
}else
 Console.WriteLine("\nUsage: GzipGunzip filename\n");
}

static void Uncompress (string filename)
{
Console.WriteLine ("Uncompressing .... {0} ", filename);
string destFile = filename.Substring (0, filename.Length - 3);
FileStream inputStream = new FileStream (filename
                                        , FileMode.Open
                                        , FileAccess.Read);
using (FileStream outputStream = new FileStream (destFile
                                         , FileMode.Create
                                         , FileAccess.Write)) {
 using(DeflateStream zipStream = new DeflateStream (inputStream
                                             , CompressionMode.Decompress)){
 int inputByte = zipStream.ReadByte ();
 while (inputByte != -1) {
  outputStream.WriteByte ((byte)inputByte);
  inputByte = zipStream.ReadByte ();
 }
 }
}

Console.WriteLine("output: {0}",destFile);
}
static void Compress (string filename)
{
Console.WriteLine ("Compressing .... {0} ", filename);
string destFile = filename + ".gz";
byte[] buffer = null;
using (FileStream inputStream = new FileStream(filename
                                 ,FileMode.Open
                                        ,FileAccess.Read)) {
 buffer = new byte[inputStream.Length];
 inputStream.Read (buffer, 0, buffer.Length);

}
Console.WriteLine ("bytes read: {0}", buffer.Length);
using (FileStream outputStream = new FileStream(destFile
                                   ,FileMode.Create
                                         ,FileAccess.Write)) {
 using(DeflateStream zipStream = new DeflateStream (outputStream
                              , CompressionMode.Compress)){
 zipStream.Write (buffer, 0, buffer.Length);
 }
}
Console.WriteLine("bytes written: {0}",buffer.Length);
}
}
}

Ahora para probar el código, voy a crear un archivo llamado dummy.zeros con un tamaño de 20 megas para pasárselo como parámetro al programa.

Este archivo lo creo con el comando dd en Linux

dd if=/dev/zero of=dummy.zeros bs=1000 count=20000

Ahora muestro el tamaño del archivo dummy.zeros, siendo de 20Mb.

Ahora comprimo el archivo dummy.zeros con el programa de ejemplo GzipGunzip, esto lo hago con el siguiente comando:

mono GzipGunzip.exe dummy.zeros

Si se ejecuta correctamente este programa debe crear un archivo llamado dummy.zeros.gz, el cual es el archivo comprimido de dummy.zeros. Comparamos el tamaño de ambos archivos.

Ahora voy a descomprimir el archivo dummy.zeros.gz , pero antes de hacerlo voy a renombrar el archivo original dummy.zeros como dummy.zeros.bak para evitar que el archivo original se sobreescriba y así poder comparar todos los archivos. Ejecutamos nuevamente el programa GzipGunzip pasándole como parámetro el nombre del archivo.

mono GzipGunzip.exe dummy.zeros.gz

Bien ahora con todos los archivos, podemos comparar sus tamaños y comprobar que las clases de compresión de .NET funcionaron.

Para diseñar programas que utilicen las clases de compresión y descompresión de archivos en C#, el primer paso es crear los flujos de entrada y de salida.

FileStream inputStream = new FileStream (filename , FileMode.Open, FileAccess.Read);
FileStream outputStream = new FileStream (destFile , FileMode.Create, FileAccess.Write))

En el caso de la compresión el flujo de compresión (DeflateStream) debe de envolver al flujo de salida, porque la entrada es un archivo sin comprimir que tendrá como destino un archivo comprimido.

DeflateStream zipStream = new DeflateStream (outputStream, CompressionMode.Compress)

Para la descompresión el flujo de compresión (DeflateStream) deberá de envolver al flujo de entrada ya que es un archivo comprimido que tendrá como destino un archivo sin comprimir.

DeflateStream zipStream = new DeflateStream (inputStream, CompressionMode.Decompress)

Hay que recordar que uno de los factores que degradan el desempeño de las aplicaciones ASP.NET es cuando existe un cuello de botella en el ancho de banda y no en la utilización del procesador, en el caso de la compresión de datos usada junto con el protocolo HTTP se recomienda para texto: JSON, XML, HTML, etc. Esta compresión no es útil para archivos de imágenes, los cuales ya se encuentran comprimidos.