Compartir a través de


El programador políglota

Colecciones .NET: Introducción a C5

Ted Neward

 

Tengo que hacer una confesión.

Durante el día, trabajo como respetable desarrollador .NET para Neudesic LLC, una firma consultora. Pero durante la noche, después de que mi esposa y mis hijos están dormidos, salgo a escondidas de la casa, con mi equipo portátil en mano y me dirijo a mi escondite secreto (un Denny's en la 148) y... escribo código Java.

Sí, amigos míos, tengo una doble vida: soy tanto desarrollador .NET como Java, o para ser más preciso, desarrollador de máquina virtual Java (JVM). Uno de los beneficios de tener un doble estilo de vida es que puedo ver las áreas donde Microsoft .NET Framework tiene algunas buenas ideas que pueden llevarse a JVM. Una de esas ideas es la de los atributos personalizados, los cuales JVM adoptó en Java5 (en el 2005 aproximadamente) con el nombre de "anotaciones". Pero también es cierto lo contrario: El JVM, de hecho, consiguió un par de cosas buenas que CLR y la biblioteca de clases base (BCL) de .NET. no (o al menos no tan bien si se siente un poco a la defensiva). Una de ellas se encuentra en el centro de BCL de .NET: las colecciones.

Colecciones: Una crítica

Parte del defecto de las colecciones .NET radica en el hecho de que el equipo de BCL debía escribir las cosas tontas dos veces: una vez para el lanzamiento de .NET Framework 1.0/1.1, antes de que los genéricos estuviesen disponibles, y otra vez para .NET Framework 2.0, después que los genéricos fuesen parte de CLR, ya que las colecciones sin versiones fuertemente tipificadas son solo tonterías. Esto significa automáticamente que uno de ellos estaba destinado a la degradación, esencialmente, en favor de las mejoras o nuevos contenidos que a la biblioteca fuesen a venir. (Java eludió este problema específico básicamente "reemplazando" las versiones no genéricas por versiones que sí lo son, lo que solo fue posible a través de la forma en que Java creo los genéricos, en lo que no me voy a adentrar). Y, aparte de las mejoras que vienen a través de LINQ en Visual Studio 2008 y C# 3.0, la biblioteca de colecciones nunca fue tan apreciada después del lanzamiento 2.0, el cual por sí mismo reimplementó más o menos las clases System.Collections en un espacio de nombres nuevos (System.Collections.Generic o SCG) de versiones fuertemente tipificadas.

Si bien, más importante es que el diseño de las colecciones .NET parece haberse centrado más en obtener algo práctico y útil en el medio natural como parte del lanzamiento 1.0, en lugar de tratar de pensar profundamente en el diseño de colecciones y cómo deberían extenderse. Esta era un área en la cual .NET Framework era realmente (sospecho que sin intención) paralelo al mundo Java. Cuando se envió Java 1.0, incluía un conjunto de colecciones básicas y utilitarias. Pero tenían unos cuantos errores de diseño (el más notorio era la decisión de que la clase Stack, una colección del tipo "último en entrar, primero en salir", extendiera directamente la clase Vector, la cual básicamente era una ArrayList). Después de que se envió Java 1.1, algunos ingenieros de Sun Microsystems trabajaron fuertemente para reescribir las clases de colecciones, la que se hicieron conocidas como colecciones de Java y lo enviaron como parte de Java 1.2.

De todos modos, .NET Framework es parte del pasado debido a una renovación de las clases de colección, idealmente en una forma que es al menos compatible en su mayoría con las clases SCG existentes. Afortunadamente, los investigadores de la Universidad de TI (tecnología de información) de Copenhague en Dinamarca han creado un digno sucesor y un complemento para las clases SCG. una biblioteca que llaman clases de colección integrales de Copenhague para C#, or C5 para abreviar.

Logística de C5

Para empezar, C5 se encuentra en Internet: itu.dk/research/c5 si desea revisar el historial de versiones o tener un vínculo al libro (PDF) de C5, aunque el libro es una versión un poco antigua. O, alternativamente, C5 está disponible en NuGet a través del comando (por ahora ubicuo) Install-Package, simplemente con escribir "Install-Package C5). Tenga en cuenta que C5 se escribe para estar disponible tanto en Visual Studio como en Mono y cuando NuGet instala el paquete, agregará referencias al ensamblado C5.dll y al ensamblado C5Mono.dll. Estos son redundantes entre sí, por lo que elimine el que no desea. Para explorar las colecciones C5 a través de una serie de pruebas de exploración, creé una prueba de Visual C# y agregué C5 al proyecto. Aparte de eso, el único cambio notable al código son dos instrucciones "de uso" que la documentación C5 también supone:

using SCG = System.Collections.Generic; using C5;

La razón para el alias es sencilla: C5 "reimplementa" algunas clases e interfaces que se llaman de la misma forma en la versión SCG, por lo tanto el dar un alias a las cosas antiguas lo deja disponible para nosotros, pero con un prefijo muy corto (IList<T> es la versión C5, por ejemplo, mientras SCG.IList<T> es la versión "clásica" de SCG).

Por cierto, en caso de que los abogados pregunten, C5 es de código abierto bajo una licencia MIT, por lo que usted es mucho más capaz de modificar o mejorar algunas de las clases C5 de lo que sería bajo una licencia GNU para el público en general (GPL) o una GNU para el público en general Lesser (LGPL).

Introducción al diseño de C5

En cuanto al enfoque del diseño de C5, parece similar al estilo de SCG, las colecciones están divididas en dos "niveles": una capa de interfaz que describe la interfaz y el comportamiento esperado de una colección dada y una capa de implementación que proporciona el código de respaldo real para una o más interfaces deseadas. Las clases SCG se aproximan a esta idea, pero en algunos casos no la llevan a cabo muy bien, por ejemplo, no tenemos ninguna flexibilidad en términos de implementación de SortedSet<T> (es decir, la elección basada en matriz, la basada en lista vinculada o la basada en hash, cada una de las cuales tiene características diferentes con respecto al rendimiento de inserción, al recorrido, etcétera). En algunos casos las clases SCG simplemente pierden ciertos tipos de colección, una cola circular, por ejemplo ( en la que cuando se recorre el último elemento en la cola, la iteración "retorna" nuevamente al comienzo de la cola), o una simple colección "contenedor" (la que no ofrece ninguna funcionalidad excepto que contiene elementos, evitando así sobrecargas innecesarias de orden, de indización, etcétera).

Efectivamente, para el desarrollador .NET promedio, esto no significa una gran pérdida. Sin embargo, en muchas aplicaciones, como el rendimiento comienza a ser un punto importante, elegir la clase de colección correcta para que coincida con el problema en cuestión se vuelve más crítica. ¿Es esta una colección que se establecerá una vez y se recorrerá en forma frecuente? ¿O es esta una colección que se agregará o eliminará en forma frecuente, pero se recorrerá en ocasiones poco comunes? Si esta colección está en el núcleo de una característica de la aplicación (o en la aplicación en sí), la diferencia entre las dos podría significar la diferencia entre "¡oh, qué excelente aplicación!" y "Bien, a los usuarios les gustó, pero pensaron que era demasiado lenta".

C5 tiene entonces como uno de sus principios fundamentales que los desarrolladores deban "codificar para las interfaces, no para las implementaciones" y de esta forma ofrece más de una docena de distintas interfaces que describen lo que la colección subyacente debe proporcionar. ICollection<T> es la base de todo esto, lo que garantiza un comportamiento de colección básico, pero a partir de ahí encontramos IList<T>, IIndexed<T>, ISorted<T> e ISequenced<T>, solo para empezar. Acá tenemos la lista completa de interfaces, su relación con otras interfaces y sus garantías generales:

  • Una SCG.IEnumerable<T> puede tener sus elementos enumerados. Todas las colecciones y diccionarios son enumerables.
  • Una IDirectedEnumerable<T> es un enumerable que puede revertirse, lo cual da una enumeración hacia atrás que enumera sus elementos en el orden contrario.
  • Una ICollectionValue<T> es un valor de colección. No es compatible con modificaciones, es numerable, sabe cuántos elementos tiene y puede copiarlos a una matriz.
  • Una IDirectedCollectionValue<T> es un valor de colección que puede revertirse a un valor de colección en reverso.
  • Una IExtensible<T> es una colección a la que pueden agregarse elementos.
  • Una IPriorityQueue<T> es extensible donde cuyos elementos mínimos y más grandes pueden encontrarse (y eliminarse) de forma eficaz.
  • Una ICollection<T> es extensible desde donde también los elementos pueden eliminarse.
  • Una ISequenced<T> es una colección cuyos elementos aparecen en una determinada secuencia (determinada tanto por el orden de inserción como por el orden de los elementos).
  • Una IIndexed<T> es una colección secuencial cuyos elementos son accesibles a través del índice.
  • Una ISorted<T> es una colección secuencial donde los elementos aparecen en orden creciente y las comparaciones de los elementos determinan la secuencia de estos. Se puede encontrar de forma eficaz el predecesor o sucesor (en la colección) de un elemento dado.
  • Una IIndexedSorted<T> es una colección indizada y ordenada. Puede determinar de forma eficaz cuántos elementos son mayores que o iguales a un elemento X dado.
  • Una IPersistentSorted<T> es una colección ordenada desde la que en forma eficaz podemos hacer una instantánea, es decir, una copia solo de lectura que no se verá afectada por las actualizaciones de la colección original.
  • Una IQueue<T>es una cola del tipo primero en entrar, primero en salir (FIFO) que además mantiene la indización.
  • Una IStack<T> es una pila del tipo último en entrar, primero en salir (LIFO).
  • Una IList<T>es una colección indizada y por lo tanto secuencial, donde el orden de los elementos se determina por medio de las inserciones y las eliminaciones. Deriva de SCG.IList<T>.

En lo que respecta a las implementaciones, C5 tiene muchas, incluidas colas circulares, listas de matriz respaldada así como de vínculo de lista respaldado, pero también listas de matriz basadas en hash y listas de hash vinculadas; matrices ajustadas; matrices ordenadas: conjuntos y contenedores de estructura jerárquica del tipo árbol, y más.

Estilo de codificación de C5

Afortunadamente, C5 no requiere un cambio significativo en el estilo de codificación y, aún mejor, es compatible con todas las operaciones LINQ (ya que se basa en la cima de la parte superior de las interfaces de SCG, de los cuales los métodos de extensión de LINQ se separan), por lo que en algunos casos puede incluir una colección C5 en el tiempo de creación sin cambiarle ningún código. Vea la Figura 1 para obtener un ejemplo de esto.

Figura 1 Introducción a C5

// These are C5 IList and ArrayList, not SCG
IList<String> names = new ArrayList<String>();
names.AddAll(new String[] { "Hoover", "Roosevelt", "Truman",
  "Eisenhower", "Kennedy" });
// Print list:
Assert.AreEqual("[ 0:Hoover, 1:Roosevelt, 2:Truman, 3:Eisenhower," +
  " 4:Kennedy ]", names.ToString());
// Print item 1 ("Roosevelt") in the list
Assert.AreEqual("Roosevelt", names[1]);
Console.WriteLine(names[1]);
// Create a list view comprising post-WW2 presidents
IList<String> postWWII = names.View(2, 3);
// Print item 2 ("Kennedy") in the view
Assert.AreEqual("Kennedy", postWWII[2]);
// Enumerate and print the list view in reverse chronological order
Assert.AreEqual("{ Kennedy, Eisenhower, Truman }",
  postWWII.Backwards().ToString());

Aun sin haber visto alguna vez la documentación C5, es muy fácil entender lo que ocurre en estos ejemplos.

Implementaciones de colección SCG frente a C5

Esto es solo la punta del iceberg con respecto a C5. En mi próxima columna veremos algunos ejemplos prácticos del uso de C5 en las implementaciones de colección SCG y algunos de los beneficios que nos ofrece. No obstante, lo animo a no esperar: Haga el NuGet, desplace C5 hacia abajo y comience a explorar por sí mismo, hay mucho para que se entretenga mientras tanto.

¡Feliz codificación!

Ted Neward es asesor arquitectónico en Neudesic LLC. Ha escrito más de 100 artículos y es autor y coautor de docenas de libros, incluido el próximo “Professional F# 2.0” (Wrox, 2010). Es un MVP de F# y un célebre experto en programación Java y habla tanto de Java como de .NET en congresos alrededor del mundo. Es consultor y mentor habitual, póngase en contacto con él en ted@tedneward.com o Ted.Neward@neudesic.com si está interesado en que venga a trabajar con su equipo. Tiene un blog en blogs.tedneward.com y lo puede seguir en Twitter en twitter.com/tedneward.

Gracias a los siguientes expertos técnicos por su ayuda en la revisión de este artículo: Immo Landwerth