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

LINQPad y la ventana de comandos

Muchas veces necesitamos saber cómo se va a comportar una línea de código o un pequeño método (si va a devolver null o un tipo inicializado por defecto, si va a cumplir una determinada condición o no).  En Microsoft Visual FoxPro (esa magnífica evolución del lenguaje dBase cuyo soporte finalizará en 2015 – v. http://msdn.microsoft.com/es-es/vfoxpro/bb308952) la ventana de comandos cumplía a las mil maravillas esa función puesto que permitía, en tiempo real, crear variables, invocar métodos e, incluso, examinar el comportamiento de las sentencias en el depurador.

Recuerdo que esta era, precisamente, una de las cosas que más echaba de menos cuando en Lusco comencé a usar C# como lenguaje de programación.  Intenté usar la ventana Inmediate del depurador de Microsoft Visual Studio, pero tenía dos inconvenientes.  El primero, que cada vez que se ejecutaba una instrucción se recompilaba la solución que estuviese activa; salvo por los segundos de compilación, este no parece un gran inconveniente.  El segundo era que había que tener cargado en memoria desde una clase los namespaces necesarios; es decir, que fracasará cualquier intento de instanciar una variable System.Data.Sqlclient.SqlConnection desde la ventana Inmediate sin haber hecho previamente un using de System.Data en alguna de las clases de la solución.

Otra de las dificultades derivadas del cambio de lenguaje con las que tuve que enfrentarme fue lo farragoso que se me antojaba el tratamiento de datos con ADO y ADO.Net, acostumbrado, como estaba, al sencillo use de Fox.  Afortunadamente, poco después de mi llegada a C#, la versión 3.0 incorporó LINQ, lo cual suponía un ligero acercamiento (desde el punto de vista del programador) al modo en el que Visual FoxPro maneja los datos que tiene precargados en memoria.  Con el objeto de acumular experiencia con LINQ llegué a una herramienta que me permitía diseñar expresiones LINQ al vuelo (sin intellisense en su versión gratuita) y que me enseñaba la instrucción SQL que esa expresión iba a enviar al servidor, si es que estaba atacando una base de datos de Microsoft SQL Server.  Esa herramienta era LINQPad (http://www.linqpad.net/).

Pero no sólo eso y aquí viene lo mejor.  LINQPad funciona de manera muy similar a la ventana de comandos del viejo Fox, permitiendo escribir una serie de sentencias C# y ejecutarlas, comprobando a continuación el estado de las variables mediante el método Dump() (los resultados de las consultas LINQ o SQL se muestran directamente).  ¿Y los usings?  Pues, al igual que en Visual Studio, podemos invocar un tipo escribiendo su espacio de nombres completo; pero también es posible definir usings específicos para una consulta en las propiedades de esa consulta.

En definitiva, que para todos aquellos que, como yo mismo, echamos de menos la ventana de comandos para una prueba en línea del funcionamiento de una expresión, LINQPad es una herramienta imprescindible.  Su autor, por cierto, es el conocido gurú de C# y .NET Joseph Albahari (http://www.albahari.com/), autor de libros como las Pocket Reference de C# 3 y 4 que yo considero manuales imprescindibles para un programador.