viernes, 19 de diciembre de 2008

Esperas paralelas

La programación concurrente suele tener sus particularidades que hace que mucha gente la evite siempre que puede lo que es una lástima porque muchas cosas se pueden hacer mejor cuando se aprovecha la posibilidad de que un proceso ejecute paralelo al actual. Es también una lástima porque luego aparecen muchas apliaciones que a pesar de ser muy útiles hay momentos en que quedan paradas si hacer nada en lo que se ejecuta algún proceso que demora mucho y que tampoco podemos interrumpir.

Uno de los errores de implementación que mas me encuentro cuando alguien nuevo en la materia intenta programar algo en paralelo son las esperas. Necesitamos esperar generalmente porque estamos a espera de algo que no ha sucedido, como que esté disponible algún recurso o que algún proceso termine. Además del hecho mismo de esperar, el programador consciente se protege de la eventualidad de que aquello por lo que estamos esperando nunca suceda.

Aunque en .NET hay varias formas de implementar las esperas, aparentemente lo primero que se le ocurre a mucha gente es esperar durante un tiempo, probablemente porque es lo mas fácil y porque existen equivalentes cuando no se programa en paralelo. Generalmente esta espera viene en dos sabores:

El del programador inconsciente:

using System;

namespace paralelos
{
class Program
{
static volatile bool EsperaPorMi = false;

static void Main(string[] args)
{
// Mandamos a ejecutar un proceso paralelo
System.Threading.ThreadPool.QueueUserWorkItem(Paralelo);

// Supuestamente aqui hacemos otras cosas antes de decidir esperar
// ...

System.Threading.Thread.Sleep(6000); // Esta es la espera

// La finalización del proceo paralelo será marcada por el valor de la
// variable EsperaPorMi
if (EsperaPorMi)
Console.WriteLine("El proceso paralelo ha terminado");
else
Console.WriteLine("El proceso paralelo no ha terminado");
Console.WriteLine("\nPresione cualquier tecla para salir");
Console.ReadKey();
}

// Este es el proceso paralelo
static void Paralelo(object estado)
{
// Esto es para simular una operación que demora cinco segundos
System.Threading.Thread.Sleep(5000);
EsperaPorMi = true;
}
}
}


Aqui el programador inconsciente supone que sabe cuanto debe demorar la operación paralela y por lo tanto lo único que tiene que hacer es esperar un poco mas y asi garantiza que cuando deja de esperar la operación ya terminó.

Desafortunadamente esta solución tiene varios fallos obvios.
Para empezar, determinar con exactitud el tiempo que demora una sección de código de un programa es poco menos que imposible en el 95% de los casos en un sistema convencional. Estos tiempos dependen mucho de la configuración sobre la que ejecuta el programa ( procesador y sus nucleos, otros programas instalados y corriendo, procesos del sistema operativo, eventos que ocurran como la abertura de una torre de cd o un acceso remoto al pc, y un largo etcétera ). Si además la operación por la que estamos esperando tiene que acceder a recursos fuera de la gestión pura del programa ( archivos, bases de datos, etc. ) mas impredecible se torna. Finalmente y en caso de que la operación sea muy demorada ( y por tanto también la espera ) mas nada es hecho durante ese tiempo y por tanto se pierde en parte el beneficio de la ejecución paralela.

El programador consciente toma en cuenta los problemas anteriores y hace esto:

using System;

namespace paralelos
{
class Program
{
static volatile bool EsperaPorMi = false;

static void Main(string[] args)
{
// Mandamos a ejecutar un proceso paralelo
System.Threading.ThreadPool.QueueUserWorkItem(Paralelo);

// Supuestamente aqui hacemos otras cosas antes de decidir esperar
// ...

Console.WriteLine("Presione cualquier tecla para interrumpir");
// Ahora viene la espera
while (!EsperaPorMi)
{
System.Threading.Thread.Sleep(500); // Dormimos poco

if (Console.KeyAvailable) // El usuario se cansó de esperar ?
{
Console.Read();
break;
}
}

// La finalización del proceo paralelo será marcada por el valor de la
// variable EsperaPorMi
if (EsperaPorMi)
Console.WriteLine("El proceso paralelo ha terminado");
else
Console.WriteLine("El proceso paralelo no ha terminado");
Console.WriteLine("\nPresione cualquier tecla para salir");
Console.ReadKey();
}

// Este es el proceso paralelo
static void Paralelo(object estado)
{
// Esto es para simular una operación que demora cinco segundos
System.Threading.Thread.Sleep(5000);
EsperaPorMi = true;
}
}
}

