martes, 21 de noviembre de 2006

Boxing y Unboxing

Pues como ya hacía tiempo que no posteaba nada 'útil' en el blog, vamos a arrancarnos por peteneras con un didáctico articulillo sobre esta característica de .NET.

El Boxing no es otra cosa que convertir un tipo "valor" (Int, Double, Structs...) en un tipo "referencia". La utilidad de esto, se puede encontrar si en alguna ocasión necesitamos pasarle un parámetro, que por definición sería "por valor", a un método que requiere parámetros que son tipos por Referencia (esto puede ocurrir con más frecuencia de lo que pensamos, y entonces nos vemos obligados a sobrecargar nuestras funciones, o a crear nuevas -OJO no confundir con el modificador ref de C#, ByRef de VB.NET).

¿Cómo se hace el Box? Muy sencillo, tenemos la siguiente variable:


int i = 35; // Esas rimas fáciles...

Y queremos utilizarla en un método, que podría ser por ejemplo éste:

static void ManejaUnObjeto(object o)
{
...
}

Primer problema: el método recibe un objeto (un tipo por Referencia), y nosotros queremos pasarle un entero (tipo por valor)... Evidentemente no podemos incluir un ref en la llamada al método puesto que no está declarado para aceptarlo. ¿Solución? Nada mejor que "convertir" esa variable, precisamente en un objeto (la Madre De Todos los tipos por Referencia). Así conseguimos pasarle la variable al método:

object objEntero = i;
ManejaUnObjeto(objEntero);

Segundo problema: Ya tengo la variable (o algo parecido) dentro del método. ¿Cómo demonios la utilizo?

static void ManejaUnObjeto(object Objeto)
{
int j = int(Objeto);
}

Para poder utilizar el valor correctamente, debemos hacer el "Unboxing" del parámetro, que no es otra cosa que un Cast al tipo de dato correspondiente. Pero ojo, porque el tipo DEBE ser el mismo de la variable original:

static void ManejaUnObjeto(object Objeto)
{
double j = double(Objeto); // Esto pega un petardazo.
}

Tenemos dos soluciones para ésto: O bien interceptamos la excepción con un try...catch(InvalidCastException) o bien nos aseguramos de que el tipo sea el esperado (es más elegante esta opción, por supuesto):

static void ManejaUnObjeto(object Objeto)
{
if(Objeto is int)
{ int j = int(Objeto); }
}

Otro aspecto a tener en cuenta es cuando le pasamos a ese método un tipo definido por nosotros:

// Definimos esta estructura (recordar que es tipo por VALOR,
// al contrario que la Clase)

struct Estructura
{
public int i, j;
}

// Definimos dentro del Main una variable de ese tipo y la
// pasamos a un método

static void Main(string[] args)
{
Estructura struc;
struc.i = 10;
struc.j = 20;
MetodoEstructura(struc);
}

Vemos aquí que no hemos hecho el Box, es decir, no hemos convertido la estructura en objeto. Esto es perfectamente válido y no es mucho problema, en principio. Por regla general, .NET hace el Boxing automáticamente cuando se encuentra que pasamos un tipo por Valor a un método que espera un objeto ("vaya, ¿entonces para qué lo explicas?" Pues porque nunca está de más saber las cosas :P ).

Proseguimos con el código anterior. Vamos ahora a escribir el método que recibe y utiliza esta estructura:

static void MetodoEstructura(object Objeto)
{
Console.WriteLine("Valor 0 {0}, Valor 1 {1}", Objeto.i,
Objeto.j);
}

Este método ni siquiera compila. ¿Motivo? Que la clase System.Object no tiene ningún miembro llamado 'i' o 'j', y aunque el compilador es capaz de hacer el Box automático del parámetro de entrada, NO hace la acción inversa. Hemos pues de proceder al Unbox manual:

static void MetodoEstructura(object Objeto)
{
// Ahora si...
if(Objeto is Estructura)
{
Estructura s = (Estructura)Objeto;
Console.WriteLine("Valor 0 {0}, Valor 1 {1}", s.i, s.j);
}
else
Console.WriteLine("No has enviado una Estructura!");
}

Una cuestión a tener en cuenta es que las operaciones de Box y Unbox, como cualquier Cast, consumen algo de tiempo de proceso y si se utilizan con demasiada frecuencia, el rendimiento de nuestra Aplicación podría verse afectado (podría). Sin embargo desde la versión 2.0 de la plataforma hay una posible solución a esto, y es el uso de los "nuevos" tipos genéricos (parecido al Variant de toda la vida de los que conocemos Delphi, jeje :P ... Sólo que en principio el uso del tipo Variant en Delphi SI penaliza el rendimiento, y los genéricos en .NET, al parecer, no lo hacen).

No hay comentarios: