Tabla de Contenido
0. Introducción1. Tipos por Valor y Tipos por Referencia
1.1 Tipos por Valor
1.2 Tipos por Referencia
2. Stack y Heap
2.1 Stack
2.2 Heap
3. Boxing y Unboxing
4. Rendimiento
5. Preguntas y Respuestas
6. Conclusiones
7. Glosario
8. Enlaces & Literatura
0. Introducción
En este artículo explicaré cómo se lleva a cabo los procesos de boxing y unboxing (prefiero utilizar los términos anglosajones para evitar traducciones vulgares) en C#. Dos operaciones que son inherentes al sistema unificado de tipos de C#. Empezaré por aclarar (brevemente) los conceptos de tipos por valor y tipos por referencia; que nos sirve para afianzar las operaciones mencionadas. También incluiré la descripción de excepciones que pueden ocurrir mientras que laboramos con boxing y boxing. Trataré de la deficiencia en el rendimiento si abusamos de estas conversiones (independiente de la dirección). Incluiré ejemplos para afianzar lo comprendido a través de la práctica. Bienvenidos.
1. Tipos por Valor y Tipos por Referencia
A pesar que en el artículo Tipos por Valor y Tipos por Referencia en C# traté más a fondo este tema, voy a destacar las características básicas de estos tipos para introducir posteriormente las operaciones protagonistas de este artículo.
1.1 Tipos por Valor
Los tipos por valor (también conocidos como tipos primitivos, o tipos integrados) no son más que aquellas literales numéricas utilizadas para las operaciones aritméticas. Las clasificación de estos tipos es la que se muestra en la Figura 1 [1].
Figura 1. Tipos numéricos predefinidos en C# [1]. |
La clase padre de todas estas categorías corresponde con ValueType [9]. Todos los valores para estas categorías son almacenados en una locación conocida estructura stack (tema de la siguiente sección).
A pesar de que este tipo de operación está permitida:
System.ValueType v = 13;
Debido a que la literal 13 es un subtipo, para precisar int (alias de Int32), de ValueType. Una operación aritmética como esta:
System.ValueType v1 = 13;
System.ValueType v2 = 6;
System.ValueType v3 = v1 + v3;
no está permitida en el lenguaje, debido a que ValueType es un tipo por referencia y no existe una versión sobrecargada del operador de suma (+) para alcanzar el resultado que esperamos.
Error que obtenemos:
error CS0019: Operator `+' cannot be applied to operands of type `System.ValueType' and `System.ValueType'
1.2 Tipos por Referencia
Empezando por el tipo object (recomendad lectura de mi artículo El Tipo object en C#) todo tipo de objeto, implícitamente, es un tipo por referencia. Otra evidencia de los tipos de datos por referencia son aquellos que se almacenan en la locación/estructura especial conocida como heap (se tratará en la siguiente sección). Cualquier clase que ya sea parte del Framework .NET o una definida por un programador es considerada un tipo por referencia.
En [8], además dicen:
"C# new operator returns the memory address of the object."
2. Stack y Heap
Introduciré los conceptos de Stack y Heap para comprender las operaciones de boxing y unboxing.
[Nota: Estas dos estructuras de almacenamiento se hayan más ampliamente explicadas en mi artículo Variables y Parámetros en C# - Parte 1.]
2.1 Stack
En [1] se define stack como: en arquitecturas de computación, un stack no es más que una región de memoria en donde los datos son removidos de acuerdo al modo primero-en-entrar-primero-en-salir (o inglés, LIFO - Last-In-First-Out [10]), es decir, que es posible extraer el último elemento de la parte superior de la pila, los demás no son accesibles. Es por eso, que a esta estructura es funcionalmente limitada, pero útil para infinidad de aplicaciones de almacenamiento de datos en tiempo de ejecución.
2.2 Heap
El heap al igual que el stack, es un bloque de memoria usado para almacenar objetos (i.e., instancias de tipos por referencia [1]). Cuando el programador declara e inicializa un objeto de una clase en particular, este objeto es almacenado en esta región de memoria especial, finalmente, retorna la referencia hacia esa locación y queda señalada por el identificador(es) declarado.
3. Boxing y Unboxing
Como aclaré desde el principio, prefiero utilizar los términos anglosajones para estas operaciones en lugar de incurrir en una traducción subjetiva que resulte vulgar. A continuación describiré conceptualmente el propósito de estas operaciones, además incluiré ejemplos concretos de su uso.
3.1 Boxing
De acuerdo con [1] y [3], la operación de boxing consiste en convertir un tipo por valor en un tipo por referencia. El tipo por valor debe implementar la interfaz o haber heredado desde la clase padre, abuelo, &c.
Por otro lado, todos los valores pasados a un tipo por referencia se localizarán en la estructura heap y el valor como tal será almacenado en el nuevo objeto creado a partir de la operación boxing.
Miremos esta operación a través de código fuente C#:
Creamos una variable entera (int):
int i = 123;
Luego copiamos el valor de esa variable en un objeto tipo object:
object o = i; // boxing implícito
O también esta versión:
object o = (object)i; // boxing explícito
En esta sentencia creamos una referencia -o-, que se localizará en el stack, la cual referenciará al valor de tipo int en el heap. En la Figura 2 [3] se describe pictóricamente este proceso. En la segunda versión se utiliza el operador de casting (object) para ejecutar un boxing explícito.
Figura 2. Proceso de boxing [3]. |
3.1.1 Ejemplo
En este ejemplo presento la versión completa del código anterior; además, haré énfasis en la diferencia de hacer unboxing sobre el valor de la variable y sobre la locación de la misma; esto se debe a que ambas locaciones de memoria son diferentes: los valores tienen asignadas ubicaciones diferentes. En [1] llaman a este efecto copiado de semántica.
Archivo BoxingPueba.cs:
Detallemos que a pesar de que hemos cambiado el valor de la variable i en la línea 11, esto no afecta el valor previo que almacenamos sobre la variable object o.
3.2 Unboxing
Con base en [1] y [3], puedo parafrasear que, la operación de unboxing consiste en convertir un objeto tipo object en un tipo por valor. La conversión también se puede realizar desde una interfaz que el tipo por valor implemente. Vale añadir [3], que esta operación consiste en:
- La compatibilidad del tipo por referencia con el tipo por valor.
- Copiado del valor del tipo por referencia al tipo por valor.
Veamos este fragmento de código:
int i = 123;
object o = i; // boxing implícito
int j = (int)o; // unboxing
Obsérvese el uso de el casting explícito en la última línea. La CLR se encarga de realizar esta operación .
Este fragmento tiene su equivalente, y está descrito en la Figura 3 [3].
Figura 3. Proceso de unboxing [3]. |
3.2.1 Ejemplo (Excepciones)
En este ejemplo veremos que el intento de hacer boxing sobre tipos no compatibles, generará la excepción InvalidCastException [2]. Su nominación indica que se trata de una excepción lanzada cuando hay una conversión -casting- inválida.
Archivo UnboxingExcepcion.cs:
A pesar de que no hemos tratado el tema de excepciones, lo que básicamente hacemos en las líneas 11-20 es capturar cualquier posible error que finalice de forma abrupta con el flujo de ejecución de la aplicación, en su lugar, preferimos tratar la excepción, en este caso mostrar un mensaje de error.
Otra excepción que se puede lanzar mientras tratamos de hacer unboxing es NullReferenceException [11]. La cual se activa cuando tratamos con una referencia null en el proceso de unboxing.
Archivo UnboxingExcepcion.cs:
A pesar de que no hemos tratado el tema de excepciones, lo que básicamente hacemos en las líneas 11-20 es capturar cualquier posible error que finalice de forma abrupta con el flujo de ejecución de la aplicación, en su lugar, preferimos tratar la excepción, en este caso mostrar un mensaje de error.
Otra excepción que se puede lanzar mientras tratamos de hacer unboxing es NullReferenceException [11]. La cual se activa cuando tratamos con una referencia null en el proceso de unboxing.
4. Rendimiento
De acuerdo con [4], esta es la consideración a tener en cuenta a la hora de trabajar con las operaciones boxing y unboxing:
"It is best to avoid using value types in situations where they must be boxed a high number of times, for example in non-generic collections classes such as System.Collections.ArrayList. You can avoid boxing of value types by using generic collections such as System.Collections.Generic.List. Boxing and unboxing are computationally expensive processes. When a value type is boxed, an entirely new object must be created. This can take up to 20 times longer than a simple reference assignment. When unboxing, the casting process can take four times as long as an assignment. For more information, see Boxing and Unboxing. "
En resumen, la operación de unboxing requiere 20 veces más recursos que la simple operación de asignación de referencias. Por otro lado, la operación de unboxing, puede tomar hasta 4 veces más recursos. Así que debemos tener mucho cuidado al tratar estas operaciones en programas de alta disponibilidad.
5. Preguntas y Respuestas
En Stack Overflow hay un par de preguntas interesantes en las que podemos profundizar profundizar, esas son:
- Operaciones boxing y unboxing de tipos int y string: [6]
- ¿El porqué de las operaciones boxing y unboxing?
6. Conclusiones
Aprendimos acerca del concepto de boxing (pasar un tipo por valor a uno por referencia), y su opuesta, la operación unboxing (conversión de un tipo por referencia a uno por valor). Estas operaciones son gestionadas por la CLR sobre las estructuras de datos stack y heap. En los ejemplos apreciamos cómo podemos capturar un entero (int) sobre un objeto object; así mismo, también observamos el manejo de excepciones para evitar terminaciones abruptas de las aplicaciones cuando se ha tratado de ejecutar una operación unboxing. Al final nos quedó claro que estas dos operaciones son costosas, en términos de ciclos de procesador y espacio en memoria.
7. Glosario
- Boxing
- Clase
- CLR
- Heap
- Tipos por referencia
- Tipos por valor
- Stack
- Unboxing
8. Enlaces & Literatura
[1]: C# 5.0 in a Nutshell by Joseph Albahari and Ben Albahari. Copyright 2012 Joseph Albahari and Ben Albahari, 978-1-449-32010-2.[2]: InvalidCastException Class (System) - http://msdn.microsoft.com/en-us/library/system.invalidcastexception%28v=vs.110%29.aspx
[3]: Boxing and Unboxing (C# Programming Guide) - http://msdn.microsoft.com/en-us/library/yz2be5wk.aspx
[4]: Performance (C# and Visual Basic) - http://msdn.microsoft.com/en-us/library/ms173196.aspx
[5]: Boxing And Unboxing C## Help | C# Help - http://www.csharphelp.com/2006/02/boxing-and-unboxing-c/
[6]: c# - boxing and unboxing in int and string - Stack Overflow - http://stackoverflow.com/questions/6423452/boxing-and-unboxing-in-int-and-string
[7]: .net - Why do we need boxing and unboxing in C#? - Stack Overflow - http://stackoverflow.com/questions/2111857/why-do-we-need-boxing-and-unboxing-in-c
[8]: Boxing and UnBoxing in C# - http://www.codeproject.com/Articles/8100/Boxing-and-UnBoxing-in-Csharp
[9]: ValueType Class (System) - http://msdn.microsoft.com/en-us/library/system.valuetype%28v=vs.110%29.aspx
[10]: [5] LIFO (computing) - Wikipedia, the free encyclopedia - http://en.wikipedia.org/wiki/LIFO_(computing)
[11]: NullReferenceException Class (System) - http://msdn.microsoft.com/en-us/library/system.nullreferenceexception.aspx
J
No hay comentarios:
Publicar un comentario
Envíe sus comentarios, dudas, sugerencias, críticas. Gracias.