En este caso el programador no pretende saber cuanto demora el proceso paralelo asi que en vez de esperar dormido durante tiempo suficiente lo que hace es verificar continuamente el estado de la variable de terminación y además verifica si el usuario pulsó una tecla indicando que ya no quiere esperar mas. Sin embargo, consciente como es, entre consulta y consulta duerme un poco, por que ?

El encargado de ejecutar las instrucciones de nuestro programa ( o mas estrictamente hablando , en lo que ellas se convierten ) es el procesador de la computadora. En un pc suelen haber en la mayoría de los casos de 1 a 4 procesadores o nucleos de procesadores con lo cual en cada momento solo pueden estarse ejecutando tantos procesos como procesadores x nucleos tengamos. Si nos damos un paseo por el task manager ( Ctrl+Shift+Esc ) y vamos a la pestaña de procesos vamos a ver que potencialmente hay muchos procesos que pueden necesitar de ejecutar y cada uno de ellos puede estar formado por muchos hilos cada uno de los cuales también necesita correr. El sistema operativo "planifica" al procesador para que le dedique un intervalo de tiempo a cada proceso/hilo y pasado ese tiempo pase a otro; eso ocurre muchas veces en cada milisegundo con lo cual nos da la idea de que se pueden ejecutar en paralelo mas procesos/hilos de los que serían posibles a juzgar por la cantidad de procesadores y nucleos que tengamos. Por lo tanto podemos concluir que el tiempo de procesador es un recurso bastante escaso, por eso el sistema no planifica al procesador ciegamente y una de las optimizaciones básicas que hace es no darle tiempo de procesador a quien no lo necesita, por lo tanto si un hilo está "dormido" por cualquier motivo el sistema no le dará tiempo de procesador con lo cual da la posibilidad de ejecutar otros que si lo necesitan. En el ejemplo anterior lo único que se hace entre espera y espera es ver si se ha precionado una tecla por lo tanto nos podemos tomar la libertad de dormir un poco ( 1/2 segundo ) antes de verificar el teclado y como el tiempo es tan corto el usuario ni se da cuenta. Si no lo hicieramos asi veríamos como aumenta el consumo de procesador con lo cual estamos ocupando sin motivos el tiempo que podría dedicarse a otro programa relentizando el performance general de la máquina.

Esta segunda solución es mejor que la primera y mas lógica, sin embargo hay casos en que ese medio segundo puede resultar mucho tiempo, especialmente si el programa ejecuta en un computador muy rápido. En esos casos no tiene sentido esperar mas de lo necesario y entonces lo que necesitamos es quedarnos dormidos y que sea el proceso paralelo el que nos despierte:

using System;

namespace paralelos
{
class Program
{
static System.Threading.ManualResetEvent EventoManual =
new System.Threading.ManualResetEvent(false);

static void Main(string[] args)
{
EventoManual.Reset(); // Nos aseguramos que no está señalizado
// Mandamos a ejecutar un proceso paralelo
System.Threading.ThreadPool.QueueUserWorkItem(Paralelo);

// Supuestamente aqui hacemos otras cosas antes de decidir esperar
// ...

Console.WriteLine("Presione cualquier tecla para interrumpir");
// Ahora viene la espera
while (true)
{
if (EventoManual.WaitOne(500)) // Dormimos solo lo necesario
{
// Si fue señalizado salimos con éxito
Console.WriteLine("El proceso paralelo ha terminado");
break;
}

if (Console.KeyAvailable) // El usuario se cansó de esperar ?
{
// No ha sido señalizado y el usuario ya no quiere esperar mas
// salimos sin éxito
Console.Read();
Console.WriteLine("El proceso paralelo no ha terminado");
break;
}
}

Console.WriteLine("\nPresione cualquier tecla para salir");
Console.ReadKey();
}

// Este es el proceso paralelo
static void Paralelo(object estado)
{
// Esto es para simular una operación que demora cinco segundos
System.Threading.Thread.Sleep(5000);
EventoManual.Set();
}
}
}


