C# - Capitulo 2
C# - Capitulo 2
qxd
7/24/07
4:33 PM
Page 63
Captulo 2
C#
EL HIJO PRDIGO DE
MICROSOFT ES UNO DE
LOS FAVORITOS PARA
DESARROLLO DE
APLICACIONES WEB,
WINDOWS Y MOBILE.
EN ESTA SECCIN SE
PRESENTAN PRCTICAS
INTERESANTES PARA
REALIZAR EN ESTA
PLATAFORMA,
APLICABLES A
CUALQUIER
PROBLEMTICA DEL
MUNDO REAL.
63
7/24/07
4:33 PM
Page 64
Creacin de aplicaciones
con soporte de plugins
Veremos algunas tcnicas que brinda el Framework .NET para escribir
aplicaciones capaces de ser extendidas sin recompilar.
Qu necesitamos?
Para que nuestra aplicacin soporte el agregado de
plugins, debemos tener en cuenta dos aspectos fundamentales: primero, los plugins que se desarrollen
deben tener acceso a elementos de la aplicacin sobre los cuales trabajar; segundo, el cdigo del plugin
debe poder determinarse en tiempo de ejecucin de la
aplicacin (no, en tiempo de compilacin), es decir
que la aplicacin debe estar totalmente desacoplada
de los plugins que se escriban en el futuro. Para lograrlo, vamos a valernos del uso de las interfaces y
el late bining (enlace tardo), conceptos que explicaremos a continuacin.
Las interfaces
Para q ue el plugin pueda conocer algunos elementos de la aplicacin y acte sobre ellos, vamos a escribir una interfaz que abstraiga las caractersticas de un
archivo de texto, y otra para el editor de texto propiamente dicho. Con el objetivo de que el plugin pueda ser
compilado independientemente de la aplicacin, colocaremos dichas interfaces en un assembly separado.
Usando Visual Studio .NET, creamos un nuevo proyecto de tipo Biblioteca de Clases, agregamos una nueva
clase (cuyo archivo llamaremos IArchivoTexto.cs) y
reemplazamos el cdigo generado por el asistente, por
este otro:
64
public interface
IArchivoTexto
{
string Nombre{ get; set;}
string Texto{ get; set; }
bool Grabado{ get; set; }
void Grabar();
}
Escribir la aplicacin
Ya tenemos las interfaces definidas;
vamos a comenzar ahora a escribir
nuestra aplicacin. Usando Visual
Studio .NET, creamos un proyecto de
tipo Aplicacin para Windows y
agregamos una referencia al que generamos antes. Lo primero que tenemos que hacer es armar la interfaz
general de la aplicacin. Agregamos
un componente MainMenu y creamos
dos opciones de men de nivel superior: Archivo (mnuArchivo) y Plugins
(mnuPlugins). Es importante darle al
7/24/07
4:33 PM
Page 65
2 | C#
men de plugins un nombre que recordemos, ya que deberemos referenciarlo ms
adelante. Agregamos tambin un TextBox
y le establecemos las propiedades MultiLine en true y Dock en Fill. A esta altura, si
ejecutamos el proyecto, la aplicacin se ver como en la [Figura 1].
Luego, vamos a definir una clase que implemente la interfaz IArchivoTexto:
public class ArchivoTexto:
IArchivoTexto
{
public ArchivoTexto()
{
texto = string.Empty;
nombre = string.Empty;
}
private bool grabado;
private string nombre;
private string texto;
public string Nombre
{
get{ return nombre; }
set{ nombre = value; }
}
public string Texto
{
get{ return texto; }
set{ texto = value; }
}
public bool Grabado
{
get{ return grabado; }
set{ grabado = value; }
}
public void Grabar()
{ // TODO: agregar
la implementacin
ArchivoTexto.Grabar
}
Como vemos, el texto del archivo, simplemente, ser una variable de tipo string.
Para no complicar demasiado el ejemplo, vamos a hacer que el formulario
principal de la aplicacin implemente la
interfaz IEditor, y que contenga todos los
elementos necesarios para pasarles a los
plugins instalados. Para que el formulario
implemente la interfaz, la agregamos a su
definicin:
blado que creamos al principio y agregamos una nueva clase que implemente la
interfaz IPlugin:
El plugin
Como la aplicacin est casi lista, vamos
a enfocarnos en la escritura del plugin
que, ms tarde, agregaremos a la aplicacin. Como dijimos, el plugin debe ser totalmente independiente de sta, por lo
cual lo colocaremos en un ensamblado separado, agregando un nuevo proyecto de
tipo Biblioteca de Clases a nuestra solucin. En l hacemos referencia al ensam-
65
7/24/07
4:33 PM
Page 66
Ya tenemos listo el editor de texto y, tambin, nuestro primer plugin, pero an no hay forma de indicarle a la aplicacin qu plugins queremos agregar.
Configuracin de plugins
Para que la aplicacin reconozca los plugins, vamos a agregar
una seccin al archivo de configuracin, donde indicaremos los
nombres y la ubicacin de cada uno de los que queremos utilizar.
Antes de continuar, agregaremos un archivo de configuracin a
nuestra aplicacin principal, utilizando el asistente de Visual Studio, que incorporar al proyecto un archivo llamado App.Config.
Por predefinicin, los valores que podemos agregar al archivo de
configuracin son pares de la forma llave y valor, que deben ir ubicados dentro del tag <appSettings>. Para nuestro caso, esta opcin
no es suficiente, ya que tenemos ms datos para configurar. Afortunadamente, el framework .NET nos provee de un mecanismo por el
cual podemos escribir nuestras propias secciones dentro del archivo
de configuracin, y nos ofrece una clase que se encarga de leer esa
seccin y traducirla para que la aplicacin pueda utilizar esos datos de configuracin. La forma de hacerlo es implementando la interfaz System.Configuration.IConfigurationSectionHandler. Esta expone un nico mtodo que recibe un nodo XML con el elemento de
nuestra seccin personalizada del archivo de configuracin, y debe
devolver un objeto que represente los datos de configuracin.
Entonces, debemos escribir una clase que represente los datos de
la configuracin. Para simplificar el ejemplo, la nuestra slo tendr una lista de los plugins instalados:
public class ConfiguracionPlugins
{
Definicin
Late binding (o enlace tardo) es la habilidad de crear
instancias de objetos cuyo tipo real no se conoce en
tiempo de compilacin, pero s, en tiempo de ejecucin.
La tcnica de late binding permite utilizar objetos
crendolos a partir del nombre de la clase al que
pertenecen, lo cual dota a las aplicaciones de una gran
flexibilidad. Cuando el tipo real del objeto se conoce en
tiempo de compilacin, se trata de un early binding (o
enlace temprano).
66
public ConfiguracionPlugins()
{}
private ArrayList pluginsInstalados =
new ArrayList();
public ArrayList PluginsInstalados
{
get{ return pluginsInstalados; }
}
}
Ahora que ya tenemos definida la clase para acceder a la configuracin, debemos escribir clase que se encargue de leer el archivo de configuracin xml y de crear el objeto correspondiente.
Agregamos una nueva clase a nuestro proyecto, la llamamos ConfiguracionPluginsSectionHandler y modificamos su declaracin
para indicar que implementa la interfaz IConfigurationSectionHandler. Como vimos antes, esta interfaz expone un solo mtodo
denominado Create, que recibe tres parmetros, de los cuales nos
interesa nicamente el ltimo, que, al momento de la invocacin
del mtodo, contiene el nodo XML correspondiente a nuestra seccin personalizada.
El formato de la seccin
Como vamos a tener que manipular un poco el documento XML,
antes de escribir el cdigo debemos tener bien definido el formato
de nuestra seccin. Queremos que nuestra aplicacin pueda tener
muchos plugins instalados, de modo que vamos a crear un elemento XML llamado <Plugins>, que podr contener a uno o ms elementos <plugin>. El elemento <plugin> tendr atributos para indicar el nombre de cada uno, el ensamblado donde se encuentra y
el nombre de la clase que lo implementa. Nuestra seccin personalizada quedar de la siguiente manera:
<Plugins>
<Plugin nombre="ContadorPalabras"
ensamblado="MisPlugins"
clase="MisPlugins.ContadorPalabras" />
</Plugins>
7/24/07
4:33 PM
Page 67
2 | C#
pluginsMenu.Add(menu, plugin);
mnuPlugins.MenuItems.Add( menu );
}
}
private void ClickMenuPlugin(object sender, EventArgs e)
{
MenuItem menu = (MenuItem)sender;
IPlugin plugin = (IPlugin)pluginsMenu[menu];
try
{
plugin.ContextoEditor = this;
plugin.Ejecutar();
}
catch(Exception ex)
{
MessageBox.Show("El plugin a generado una
excepcin con el mensaje: " + ex.Message,
"Error en Plugin", MessageBoxButtons.OK,
MessageBoxIcon.Error);
}
}
Conclusiones
A lo largo de este artculo, vimos los pasos necesarios para construir una aplicacin cuya funcionalidad pueda extenderse mediante el agregado de plugins. Lo ms importante es analizar con sumo
detalle qu elementos de la aplicacin principal queremos que sean
accesibles desde el plugin, para disear y escribir las interfaces de
manera apropiada.
67
7/24/07
6:02 PM
Page 68
Desarrollo guiado
por pruebas
Esta tcnica propone que los programadores escriban sus
propias pruebas, las implementen antes de escribir cada clase
y, luego, las programen con el fin de pasarlas.
68
rior. Segn esta prctica, es el programador quien realiza las pruebas de unidad de su propio cdigo, y las implementa antes de escribir el cdigo que debe ser probado.
Cuando recibe el requerimiento de implementar una parte del sistema, y una
vez que comprendi cul es la funcionalidad pretendida, debe empezar por
pensar qu pruebas tendr que pasar ese fragmento de programa (o unidad) para que se lo considere correcto. Luego, procede a programar; pero no el cdigo de la unidad que le toc, sino el que se va a encargar de llevar a cabo las
pruebas. Cuando est satisfecho de haber escrito todas las pruebas necesarias
(y no antes), comienza a programar la unidad, con el objetivo de pasar las
pruebas que program.
La forma de trabajo del programador tambin cambia mucho durante la
escritura del cdigo funcional propiamente dicho. En vez de trabajar durante horas o das hasta tener la primera versin en condiciones de ser probada, va creando pequeas versiones que puedan ser compiladas y pasen, por
lo menos, algunas de las pruebas. Cada vez que hace un cambio y vuelve a
compilar, tambin repite la ejecucin de las pruebas de unidad. Y trata de
que su programa vaya pasando ms y ms pruebas hasta que no falle en
ninguna, que es cuando se considera que est listo para ser integrado con
el resto del sistema.
Una de las grandes diferencias con el estilo de trabajo tradicional es que el
programador ya no compite con otro u otros, sino que lo hace consigo mismo.
Se enfrenta con el desafo de escribir cdigo que pase las pruebas que l mismo ha programado. Y, siguiendo las premisas de muchos de los mtodos giles, slo programar lo que sea estrictamente necesario para ese fin.
Pasemos a un ejemplo muy simple, para ilustrar cmo funciona este proceso. Supongamos que nos toca desarrollar un componente .NET que se encargue de ordenar un arreglo de valores enteros y que, como parte del diseo, se
ha decidido hacerlo mediante el algoritmo de bubble sort u ordenamiento por
burbujeo. Para los que no recuerden o no conozcan este algoritmo, consiste en
recorrer el arreglo intercambiando entre s los elementos contiguos que estn
en el orden inverso al buscado, y recomenzar ese procedimiento tantas veces
como sean necesarias, hasta que no queden pares por intercambiar.
7/24/07
6:02 PM
Page 69
2 | C#
Como estamos empleando TDD, tenemos que empezar por decidir qu pruebas vamos a hacer. Elegimos probar con varios
arreglos de cinco elementos que siempre sern los nmeros del
1 al 5. Y se nos ocurren las siguientes pruebas:
> Un arreglo ya ordenado.
> Uno que tenga solamente un par de elementos contiguos fuera
de orden.
> Uno que tenga el ltimo elemento al principio (y el resto en orden).
> Uno que tenga el primer elemento al final.
> Uno con el orden de sus elementos completamente invertido.
Tal vez no sea suficiente, pero yo me siento conforme. Pienso que
si logro escribir un programa que pase todas estas pruebas, habr
cumplido con lo pedido.
El paso siguiente es implementar las pruebas en forma de un programa que examine nuestro componente (que todava no existe).
Dado que todo desarrollador guiado por pruebas (y quien quiera hacer pruebas de unidad) tiene que crear frecuentemente programas
de este tipo, los entornos modernos de desarrollo ofrecen herramientas integradas que facilitan el trabajo. Veamos cmo hacerlo
en Microsoft Visual Studio Team System 2005, que es el entorno
que yo uso para trabajar en .NET. Pero los pasos por seguir no difieren mucho si se usa otro ambiente de ltima generacin.
Entre las extensiones que brinda Team System a la instalacin bsica de Visual Studio 2005, se agrega un tipo de proyecto entre los
que se sugieren en el momento de crear uno nuevo: Test Project,
como se muestra en la [Figura 1].
Para crear casos de prueba de unidad, Visual Studio se basa
fuertemente en los atributos que .NET permite asociar como me-
EL PROGRAMADOR IMPLEMENTA
LAS PRUEBAS DE SU PROPIO CDIGO,
ANTES DE ESCRIBIRLO.
plantilla de una clase de pruebas. Aunque no es necesario hacerlo as, antes de empezar a programar las pruebas, vamos a crear
el esqueleto de la clase que se va a encargar de hacer el ordenamiento, para tenerlo disponible con IntelliSense. Es una clase
muy simple, con un nombre asignado por defecto por Visual
Studio y un mtodo que ser el que efecte el ordenamiento:
using System;
using System.Collections.Generic;
using System.Text;
namespace Bubble1
{
class Class1
{
public void Sort(int[] arr)
{
}
}
}
69
7/24/07
6:02 PM
Page 70
/// <summary>
/// Prueba con el arreglo invertido.
/// </summary>
[TestMethod]
public void PruebaInv()
{
int[] arr = new int[] { 5, 4, 3, 2, 1 };
sorter.Sort(arr);
CollectionAssert.AreEqual(resp, arr);
}
}
}
> Un ejemplo del arreglo ordenado, para compararlo con el resultado de cada prueba:
70
7/24/07
6:02 PM
Page 71
2 | C#
Superando pruebas
Para avanzar, escribimos la primera versin operativa del mtodo Bubble1.Class1.Sort(int[] arr). Se trata de un ciclo que recorre
el arreglo invirtiendo los pares contiguos desordenados:
public void Sort(int[] arr)
{
for (int i = 0; i < arr.Length - 1; i++)
if (arr[i] > arr[i + 1])
{
int aux = arr[i];
arr[i] = arr[i + 1];
arr[i + 1] = aux;
}
}
Conclusin
71
7/24/07
4:45 PM
Page 72
Delegados en C#
Para asegurar el uso de punteros a funciones, C# 2.0 implementa los
delegados, que le agregan la caracterstica de orientacin a objetos.
En este artculo, analizamos su funcionalidad y su uso.
uando queremos enterarnos de los cambios en un sistema que estamos observando, solemos verificar su estado y, tal vez,
guardar el valor de la variable observada.
Con cierta frecuencia, repetimos esta accin, verificando una y otra vez el valor obtenido y
comparndolo con los anteriores. De la comparacin
entre dos valores distintos, podemos inferir cambios en
el estado del sistema observado.
Esta forma de registrar el comportamiento de un sistema no es la mejor en materia de recursos. Es evidente que, cuando aumenta la frecuencia de observacin
(medida como la cantidad de veces que obtenemos el
valor de una variable del sistema en una unidad de
tiempo), crece la sensibilidad a los cambios. Esto quiere decir que observaremos ms cambios (en realidad,
estaremos observando la misma cantidad, pero con una
resolucin mucho mayor).
Obviamente, esta situacin plantea una desventaja
muy importante, que, en su forma ms extrema, nos
lleva a utilizar ms recursos de lo debido, con lo cual
se provocan cambios en el comportamiento del sistema
observado como consecuencia de observarlo.
Otra forma de observar cambios en un sistema consiste en usar un mecanismo de observador / observado
(implementado en el observer pattern, por ejemplo). Este tipo de mecanismo permite la suscripcin a eventos
generados dentro de un sistema. De este modo, si se
producen los eventos que se estn observando, el mismo sistema nos avisar sobre los cambios, momento en
el cual medimos la variable de inters para determinar
el nuevo estado.
Por lo general, la segunda forma es mejor en cuanto
al uso de recursos. As, un mecanismo de suscripcin
suele resultar mucho mejor para la observacin de
cambios de estado de un sistema. Muchos lenguajes soportan esta funcionalidad a travs de la implementacin de mtodos callback (callback methods), que reciben direcciones a otros mtodos como parmetros.
Qu es un delegado?
Un delegado es un tipo del CLR que deriva de System.Delegate y que se utiliza para manejar punteros a funciones.
>
>
El poder de la ignorancia
Una ventaja de los delegados es que no conocen la clase de mtodos que encapsulan. Slo se requiere que sean consistentes con el tipo del delegado, lo
cual los convierte en una excelente opcin para la invocacin annima. Veremos este tema ms adelante.
De esta manera, el conocimiento que tiene el cliente acerca de la implementacin es nulo; simplemente, consume la funcionalidad implementada.
1. Declarar
La forma ms sencilla de declarar un delegado es la siguiente:
delegate void Delegado1();
Por supuesto que los delegados pueden tener tipo de retorno y/o parmetros
de funcin. Veremos cmo se utilizan ms adelante.
2. Instanciar
Delegate1 d = new Delegate1(Funcion1);
En este caso, Funcion1 es una funcin que no devuelve ningn valor. Deben
coincidir los tipos devueltos de la funcin y el delegado (a esto nos referimos
7/24/07
4:45 PM
Page 73
2 | C#
3. Invocar
Para utilizarla:
Todo junto
El siguiente ejemplo implementa los tres pasos en una aplicacin
de consola.
delegate void Delegate1(); // 1-Declarar
class Program{
static void Funcion1(){
Console.WriteLine("Funcion1 acaba de ser
invocada");
}
static void Main(string[] args) {
Delegate1 d = new Delegate1(Funcion1);
// 2-Instanciar
d(); // 3-Invocar
}
}
Ntese cmo el mtodo Funcion1() es invocado a travs del delegado, y no, directamente. Si bien el ejemplo es muy sencillo, no tiene mucho sentido, ya que la misma funcionalidad podra lograrse
llamando a Funcion1(). Por lo tanto, veamos otro caso.
Otro ejemplo
Este ejemplo implementa una clase Contador, que contiene dos
propiedades: Desde y Hasta, que definen un rango de accin para
el contador. Existe, adems, un mtodo Comenzar(), que cuenta
empezando en Desde hasta llegar a Hasta e informa a los observadores (clientes) sobre el nuevo valor. Con cada iteracin, el estado
interno del Contador cambia. Como dijimos antes, la clase informa
del cambio pasando el valor. Ms adelante veremos una modificacin a este cdigo.
Volviendo al ejemplo, cada vez que se produce la iteracin, avisamos
a todos los consumidores de la clase (clientes) que estn observando los
cambios de estado, que hubo una modificacin en el sistema.
class Test
{
public static void Aviso(int x) {
Console.WriteLine("Pasada numero {0}",
x.ToString());
}
}
class Program
{
static void Main(string[] args) {
// preparar
Contador c = new Contador();
c.Desde = 10; c.Hasta = 100;
// delegar
Paso pTemp = new Paso(Test.Aviso);
// Paso era el delegado
c.Comenzar(pTemp); // con cada pasada,
la funcion Aviso es invocada
}
}
De este modo, podemos proveer la funcionalidad deseada a travs de una clase o mtodo (en nuestro caso, la clase Test) para los
eventos generados en otra clase (clase Contador).
Veamos ahora una modificacin del ejemplo anterior, que agrega
una propiedad a Contador, llamada Value. sta ir cambiando con
cada pasada del mtodo Comenzar() y representar los distintos
estados de Contador. As, en vez de pasar el valor como parmetro, se cambiar el valor de la propiedad Value y se informar a los
observadores que hubo un cambio en el objeto.
Los observadores de este ejemplo tienen cierto conocimiento
sobre Contador, por lo que averiguan su estado consultando la propiedad Value.
Archivo: Contador.cs
delegate void Paso(int x);
class Contador {
private int _Desde, _Hasta;
public int Desde
{
get { return _Desde;}
set { _Desde = value; }
}
public int Hasta
{
get { return _Hasta; }
set { _Hasta = value; }
}
73
7/24/07
4:45 PM
Page 74
c.Comenzar(pTemp);
// para detener
Console.Read();
Contador.cs
delegate void Paso(Contador contador);
class Contador
{
private int _Desde, _Hasta;
private int _Value;
public int Value
{
get { return _Value; }
set { _Value = value; }
}
public int Desde { get { return _Desde; }
set { _Desde = value;}}
public int Hasta { get { return _Hasta; }
set {_Hasta = value;}}
public void Comenzar(Paso p){
for (int i = this.Desde; i < this.Hasta; i++){
this.Value = i;
p(this); // delegar
}
}
}
Test.cs
class Test
{
public static void Aviso(Contador c)
{
Console.WriteLine("Pasada numero {0}", c.Value);
}
}
Program.cs
class Program
{
static void Main(string[] args)
{
// preparar
Contador c = new Contador(); // instanciar
c.Desde = 10; c.Hasta = 100;
// delegar
Paso pTemp = new Paso(Test.Aviso);
74
}
}
Ms ejemplos
Un cdigo como el siguiente utiliza delegados (delegate o System.Delegate) para mantener informado al usuario sobre el avance
de una tarea que ocurre dentro de una clase, por medio del uso de
una barra de progreso. As, Aviso() podra actualizar controles de
interfaz, como en el ejemplo siguiente:
class Proceso {
public delegate void Paso(int x);
private int _Desde, _Hasta;
public int Desde
{
get { return _Desde; }
set { _Desde = value; }
}
public int Hasta
{
get { return _Hasta; }
set { _Hasta = value; }
}
public void Comenzar(Paso p)
{
for (int i = this.Desde; i < this.Hasta; i++)
{
p(i);
for (int j = 0; j < 100000; j++) ; // delay
}
}
}
7/24/07
4:45 PM
Page 75
2 | C#
Resumen
Hemos visto que un delegado es un mecanismo para implementar llamadas a funciones. Por supuesto, su uso puede ampliarse a
cualquier observador. Tambin aprendimos cmo tratar los delegados y vimos que un delegado contiene una copia de la instancia, si el mtodo asociado es de instancia, o slo el mtodo, si ste es esttico.
75
7/24/07
6:03 PM
Page 76
Procesamiento automtico
de archivos de texto
Pese a que XML sigue creciendo, por lo general usamos archivos de texto
con registros de ancho fijo o delimitados. Podemos recurrir a la librera
FileHelpers para procesar estos archivos sin mucho cdigo.
Comencemos a trabajar
Empecemos con un ejemplo sencillo para clarificar
las cosas. Supongamos que tenemos que leer y escribir
archivos con el siguiente formato:
using FileHelpers;
[DelimitedRecord(",")]
public class Cliente
{
public int Codigo;
public string Nombre;
public decimal Saldo;
[FieldConverter(ConverterKind.Date, "ddMMyyyy")]
public DateTime FechaAlta;
}
Para los ms desorientados, esas etiquetas encerradas entre [ ] son los atributos en C#, que nos permiten agregar esa metadata que los FileHelpers necesitan para identificar el formato de cada registro. Al mirar el ejemplo, queda claro que cada cliente es un registro delimitado por , y que el ltimo campo es
un campo fecha que tiene el formato ddMMyyyy. He aqu otra ventaja de utilizar la librera: el cdigo queda autodocumentado; si lo hubisemos hecho a
la antigua, el formato del archivo no estara explicito en ningn lado y habra
que deducirlo del cdigo. Ahora sigamos con el cdigo necesario para leer y
escribir estos archivos con los FileHelpers y abracadabra:
// Le decimos qu clase queremos manejar
2732,Juan Perez,435.00,11052002
554,Pedro Gomez,12342.30,06022004
112,Ramiro Politti,0.00,01022000
924,Pablo Ramirez,3321.30,24112002
..
76
Como vemos, es una lnea de cdigo para cada operacin. Por debajo la librera se encarga de todo lo que antes describimos, pero de manera totalmente transparente, lo cual nos deja ms tiempo para desarrollar el resto del programa. En el CD que acompaa a la revista o a travs de Internet pueden encontrar el cdigo completo de estos ejemplos.
7/24/07
6:03 PM
Page 77
2 | C#
Imports FileHelpers
<DelimitedRecord(",")> _
Public Class Cliente
Public Codigo As Integer
Public Nombre As String
Public Saldo As Decimal
<FieldConverter(ConverterKind.Date, "ddMMyyyy")> _
Public FechaAlta As DateTime
End Class
File Helpers
DataStorages y DataLinks
[FieldFixedLength(5)]
pulic int Codigo;
[FieldFixedLength(20)]
[FieldTrim(TrimMode.Right)]
public string Nombre;
[FieldFixedLength(8)]
[FieldConverter(typeof(TwoDecimalConverter))]
public decimal Saldo;
[FieldFixedLength(8)]
[FieldConverter(ConverterKind.Date, "ddMMyyyy")]
public DateTime FechaAlta;
004350011052002
123423006022004
000000001022000
033213024112002
Como vemos, la definicin est un poco ms cargada, ya que necesitamos ms metadata para poder describirla por completo.
Cada campo de una clase definida como FixedLengthRecord debe tener el atributo FieldFixedLength sobre cada uno de ellos. Como se ve en el ejemplo, es muy claro y simple modificar la cantidad de caracteres que abarca cada campo.
En la clase hay, adems, un atributo FieldTrim(TrimMode.Right),
que provee la librera para eliminar los caracteres en blanco que
queremos descartar durante la lectura.
Finalmente, lo ms extrao puede que sea el atributo FieldConverter (typeof (TwoDecimalConverter)). ste les dice a los FileHelpers que, para convertir un string al valor del campo o viceversa,
use el conversor especificado y no el que tiene predefinido.
Todo conversor personalizado debe heredar de la clase ConverterBase y sobrescribir sus dos mtodos StringToField y FieldToString;
en este caso, la implementacin del conversor sera:
La clase bsica de la librera para leer y escribir registros en una sola operacin.
FileHelperEngine<T>
FileHelpersAsyncEngine
FileHelpersAsyncEngine<T>
MasterDetailEngine
Permite manipular archivos con dos tipos de registros que tienen una relacin maestro
detalle.
FileTransformationEngine
CommonEngine
Una clase con mtodos estticos que simplifican el procesamiento, ya que no se necesita
crear ningn objeto.
ExcelStorage, AccessStorage
y SqlServerStorage
77
7/24/07
6:03 PM
Page 78
}
}
Manejo de errores
Hasta aqu, todo perfecto, pero todos sabemos que, al leer cada registro, pueden aparecer diferentes errores de formato. Para
manejarlos, los FileHelpers tienen diferentes opciones, que se
78
7/24/07
6:03 PM
Page 79
2 | C#
aplican tanto a las operaciones de lectura como a las de escritura. Todos los motores de procesamiento cuentan con un objeto
ErrorManager, al cual le podemos indicar cmo debe reaccionar
ante un error con la propiedad ErrorMode, que es un enumerado
y puede recibir estos valores:
> ErrorMode.ThrowException
ste es el comportamiento predefinido, e indica que, cuando se
encuentra un error, se levanta una excepcin y se detiene el procesamiento. No es del todo adecuado, ya que perdemos los datos ledos hasta el momento y no podemos continuar.
> ErrorMode.IgnoreAndContinue
En este modo la librera slo devuelve los registros que no contienen errores, y descarta completamente los dems.
>ErrorMode.SaveAndContinue
Es el comportamiento ms avanzado. Funciona como el anterior, pero en vez de descartar los errores, los almacena internamente para que, luego, podamos recuperarlos.
Para recuperar estos errores usamos la propiedad ErrorCount y
Errors de la clase ErrorManager, de la siguiente manera:
engine.ErrorManager.ErrorMode = ErrorMode.
SaveAndContinue;
Cliente[] clientes = (Cliente[]) engine.ReadFile
("clientes.txt");
if (engine.ErrorManager.ErrorCount > 0)
{
engine.ErrorManager.SaveErrors("errores.txt");
}
tabla. En la documentacin pueden encontrar informacin acerca de cmo usar estas caractersticas.
Otra funcionalidad importante introducida en las ltimas versiones es la capacidad de convertir archivos de un formato al otro. Si
volvemos al ejemplo inicial, basta con agregar a la clase de ancho
fijo el siguiente mtodo:
[TransformToRecord(typeof(Cliente2))]
public Cliente2 CrearSimilar()
{
Cliente2 res = new Cliente2();
res.Codigo = this.Codigo;
res.Nombre = this.Nombre;
res.Saldo = this.Saldo;
res.FechaAlta = this.FechaAlta;
return res;
}
Usos avanzados
La librera no se limita, simplemente, al procesamiento de archivos planos, sino que, adems, permite importar y exportar datos de
otros formatos o servidores.
Entre otros, permite obtener e insertar registros en archivos de
Microsoft Excel y Access, como as tambin conectarse a una base de datos SQL Server y extraer o insertar registros en alguna
Tambin tenemos la posibilidad de manejar archivos con formato del estilo maestro-detalle. La clase responsable de hacerlo es
MasterDetailEngine, que est en el namespace FileHelpers.MasterDetail; en la documentacin encontrarn la manera de usarlo.
Recursos relacionados
Pgina oficial de los FileHelpers
> http://filehelpers.sourceforge.net
Foros con ideas y preguntas
> http://filehelpers.sourceforge.net/forums
File Helpers
DataStorages y DataLinks
79
7/24/07
4:37 PM
Page 80
Morfologa de la solucin
Siempre que pensamos en una aplicacin escalable,
lo primero que nos planteamos es de qu manera estar conformada la estructura, o si se quiere, el esqueleto de la aplicacin. Este planteo lleva a que el equipo
de arquitectos distribuya la estructura en diferentes capas (tiers), que cumplen una funcin especfica dentro
de la aplicacin. Por ejemplo, una aplicacin, bsicamente, contar con una capa de presentacin, que
muestra la informacin que el usuario necesita por
pantalla y la funcionalidad establecida para el tipo de
informacin que va a mostrar; una capa de negocio,
Capa de presentacin
Capa de negocios
Capa de acceso a datos
Base de datos
[Figura 1] Representacin bsica de la separacin por capas de una aplicacin.
80
Servicios externos
7/24/07
4:37 PM
Page 81
2 | C#
Presentacin
Cliente ABM
Pgina ASPX
<<uses>>
Negocio
Cliente
<<uses>>
Datos
Acceso a datos
Caso prctico
Una vez que hemos explicado el funcionamiento de ObjetctDataSource, es tiempo de pasar a una demostracin, para tener una idea
ms acabada de su uso y, as, poder implementarlo. Para esto, realizaremos un ejemplo que sintetice su funcionamiento.
En este caso, crearemos un objeto de negocio que manejar el
ABM (alta, baja y modificacin) de clientes de una compaa, como as tambin, un objeto de entidad que representar a un cliente especfico dentro de la capa de negocio. Por lo tanto, crearemos
una clase denominada Clients, que tendr, dentro de sus miembros,
las cuatro operatorias bsicas de negocio relacionadas al ABM de
un cliente: GetClients, AddClient, UpdateClient y DeleteClient.
Tambin generaremos una clase Client en la que se almacenar la
informacin correspondiente al cliente, mediante las propiedades
Id, FirstName, LastName y Address.
namespace Product.Customer
{
public class Clients
{
public Clients()
{
}
public Client[] GetClients()
{
Client[] client = new Client[];
//TODO: objeto de acceso a datos.
return client;
}
public void AddClient(string firstname, string
lastname, string address)
{
Client client = new Client();
client.FirstName = firstname;
client.LastName = lastname;
client.Address = address;
AddClient(client);
81
7/24/07
4:37 PM
Page 82
}
public void AddClient(Client client)
{
//TODO: objeto de acceso a datos para insercin.
}
public void UpdateClient(int id, string firstname,
string lastname, string address)
{
Client client = new Client();
client.Id = id;
client.FirstName = firstname;
client.LastName = lastname;
client.Address = address;
UpdateClient(client);
}
public void UpdateClient(Client client)
{
//TODO: objeto de acceso a datos para actualizacin.
}
public void DeleteClient(int id)
{
//TODO: objeto de acceso a datos para borrado.
}
}
public class Client
{
Una vez creado el objeto entidad y el objeto de negocio en su capa correspondiente el cual asumimos que trabaja contra la capa
de acceso a datos para realizar la persistencia, modificacin, borrado y seleccin, pasaremos a crear la pgina ASPX que contendr
tanto el control de presentacin de datos, como el ObjectDataSource que vincular el objeto de negocio al control de presentacin.
Para hacerlo, seguimos este procedimiento. En primer trmino,
agregamos un control que soporte el enlace con ObjectDataSource.
Para saber si ese control soporta el enlace, slo tenemos que ver si
posee la propiedad DataSourceID; de ser as, significa que lo soporta. Para representar nuestro ejemplo usaremos el control FormView, que permite listar un conjunto de registros, pero de a uno por
vez, mediante el uso de una plantilla que puede estar constituida
por subcontroles, como Labels, TextBox, etc.
<asp:FormView ID="frmViewCustomer" runat="server"
DataSourceID="objDs" DataKeyNames="Id"
AllowPaging="true">
<ItemTemplate>
<table>
<tr><td>
<asp:Button ID="btnEdit" runat="server"
Text="Editar" CommandName="edit" />
<asp:Button ID="btnNew" runat="server"
Text="Nuevo" CommandName="new" />
<asp:Button ID="btnDelete"
runat="server" Text="Delete"
CommandName="delete" />
</td></tr>
<tr><td>
Nombre:
<asp:Label runat="server" ID="firstName"
Text='<%# Bind("FirstName") %>'></asp:Label>
</td></tr>
<tr><td>
Apellido:
<asp:Label runat="server" ID="lastName"
Text='<%# Bind("LastName") %>'></asp:Label>
</td></tr>
<tr><td>
Direccin:
<asp:Label runat="server" ID="Address"
Text='<%# Bind("Address") %>'></asp:Label>
</td></tr>
</table>
</ItemTemplate>
<EditItemTemplate>
</EditItemTemplate>
<InsertItemTemplate>
</InsertItemTemplate>
</asp:FormView>
Propiedad
Descripcin
SelectMethod
Carga u obtiene el nombre del mtodo que invoca el ObjectDataSource para obtener informacin
del origen de datos.
UpdateMethod
Carga u obtiene el nombre del mtodo que invoca el ObjectDataSource para actualizar informacin.
DeleteMethod
Carga u obtiene el nombre del mtodo que invoca el ObjectDataSource para eliminar informacin
del origen e datos.
InsertMethod
Carga u obtiene el nombre del mtodo que invoca el ObjectDataSource para insertar nuevos
registros al origen de datos.
82
7/24/07
4:37 PM
Page 83
2 | C#
Eventos ObjectCreating,
ObjectCreated y ObjectDisposing
Al momento de utilizar el control ObjectDataSource, hay que tener
en cuenta que cada invocacin que hace el control a un mtodo en
particular genera un ciclo de vida del objeto en forma independiente respecto de la siguiente invocacin; o sea, por cada llamado se
crear el objeto, lo utilizar el ObjectDataSource y, luego, el objeto
de negocio ser destruido. Por lo tanto, si el diseo de un objeto de
negocio fue pensado para realizar una gran cantidad de tareas lo
cual implica que sea grande y pesado para procesar, tal vez el uso
del ObjectDataSource deje de ser una buena forma para tratar el tema de acoplamiento entre la capa de presentacin y la de negocio.
O, quiz, sea momento de volver a pensar en el diseo de los objetos de negocio, para hacerlos ms modulares y, como consecuencia,
ms pequeos. En fin, en caso de que no sea posible redisear un
objeto de negocio pesado y, aun as, se quiera utilizar ObjectDataSource, una de las maneras de controlar el ciclo de vida de los objetos que crea el objeto de negocio podra ser mediante el uso de los
enventos ObjectCreating, ObjectCreated y ObjectDisposing, que permiten controlar el ciclo de vida de los objetos que son creados por
ObjectDataSource para invocar su funcionalidad de negocio.
La manera en que funcionan estos eventos es un tema bastante
largo como para incluirlo en unas pocas lneas, pero, por lo menos,
tengan en cuenta que hay una solucin al problema, aunque la
desventaja que tiene respecto del uso de objetos de negocio pequeos es que la forma de uso declarativa de ObjectDataSource ya dejara de servir. Mejor dicho, ya no podra ser la nica forma de en-
Presentacin
Cliente ABM
ObjectDataSource
Negocio
Datos
Cliente
Acceso a datos
carar la solucin, puesto que es necesario programar los manejadores (handler) de los eventos para tener control sobre el ciclo de
vida de los objetos en cuestin mediante el uso de una cach que
guarde los objetos usados y, por medio de estos eventos, preguntar
si el objeto ya existe. De existir, hay que recuperarlo y pasrselo al
ObjectDataSource; de lo contrario, habr que crear uno nuevo.
Recordar que
El ObjectDataSource es un control que funciona muy bien, aunque, al momento de usarlo, como programadores, deberamos tener
en cuenta ciertos aspectos que harn a su buen desempeo y evitarn cualquier posible inconveniente que podra surgir como consecuencia de su uso.
En primer lugar, es preciso saber que el ObjectDataSource crea y
destruye las instancias del objeto de negocio creado por cada mtodo llamado. Esto significa que no mantiene en memoria el objeto de negocio por el tiempo de vida del request que se genera como consecuencia de una solicitud al Server. Por lo tanto, es recomendable usarlo slo con objetos de negocio que no sean demasiado pesados. La alternativa para mantener el control sobre el ciclo
de vida de los objetos es utilizar los eventos ObjectCreating, ObjectCreated y ObjectDisposing.
Conclusin
El uso de ObjectDataSource es una buena opcin, porque deja
el cdigo de la capa de presentacin mucho ms limpio y, por lo
tanto, ms amigable al momento de realizar el mantenimiento
del producto, considerando que la vinculacin entre el objeto de
negocio y el control de presentacin puede realizarse ntegramente en forma declarativa.
Evento
Descripcin
ObjectCreating
ObjectCreated
ObjectDisposing
83
7/24/07
4:39 PM
Page 84
USO DE METADATOS
Reflection con C#
Reflector
Veremos un ejemplo prctico de cmo usar la reflexin.
Hay otras herramientas ms interesantes que proporcionan hasta algo del cdigo fuente de un programa compilado, lo cual puede ser peligroso. Una de las herramientas se llama Reflector y se puede bajar gratuitamente desde www.aisto.com/roeder/dotnet. Reflector es un navegador de clases para componentes .NET. Soporta vistas a nivel de espacios de nombres y ensamblados, bsqueda de
tipos y miembros, adems de documentacin XML, rboles de llamadas y grficos. Tambin es decompilador de
Visual Basic, Delphi y C#, rboles de dependencias y ms.
Ejemplo prctico
Veremos un ejemplo sencillo en el que trataremos de
identificar todas las clases, tipos, delegados y algunos
otros datos ms que el programa contiene, informando
por pantalla, en consola. Empezaremos por el primer
archivo, que deja una clase disponible con todo tipo de
miembros de clase para hacer actuar una reflexin sobre ella; sta es su nica misin:
public class Reflected
{
public
int
MyField;
protected ArrayList Array;
nimos el array
public Reflected()
// aca defi-
{
Array = new ArrayList();
// creamos el objeto
Array.Add("Datos que iran en el array"); // agregamos un dato
}
public float MiPropiedad
{
get
{
return MiEvento();
}
84
7/24/07
4:39 PM
Page 85
2 | C#
}
Ms reflexin
}
public static float MyStaticMethod()
{
Console.WriteLine("Invocando MyMethod Esttico.");
return 0.02f;
}
public delegate float Delegado();
public event Delegado MiEvento =
new Delegado(MyStaticMethod);
public enum Enum { valOne, valTwo, valThree };
}
reflect.GetReflectionInfo(Ensamblado);
Console.ReadLine();
PropertyInfo[] MyProperties =
type.GetProperties();
85
7/24/07
4:39 PM
Page 86
}
}
Int32 GetHashCode()
Bolean Equals(System.Object)
System.String ToString()
System.Type GetType()
Type: Reflected
86
object reflectedObject =
Activator.CreateInstance(classType);
propGet.Invoke(reflectedObject, null);
MethodInfo myMethod =
classType.GetMethod("MyInstanceMethod");
myMethod.Invoke(reflectedObject, null);
}
}
Conclusin
Hemos visto que la reflexin nos permite descubrir informacin bastante avanzada sobre un programa, y el mismo Microsoft ha lanzado productos destinados a proteger los ensamblados. Este tipo de investigaciones facilita muchas tareas, pero
tambin hace que terceros puedan mirar nuestro cdigo y hasta
robar partes de l.
Por otro lado, podemos activar cdigo dinmicamente, crearlo y
guardar nuevos programas generados segn nuevas condiciones
que se presenten dentro de un sistema dado, como compiladores y
motores de guiones.