viernes, enero 05, 2007

Inicializadores de colecciones

Collection initializers: a new feature in C# 3.0Junto con los inicializadores de objetos, C# 3.0 introduce inicializadores de colecciones, para simplificar la inicialización de objetos cuyas clases implementen la interfaz genérica ICollection<T>.
Suponga que estamos trabajando con C# 2.0, y que queremos crear una lista genérica de enteros y añadirle unos cuantos elementos iniciales:
List<int> lista =
new List<int>(new int[] { 1, 2, 3, 4 });
El ejemplo anterior utiliza uno de los constructores de la clase List, que recibe un parámetro de tipo IEnumerable, en su versión genérica. El CLR considera que todo vector implementa dicha interfaz, por lo que el compilador genera instrucciones para crear el vector para luego pasarlo al constructor de List.
¿Se da cuenta de lo ineficiente que es esta técnica? Observe el código aproximado que debe generar el compilador, con sintaxis C# para no hacerlo ilegible:
int[] temp = new int[4];
temp[0] = 1;
temp[1] = 2;
temp[2] = 3;
temp[3] = 4;
List<int> lista = new List<int>(temp);
Pero esto es sólo lo que ocurre a la vista de todos. Recuerde que el parámetro recibido por el constructor es una interfaz IEnumerable. El constructor debe ejecutar el método GetEnumerator de la referencia recibida, y llamar al método MoveNext repetidas veces sobre el resultado de la llamada.
Bienvenidos a C# 3.0. Ahora podemos usar esta sintaxis:
List<int> lista =
new List<int>(){ 1, 2, 3, 4 };
Los cambios son sutiles, pero importantes. Ahora utilizamos el constructor sin parámetros de List, aunque tenemos la libertad de usar el constructor que mejor nos parezca, pues la lista de elementos iniciales no se pasa como parámetro del constructor. Por el contrario, la lista se yuxtapone tras el paréntesis de cierre de la lista de parámetros. Aunque parece un vector literal, no lo es: no hay indicación del tipo de los elementos... porque exigiremos que sean compatibles con el tipo de elemento de la lista. La traducción es la siguiente:
List<int> lista = new List<int>(temp);
lista.Add(1);
lista.Add(2);
lista.Add(3);
lista.Add(4);
Ya no tenemos que crear e inicializar un vector de usar y tirar. Ya no hay un bucle dentro del constructor. Ya no se crea una instancia temporal de IEnumerator para el bucle del constructor. De manera que no sólo ahorramos tiempo, de manera directa, sino que también ahorramos memoria, y aliviamos la carga del garbage collector.
Sólo nos queda un detalle por aclarar. Las llamadas al método Add tienen en realidad este aspecto:
((ICollection<int>)lista).Add(1);
Pero esto no significa una carga adicional, porque la conversión anterior no genera código adicional... al menos en lenguaje intermedio. Tengo que comprobar todavía lo que ocurre cuando se traduce a código nativo.
Como imaginará, cuento todo esto porque acabo de añadir a Freya estos inicializadores de colecciones. Eso sí, la sintaxis provisional es "fea":
List[Integer] := new List[Integer]![1,2,3,4];
No podemos yuxtaponer sin más el inicializador a la expresión new, porque Freya permite omitir los paréntesis cuando no hay parámetros, y porque usamos los corchetes para los parámetros de tipos genéricos. No podemos usar ninguno de los operadores permitidos en expresiones, porque introduciríamos ambigüedad en el lenguaje. He probado el uso de la asignación de Pascal, y aunque no causa problemas, el efecto estético es horroroso. Me he quedado con el signo de admiración porque me ha parecido el menor de los males.

Etiquetas: ,