Aqui hemos quitado la variable de espera y la hemos sustituido por un evento manual. El evento tiene dos estados: señalizado y no señalizado. En su estado señalizado si llamamos al método WaitOne este retorna inmediatamente mientras que en el estado no señalizado el mismo llamado provoca que la ejecución quede bloqueada hasta que alguien ( otro proceso/hilo ) señalice al evento y desbloquee nuestra ejecución. Además el método WaitOne tiene la opción de pasarle el tiempo que queremos quedar bloqueados y si este pasa el retorna el valor false diciendo que nos desbloqueamos porque pasó el tiempo que le dijimos mientras que si fuimos desbloqueados por otro proceso retorna true.

En .NET tenemos muchas herramientas para tratar con los problemas que la concurrencia plantea, solo que es necesario saber usar los adecuados en casa caso.

viernes, 2 de mayo de 2008

Mejor me voy

La informática es una tecnología que nos afecta a todos de forma directa o indirecta por eso estar informado sobre lo que pasa en el mundo de los ordenadores es algo que muchos consideran interesante y otros hasta importante. Desafortunadamente estar informado en estos temas no es siempre facil ya que cada noticia puede venir "contaminada" de terminología propia del ramo que no todos consiguen entender y es muy facil caer en malos entendidos. Para empeorar la situación, esta información es bastante escasa en su riqueza en el mundo hispanoparlante por eso blogs como el de Enrique Dans son de gran valor para mucha gente que quiere saber lo que pasa sin tener que pasar un curso de informática y otro de inglés.

Recientemente se ha armado un revuelo en el blog de E. Dans debido a que este se hace eco de una noticia acerca de una herramienta de Microsoft que él cataloga como posible backdoor y contra la cual muestra su desagrado. El Sr. Dans usa una palabra que le suena cool para hacer algo que hoy en dia también es cool: atacar a Microsoft. La superficie de ataque a Microsoft es bastante amplia ya que de compañía innovadora a pasado a dinosaurio en un mundo donde todo cambia cada dia y para colmo ha intentado mantenerse en su posición no usando productos de calidad para competir sino la fuerza de su peso. Hasta ahi muy bien. El problema está en que el Sr. Dans hasta el momento de la redacción de su post parece que no tenía la menor idea de lo que era un backdoor por lo que en su post además de su opinión ( tan respetable como la de cualquiera, especialmente dentro de su propio blog ) plantea un hecho que no es cierto.

La reacción no se hizo esperar. Desde los primeros comentarios le intentaron explicar que no, que eso no era una puerta trasera y que el programa en cuestión no necesariamente era un arma del maligno sino una herramienta forense de las tantas que existen y que además no tiene nada que ver con la seguridad de Windows porque tales existen para cualquier sistema operativo.

El hecho de que el creador del sistema operativo sea el que oferte tales herramientas puede ser perfectamente cuestionable y por ahi iba el post, pero se le fue la mano y mezcló las cosas usando temas y términos que desconocía. A esas alturas en otros sitios hubiera aparecido un update aclarando las cosas, no cambiando de opinión sino esclareciendo los terminos, por que ? Pues porque en el mundo hispanoparlante las fuentes de información sobre tecnología no son tan abundantes como en el anglosajón y el Sr. Dans es tenido como "experto" no solo por sus fieles lectores sino también por otras publicaciones. Además, llamarle a una cosa por otro nombre solo porque suena mejor no solo es incorrecto sino que puede caer en el ámbito de la difamación. Que Microsoft ya ha difamado mucho ? Pues si, por eso ( y por muchas otras cosas ) no es de las empresas mas queridas que digamos, pero eso no justifica que nosotros debamos hacer lo mismo.

La respuesta del Sr. Dans ha sido otra entrada donde arremete contra los que le han criticado ( algunos de forma grosera, la verdad sea dicha ) diciendo que en su blog escribe lo que le da la gana y relatando, a modo de constatar autoridad, lo muy leido y referenciado que es. Es decir. Enrique Dans es un formador de opinión y lo sabe. A lo mejor por eso debería tener mas cuidado en investigar lo que va a escribir cuando va a utlizar tecnicismos que no conoce o que no entiende del todo. Pero quién soy yo para decirle a él lo que debe hacer o como debe hacerlo. Por otro lado ni él ni sus fieles son amigos de las cosas técnicas. Hubo un lector que se deshizo en explicaciones para tratar de hacer entender de que iba el tema a otro. Nada. Como con una pared, hasta la guerra en Iraq estuvo por ahi.

