martes, 29 de octubre de 2013

Problemas de uso de colecciones no genéricas



En las versiones anteriores a framework 2.0, era absolutamente normal trabajar con colecciones de tipo object como base, entre ellas ejemplos como ArrayList, Queue, List, etc., Esto traía dos problemas principales a los desarrolladores:

  • Problemas de Conversion de Tipos.
  • Problemas de rendimiento.
Vamos a ver de manera un poco más amplia cada uno de ellos.




Recuerda que aquí tienes el indice de todos los posts del Curso de LinQ.




Problemas de Conversión de tipos

Las colecciones anteriormente nombradas, al aceptar objetos del tipo base object, no tenían ningún tipo de control sobre los elementos que almacenaban, pudiendo almacenar objetos de clases completamente diferentes y sin ningún tipo de concordancia ni relación entre ellas. Por otra parte el compilador no podía realizar ninguna comprobación de tipos por lo que este tipo de errores se daban en tiempo de ejecución en vez de en tiempo de compilación. La única opción que existía era crearse colecciones propias e implementar el tipo de restricciones.

Vamos a ver un ejemplo:

static void Main(string[] args)
{
    ArrayList numeros = new ArrayList() { 1, 2, 3, 4, 5, 5.5 };

    int resultado = 0;

    foreach (object numero in numeros)
        resultado += (int) numero; // Excepción, ya que el '5.5' no es int
}

Como podemos observar en el ejemplo, no hemos tenido ningún problema a la hora de instanciar el ArrayList, para añadir un ‘5.5’ que es un valor que sería de tipo double o decimal y el compilador nos ha permitido compilar sin mostrar ningún tipo de error. Esto podría haber sido mucha más bestia, ya que aquí se podrían haber hecho barbaridades como esta:

ArrayList numeros = new ArrayList() { 1, 2, 3, 4, 5, 5.5, DateTime.Today, "hola", new Exception() };



El problema viene cuando intentamos hacer el sumatorio de los valores de nuestra colección y tenemos que hacer el obligado casting a entero, al llegar a los datos no admitidos para la conversión, se elevaría una excepción.

Este problema quedaría subsanado con el uso de Generics y sus (Type Saves).

Como podemos ver en el pantallazo, el compilador ya nos avisaría del error antes de compilar:






Problemas de rendimiento

El otro problema más significativo viene dado por la pérdida de rendimiento derivado del boxing/unboxing. El CLR permite crear tipos por referencia class y tipos por valor structs. Los primeros guardan una referencia al bloque de memoria que ocupa el objeto dentro del heap, y los segundos guardan un espacio dentro de la pila. El lugar de almacenamiento es completamente diferente para unos que para otros, y en algunos casos viene a ser llamado la memoria lejana y la cercana.

El proceso de boxing, consiste en envolver una estructura dentro de un objeto de tipo object:


int numero = 1;
object objetoNumero = numero;

A simple vista, parece una asignación normal, pero en el proceso interno, el CLR tiene que convertir el tipo por valor a un tipo por referencia, realizando el cambio y transformando los datos de la pila al heap, con el coste en rendimiento que esto conlleva. Para un dato, puede parecer irrelevante, pero en colecciones de miles de elementos la perdida de rendimiento es significativa.

El proceso de unboxing, consiste justamente en el proceso contrario al anterior, es el paso en que desempaquetamos el dato y lo transformamos de un tipo referencia a un tipo valor:



object objetoNumero = 1;
int numero = (int) objetoNumero;


Pues aquí finalizamos el repaso a los problemas que teníamos los desarrolladores de .NET antes de la aparición de Generics.