Unboxing arrays

C# es un lenguaje fuertemente tipado, es decir, toda variable debe ser definida como de un determinado tipo antes de poder ser usada (para los legos diremos que entre estos tipos se encuentran los números, las fechas, las cadenas de caracteres o cualquier otro tipo de mayor complejidad que hayamos programado combinando los anteriores). En la inmensa mayoría de las ocasiones ésta es una característica deseable, puesto que garantiza que las versiones de prueba (y de producción) están libres de cierto tipo de errores y además dota a la ejecución del código de una mayor ligereza.

En realidad todos (sí, todos, incluso los números) los tipos que se pueden usar en C# pueden considerarse como de tipo object (el más general de los tipos en C#); en otras palabras, cualquier variable en C# puede ser definida y considerada como de tipo object o como de un tipo más específico, siendo ésta la opción que debemos preferir siempre por las razones antes apuntadas. Pero ocurre que, en muy raras ocasiones (prácticamente nunca gracias a la introducción de los genéricos en la versión 2.0 del lenguaje), necesitamos hacer uso de esta relación de dependencia y tenemos que echar mano a una variable object para guardar en ella cualquiera de los tipos a los que aludimos anteriormente.

Cuando esto lo hacemos para números de cualquier tipo, valores lógicos (verdadero o falso) o estructuras que combinen cualquiera de estos tipos estaremos ante lo que se conoce como boxing. Se denomina unboxing al proceso inverso, es decir, a la conversión de una variable object en un tipo numérico, lógico o en una estructura de números y valores lógicos.

Mientras que el boxing es automático, el proceso de unboxing necesita de una conversión explícita que resultará en un error si “dentro” del object no se encuentra un valor del tipo al que tratamos de convertir. La siguientes tres líneas de código definen una variable entera de 32 bits de nombre entero a la que se le asigna el valor 23, definen una variable de tipo object y de nombre objeto a la que asignamos el valor de la variable entero y recuperan en la consola el tipo contenido en la variable objeto:

int entero = 23;
object objeto = entero;
Console.WriteLine(objeto.GetType().Name);

Si ejecutamos este código (por ejemplo en LinqPad – véase este post-) obtendremos que la variable objeto presenta un tipo Int32 (entero de 32 bits).

Para el proceso de unboxing, como queda dicho, debemos convertir objeto al tipo correcto. Así, el siguiente código crea una variable entera de 32 bits denominada otroEntero:

int otroEntero = (int)objeto;

Pero si tratamos de crear, por ejemplo, una fecha obtendremos un error de ejecución:

DateTime fecha = (DateTime)objeto; // Provoca un InvalidCastException

Cuando lo que hacemos es el boxing de una matriz la cosa se complica un poco, puesto que no solo necesitamos saber el tipo a la hora de hacer el unboxing, sino que también necesitamos saber las dimensiones, necesitamos saber si debemos crear un vector, una matriz de 2 dimensiones o una matriz n-dimensional. En concreto, este problema me surgió porque necesitaba saber el número de filas en una matriz de la cual no sabía ni el tipo, ni el número de elementos, ni el número de dimensiones.

Definamos pues una matriz de enteros de dos filas y tres columnas y veamos qué tipo es una variable object que la contenga:

int[,] enterosMatriz = { {11, 12, 13}, {21, 22, 23} };
object objeto = enterosMatriz;
Console.WriteLine(objeto.GetType().Name);

En este caso obtenemos que la variable objeto es de tipo Int32[,], es decir, es una matriz de enteros de 32 bits de dos dimensiones. Ahora la cuestión es saber cómo obtener el número de dimensiones sin tener que procesar el nombre del tipo y contar las comas, lo cual -dicho sea de paso- me parecía un poco “primitivo”. Afortunadamente, la clase Type devuelta por el método GetType() dispone a su vez de un método GetArrayRank() que devuelve el número de dimensiones si el Type sobre el que se aplica es una matriz. Para comprobar que efectivamente estamos ante una matriz y evitar que este método nos dé un error en ejecución, disponemos de la propiedad IsArray. Así, el siguiente código comprueba si la variable objeto es una matriz y, si lo es, devuelve el número de dimensiones de la misma; en caso de que no lo sea, devuelve un cero:

if (objeto.GetType().IsArray)
{
Console.WriteLine(objeto.GetType().GetArrayRank());
}
else
{
Console.WriteLine(0);
}

Aunque parece que vamos por el buen camino, no he encontrado nada en el objeto Type que me devuelva el número de elementos, columnas o filas que contiene la variable a la que se refiere, por lo que saber cuántas filas tiene la matriz objeto sigue siendo inabordable por esta vía.

Fue entonces cuando recordé que C# dispone de un tipo de matriz genérico que raramente uso: la clase Array. Simplemente haciendo el cast a este tipo, ya dispongo de una matriz con un montón de propiedades y métodos específicos para matrices. En concreto, creo que el método GetUpperBound(int dimension) es el más adecuado; hay que recordar que en C# las matrices son en base cero, por lo que al resultado de este método hay que sumarle uno para obtener el número de elementos en la dimensión especificada. Como lo que queremos obtener es el número de filas, debemos llamar a este método pasándole un cero como parámetro -la primera dimensión es la de las filas-:

if (objeto.GetType().IsArray)
{
Console.WriteLine(((Array)objeto).GetUpperBound(0) + 1);
}
else
{
Console.WriteLine(0);
}

A veces, como acabamos de ver, estamos tan acostumbrados a manejar clases especializadas y genéricas para las listas y colecciones que cuando queremos volver a lo más básico, cuesta mucho recordar qué teclas hay que pulsar.

Advertisements


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s