Al final lo que sucede es que los lectores de perfil técnico abandonan. Si, porque nosotros no somos tan dados a los debates políticos donde las partes no quieren entender ni saber la verdad, les basta con tener la razón y no la tenemos todos ? Por mi parte me limito a leer los escritos de Enrique en el sitio del Instituto de Empresa que de lo suyo si sabe un montón. A su blog ? Gracias, pero no gracias.

sábado, 12 de abril de 2008

Pérdidas de memoria en .NET

Una de las mejores cosas de las plataformas "manejadas" como Java y .NET es la posibilidad de no tener que preocuparnos con los detalles del control del uso de la memoria, sin embargo hay situaciones que pueden provocar pérdidas de memoria si no las tenemos en cuenta como intenta explicar Calvin Hsia en Examine .Net Memory Leaks .

Este es un tema que muchas veces confunde, asi que me tomé la libertad de copiar su ejemplo y llevarlo a C#.


using System;

namespace Test
{
class Program
{
static void Main(string[] args)
{
var oldPeak = 0L;
for (int i = 0; i < 100; i++)
{
var oWatcher = new MyWatcher();
// oWatcher.UnSubscribe(); // Quitar comentario para quitar el controlador
oWatcher = null;
GC.Collect(); // Recolectar
GC.WaitForPendingFinalizers(); // Esperar por los finalizadores ( destructures )
GC.Collect(); // Recolectar los que tienen destructores
var newPeack = System.Diagnostics.Process.GetCurrentProcess().PeakWorkingSet64;
if (i > 0)
Console.WriteLine("Todo liberado? {0} WorkingSet ={1} Pico ={2} Delta ={3}",
i, System.Diagnostics.Process.GetCurrentProcess().WorkingSet64,
newPeack, (newPeack - oldPeak));
oldPeak = newPeack;
}
GC.Collect(); // Recolectar
GC.WaitForPendingFinalizers(); // Esperar por los finalizadores ( destructures )
GC.Collect(); // Recolectar los que tienen destructores
Console.ReadKey();
}
}

class MyWatcher
{
string[] _myLargeMemoryEater = new string[100000];
System.IO.FileSystemWatcher _fsw;
public MyWatcher()
{
_fsw = new System.IO.FileSystemWatcher();
_fsw.Path = "c:\\";
_fsw.Filter = "*.*";
_fsw.Created += _fsw_Created;
_fsw.EnableRaisingEvents = true;
}
public void UnSubscribe()
{
_fsw.Created -= _fsw_Created;
_fsw.Dispose();
}

void _fsw_Created(object sender, System.IO.FileSystemEventArgs e)
{
Console.WriteLine(e.FullPath);
}
~MyWatcher()
{
Console.WriteLine("Terminado en hilo "+System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
}
}
}


Al ejecutar el programa vemos como va consumiendo mas y mas memoria aun cuado en cada iteración estamos forzando la recolección de basura y la instancia que estamos creando no tiene aparentemente ninguna referencia. Aparentemente.

En realidad la referencia si existe porque al "enganchar" un evento a la clase FileSystemWatcher es como si estuviéramos enganchando un evento a un objeto del sistema operativo y mientras tal exista la referencia continuará ahi, no importa que no tengamos en el programa una referencia directa aparente al objeto.

Ahora si quitamos el comentario a la linea que dice
// oWatcher.UnSubscribe();

cada llamada al recolector de basura libera la memoria debido a que la llamada a UnSubscribe "desconecta" nuestro objeto del FileSystemWatcher, sin embargo es bueno notar que dos cosas se hacen aqui: una desconectar el evento, la otra liberar el FileSystemWatcher, por que ? El FileSystemWatcher como había dicho representa un objeto del sistema operativo, de hecho en su implementación interna debe tener un handle a un objeto del sistema operativo y no va a ser liberado hasta que se llame a Dispose, podemos hacer la prueba comentando esa linea y ver los resultados.

Como podemos ver aunque .NET maneja la memoria por nosotros, somos responsables cuando desde nuestro programa colocamos referencias a elementos externos, como en este caso objetos del sistema operativo.

miércoles, 9 de abril de 2008

Donde está Oracle Home ?

El Oracle Home es, entre otras cosas, la forma de localizar los productos de Oracle en una máquina. Pueden haber varias pero siempre hay una por defecto que es la utilizada por la mayoría de las aplicaciones que dependen de un cliente de oracle para funcionar.

En un mundo ideal el cliente de Oracle es instalado por un administrador y las aplicaciones solo tienen que usar el que esté ahi y no preocuparse mucho mas. En el mundo real el cliente es instaldo muchas veces por cualquiera y en muchas ocaciones es necesario saber donde está. Si como parte de la configuración de tu aplicación necesitas decir a que instancia/servicio de Oracle te conectas es posible que necesites saber cuales son las que están disponibles en la máquina y como esa información está en el archivo tnsnames.ora , tienes que saber en que directorio está instalado oracle home por defecto.

Siendo una cosa tan importante es curioso que halla tanta confusión sobre esto especialmente en plataforma Windows. Eso se debe en parte a que la documentación de Oracle al respecto no es muy abundante.

Lo que yo he implementado y hasta el momento me ha funcionado bien ( creo que lo vi en alguna parte en la documentación de Oracle ) es que hay un archivo llamado oracle.key en el directorio bin de la instalación que dice en que lugar en el registro de Windows está la información del Oracle Home. Por lo tanto para encontrar el ORACLE_HOME:

1 .- Recorrer los directorios que están en el camino ( variable de ambiente PATH, separados por punto y coma ).
2 .- Detenerse en el primero donde se encuentre el archivo oracle.key.
3 .- En ese archivo está el lugar en el registro donde está la información del ORACLE_HOME ( por ejemplo SOFTWARE\ORACLE\KEY_OraDb10g_home1 ).

Y ya tenemos la ubicación del Oracle Home en el registro, de ahi ganamos otros datos interesantes como directorios , valores de configuración , ubicación de otros productos, etc.

domingo, 2 de marzo de 2008

Herencia múltiple en C# ( resuelta )

Bueno, en realidad no, no hay forma de resolver el tema de la herencia múltiple en C# sin modificar el lenguage, pero siempre podemos acceder a soluciones alternativas mas o menos funcionales para lograr los mismos resultados que queríamos obtener con la herencia múltiple.

Estas soluciones alternativas dependen en general de los medios que poseemos y los resultados esperados. En muchos casos el problema es que tenemos una clase utilitaria que espera objetos de un tipo determinado ( no una interface ), para que nuestro objeto pueda ser utilizado en ese contexto heredamos de aquel y listo:


// Este es el tipo que se usa en la clase utilitaria
public class Usable
{
public virtual void HacerAlgo()
{
Console.WriteLine("Hice algo");
}
}

// Esta es la clase utilitaria
public class Utilitaria
{
public void Usa(Usable u)
{
u.HacerAlgo();
}
}

// Y este es nuestro tipo
class NuestroTipo : Usable
{
public override void HacerAlgo()
{
Console.WriteLine("Hacer otra cosa");
}
}

Hasta aqui todo bien. Pero que pasa si nuestro tipo ya heredaba de otro ? Si hubiera herencia múltiple heredábamos de Usable y de nuestro tipo base pero como no hay siempre podemos instanciar un objeto de tipo Usable y publicarlo para luego usarlo:


class NuestroTipo : NuestroTipoBase
{
private Usable m_usa = new Usable();
public Usable Usar { get { return m_usa; } }
}

...

Utilitaria util = new Utilitaria();
NuestroTipo tipo = new NuestroTipo();
util.Usa(tipo.Usar);

Evidentemente este ejemplo tiene una falla fundamental: esto no tiene nada que ver con la herencia. Si queríamos usar la herencia es porque probablemente hay algo dentro del tipo Usable que queríamos reescribir asi que simplemente instanciarlo dentro del nuestro no resuelve ningún problema. Al final lo que queremos es redefinir el comportamiento del tipo Usable y además queremos en esta redefinición haga uso de las interioridades de nuestro tipo. La solución es crear una clase interna que herede del tipo Usable y que redefina lo que sea necesario, además sería necesario siempre pasarle una instancia de nuestro objeto para poder tener acceso a sus interioridades. Algo asi:


// Este es nuestro tipo que hereda de otro
public class NuestroTipo : NuestroTipoBase
{
// Este es nuestro usable interno
private class NuestroUsable : Usable
{
// Evidentemente necesitamos acceder a las
// interioridades del objeto master
NuestroTipo m_master;
public NuestroUsable(NuestroTipo master)
{
m_master = master;
}
public override void HacerAlgo()
{
Console.WriteLine("Hacer otra cosa ( internamente )");
}
}
private readonly NuestroUsable m_usa;
public Usable Usar { get { return m_usa; } }
public NuestroTipo()
{
m_usa = new NuestroUsable(this);
}
}

Esto resuelve el problema en parte, aunque nos deja todavía con la necesidad de instanciar el objeto, cosa que además tiene que hacerse en el constructor ya que no nos permiten instanciarlo en la declaración pasándole además this como parámetro.

Lo mejor evidentemente sería tener herencia múltiple nativa en el lenguage pero como no hay podemos acudir a este tipo de mecanismos para simularla cuando la necesidad se presenta.

viernes, 29 de febrero de 2008

Herencia múltiple

C# no tiene herencia múltiple, Java tampoco, Delphi tampoco. De los lenguages mas usados solo C++ tiene herencia múltiple y en la mayoría de los programas que he visto no es usada. Sin embargo es una de las características que mas extraño en mi viaje por .NET desde C++ porque ayuda a resolver muchos problemas. Me he encontrado conque quiero que una clase sea un componente por el uso que le voy a dar ( hereda de System.ComponentModel.Component ) pero además quiero que se beneficie de un mecanismo de persistencia que he creado. Al final terminas por hacer magia para que el mecanimo no heredado sea capaz de adivinar de todo ( reflection mediante ) o te creas una interface.

El método socorrido de la interface tiene la desventaja de que tienes que implementar los métodos de esta. En muchos casos instancias dentro de la clase un objeto que va a hacer el trabajo y los métodos de la interface solo llaman a los métodos de ese objeto pero esa solución es extremadamente trabajosa si la interface tiene varios métodos.

Sería interesante que se pudiera "delegar" en un objeto. Me gustaría poder hacer automáticamente lo que tanto he tenido que hacer a mano. Decirle al compilador que los métodos de la interface son implementados por un objeto de la clase. Eso en parte se puede lograr en .NET 3 con los métodos de extensión, pero solo en parte, se puede tener la funcionalidad agregada a la clase pero no se pueden aprovechar las ventajas de la herencia. Una solución podría ser en la definición de la clase decir como hasta ahora:

class Ejemplo : IEjemploInterface

pero cuando se declare el objeto interno que implementa los métodos de la interface decir algo como:

implements IEjemploInterface ObjetoInterface obj;

para decir que los llamados a los métodos de la interface se "delegan" en la instancia de ese objeto. Eso sería muy útil ... y me resolvería muchos problemas.

martes, 29 de enero de 2008

Cuando se habla de .NET 3 lo primero que salta a la mente es LINQ. Todo el mundo habla de LINQ como una maravilla de la técnica, y en realidad es bastante interesante.
Imaginemos que tenemos la clase Persona:

class Persona
{
string m_nombre;
string m_apellido;
public Persona(string nombre, string apellido)
{
m_nombre = nombre;
m_apellido = apellido;
}
public string Nombre { get { return m_nombre; } }
public string Apellido { get { return m_apellido; } }
public override string ToString()
{
return Nombre + " " + Apellido;
}
}

y tenemos una colección de personas:

List< Persona > personas = new List< Persona >();
personas.Add(new Persona("Rodrigo", "Rojas"));
personas.Add(new Persona("Armando","Fuentes"));
personas.Add(new Persona("Lucia", "Wong"));
personas.Add(new Persona("Maritza","Rojas"));

usando LINQ podríamos buscar a todos los "Rojas" asi:

var a = from p in personas
where p.Apellido == "Rojas"
select p;
foreach (Persona per in a)
{
Console.WriteLine("{0}", per);
}

Ya se que también podría preguntar en el ciclo, pero es evidente que se ve cool por un lado, por otro lado el ejemplo es idiotamente simplista pero las consultas que se construyen con LINQ pueden ser bastante mas complejas, sin contar con el hecho de que separar el ciclo de la selección tiene la ventaja de poder usar el mismo ciclo en selecciones diferentes, algo que ya se hace en .NET 2 con el yield return pero que aqui se lleva mas lejos con este mecanismo que permite estandarizar una buena parte de las búsquedas sobre colecciones, algo tan común en cualquier programa.

Con todo esto del LINQ no se notan los mecanismos verdaderamente poderosos que permiten que exista, estos son los métodos de extensión (extension methods) y el patrón consulta (query expression pattern).

Los métodos de extensión no son mas que una nueva forma de llamar a un método estático que permite que la invocación sea , quizas , mas clara. Por ejemplo: Vamos a suponer que queremos en el código anterior obtener el nombre de una manera mas ... formal, en vez de nombre+apellido, queremos apellido+","+nombre, pero supongamos que esa clase no fue escrita por nosotros y no podemos ir para atrás y modificarla por los motivos que fueran. Lo mas probable es que creemos un método estático en alguna clase, que si además solo hace eso también será estática, y que nos haga el trabajo, algo asi:

static class Nombrar
{
public static string NombreFormal(Persona per)
{
return per.Apellido + ", " + per.Nombre;
}
}

el ciclo anterior quedaría asi:

foreach (Persona per in a)
{
Console.WriteLine("{0}", Nombrar.NombreFormal(per));
}

Ahora convertimos NombreFormal en método de extensión con un ligero cambio:

static class Nombrar
{
public static string NombreFormal(this Persona per)
{
return per.Apellido + ", " + per.Nombre;
}
}

y el ciclo se puede escribir asi:

foreach (Persona per in a)
{
Console.WriteLine("{0}", per.NombreFormal());
}

Es todo una cuestión de imagen, pero queda sin duda mucho mejor.

Y ahora entra el patrón consulta. Este dice que si una clase implementa ciertos métodos entonces puede usarse en una expresión de consulta, como la que vimos al inicio, estos métodos son enrte otros Select, Where, Join, OrderBy , entre otros. Y que es LINQ ? Pues LINQ no es mas que la implementación de esos métodos para cualquier tipo que implemente la interface System.Collections.Generic.IEnumerable< t > por eso es que se puede usar con cualquier colección, hasta con un arreglo. Lo poderoso de esto es que uno puede hacer su propio LINQ, es decir, implementar estos métodos para hacer que nuestras clases se comporten como orígenes de datos "consultables".

sábado, 19 de enero de 2008

La paradoja de Galiano

Hay gente que simplemente sabe como decir las cosas y hay dias en que lo que esa gente dice nos obliga a pensar un poco en lo injusta que es la historia y nos hace sentir un poco mal, por nuestro bien. Eso me ha pasado con La paradoja andante de Eduardo Galiano.

jueves, 3 de enero de 2008

Teorías de la conspiración

Primero que nada, soy un gran aficionado a las teorías de la conspiración. Las encuentro interesantes como historias alternativas, aunque de forma general no les doy ningún crédito. Parto del principio que si alguien con un determinado grado de poder se propone esconder una verdad terrible probablemente lo consiga y las teorías de la conspiración seguramente estarán tan lejos de la verdad como la historia oficial.

En el mundo del software, y sobre el tema de software libre y open source este tema es recurrente y hay veces que llega al extremo del ridículo, pero lo verdaderamente lamentable es que hayan personas que queriendo defender una idea le hagan tanto daño comportándose como fanáticos fundamentalistas y no dedicando su tiempo a cosas mas productivas.

En este sentido leí este interesante artículo de Bruce Byfield.

miércoles, 2 de enero de 2008

Llorando a Netscape

Netscape ha muerto y cualquier "odiador" de Microsoft que se respete ha escrito una elegía a sus virtudes y a como MS lo martirizó hasta su desaparición. Sin embargo una de las mas patéticas que he visto ha sido la de Steven J. Vaughan-Nichols R.I.P. Netscape.

Sin olvidar que realmente fue este el primer browser realmente popular y un impulsador de la web tal y como la conocemos hoy, me parece que achacarle toda la culpa a MS es darles demaciado crédito. Sus propietarios pusieron sin duda su "granito de arena" en la destrucción del explorador y no supieron hacer la competencia a Microsoft, no intentando retomar el 90% del mercado que perdieron, sino sirviendo de alternativa a IE en todos los campos donde este era devil ( y eran muchos ) como hace ahora su hijastro Firefox.

A mi me suele gustar lo que escribe Vaughan-Nichols de forma general, pero por veces parece que le dicen que no es lo suficientemente radical y trata de ponerse a la izquierda de la izquierda ( informáticamente hablando ).


En fin, que descanse en paz Netscape, larga vida a Firefox.