Conversión entre Tipos La conversión entre tipos permite comparar y copiar valores entre diferentes tipos. En esta lección describiremos como convertir un tipo dado en otro. Conversión en VB y C# Existen 2 tipos de conversiones entre tipos: Conversión implícita: Son aquellas conversiones entre tipos que no requieren una sintaxis especial en el código. Este tipo de conversiones se emplean cuando tipo destino puede acomodar todos los valores posibles del tipo origen. Visual Basic por defecto permite la conversión implícita entre tipos independientemente si el tipo destino puede acomodar todos los valores del tipo origen. C# prohíbe las conversiones implícitas que causen una pérdida de rango o precisión. Ejemplos de conversiones implícitas: C# int i = 3; double d = 3.1416; d = i; // ahora d = 3 Visual Basic Dim i as Integer = 1 Dim d as Double = 3.1416 d = i ' ahora d = 3 Conversión explícita: Son aquellas conversiones entre tipos que requieren el uso de palabras reservadas del lenguaje o de de tipos del framework que ayudan en la conversión. Este tipo de conversiones se emplean cuando el tipo destino no puede acomodar todos los posibles valores del tipo origen y por lo tanto se requiere expresar explícitamente en el código esa intención. Las conversiones explícitas de emplean cuando se conoce de antemano que el valor origen puede ser convertido al tipo destino sin pérdida o error. Existen muchas maneras de realizar conversiones explícitas, entre ellas: A nivel de .Net Framework, esto es independiente del lenguaje elegido System.Convert Provee métodos que permiten la conversión entre tipos que implementan la interface System.IConvertible. Todos los tipos de datos del Framework implementan IConvertible. tipo.ToString Para convertir el valor de una instancia a una cadena que lo representa. Donde “tipo” es cualquier tipo de dato del framework o definido por el usuario que sobrecarga la función “ToString”. Si la conversión no es posible se lanza una excepción. tipo.Parse Para convertir una representación en cadena a un tipo específico. Donde “tipo” es cualquier tipo de dato básico del framework. Si la conversión no es posible se lanza una excepción. tipo.TryParse y tipo.TryParseExact Es similar al método “Parse”, sólo que no lanza una excepción si la conversión falla sino que devuelve el valor booleano false. En C# (tipo) expresión Los paréntesis se pueden utilizar para especificar conversiones de tipos. Donde “tipo” es el tipo al que deseamos convertir, que tiene definido operadores de conversión. De esta manera se invoca explícitamente al operador de conversión de un tipo a otro. La operación de conversión falla y genera un error en tiempo de compilación si no hay un operador de conversión definido entre los tipos que se desea convertir. as La palabra reservada “as” se utiliza para realizar conversiones entre tipos por referencia compatibles, si la conversión no es posible se retorna null en vez de lanzar una excepción. En VB CType(expresión, tipo) Es análogo al operador de conversión (tipo) expresión de C#. CBool(expresión), CInt(expresión), CStr(expresión), etc. Permiten la conversión entre los diferentes tipos básicos de Visual Basic. Estas funciones con compiladas en línea, proporcionando una mejor performance. Se recomienda utilizar estas funciones de conversión de tipos de Visual Basic en preferencia a las que proporciona .Net Framework con el método ToString() o la Clase Convert, ya que estas funciones están diseñadas para una interacción óptima con Visual Basic. DirectCast(expresión, tipo) Es similar a CType pero sólo opera en tipos por referencia. Para que la conversión sea exitosa se requiere una relación de herencia o de implementación entre los tipos de datos de los argumentos de lo contrario se lanza una excepción. Lo que significa que un tipo debe heredar o implementar el otro. TryCast(expresión, tipo) Es exactamente igual a DirectCast salvo que en vez de lanzar una excepción cuando la conversión no es posible devuelve Nothing. Boxing y Unboxing Boxing: Convierte un tipo por valor a un tipo por referencia. Cuando necesitamos tratar a tipos por valor como enteros, booleanos o estructuras como si fueran referencias se construye un objeto del tipo System.Object y se copia el valor a esa referencia. Boxing ocurre implícitamente cuando un tipo por valor es pasado como parámetro a un tipo object. Por ello es recomendable no diseñar métodos que acepten parámetros del tipo System.Object sino crear tantas sobrecargas del método como con tipos por valor a utilizar. La operación de Boxing, también ocurre implícitamente cuando se llaman a métodos virtuales de una estructura que hereda de System.Object, como por ejemplo el método ToString(), GetHash() y Equals. Por ello se recomienda sobrecargar estos métodos cuando se definen estructuras o nuevos tipos por valor. C# int p = 123; object o; o = p; // Boxing implícito o = (object) p; // Boxing explícito Visual Basic Dim p as Integer = 123; Dim o as Object; o = CType(p, Object) ' Boxing explícito Unboxing: Convierte un tipo por referencia a un tipo por valor. Ocurre cuando se asigna una referencia de un objeto a un tipo por valor. El valor almacenado en la referencia de objeto es copiado en la ubicación que ocupa el tipo por valor. C# int p; p = (int) o; // Unboxing explícito Visual Basic Dim p as Integer p = CType(o, Integer) ' Unboxing explícito La operación de Unboxing debe ser realizada con un operador de conversión explícito, si el valor almacenado en la referencia de objeto no es del mismo tipo del que se especifica en la conversión se generará una excepción. Consejos: Evitar aceptar los argumentos del tipo Object en métodos, para ello se recomienda el uso de Generics ó de crear varias sobrecargas del método en cuestión que acepte varios tipos por valor. Implementando Conversiones en Tipos Definidos por el Usuario Cuando creamos una aplicación muchas veces definimos tipos de datos propios para facilitar el manejo de información por parte de la misma; ahora bien, para lograr una integración óptima con el framework, debemos de tener en cuenta detalles como el manejo de conversión entre tipos y otras funcionalidades que pueden ser logradas a través de la sobre escritura de métodos. En este caso vamos a seguir concentrados en el tema de interés que hemos venido estudiando: las conversiones. Hay varias formas de implementar –o habilitar en su defecto- las conversiones en tipos que han sido definidos por uno mismo, y la técnica que escojamos dependerá del tipo de conversión que queramos hacer. Las formas más comunes se enlistan a continuación: Podemos definir los operadores de conversión para simplificar las conversiones de ampliación o reducción entre tipos numéricos. (Recordemos las densidades de los conjuntos que son representados por tipos int o float por ejemplo) Veamos ahora la sobre escritura de los métodos toString y Parse para conversión entre nuestra clase y la clase String. Esta es una muy buena práctica dado que como programadores asumimos que cada objeto tiene bien definida la forma de presentarse como String, a pesar de que generalmente no se implemente y quedamos con el nombre de la clase únicamente. Adicionalmente podemos implementar la interface System.IConvertible para habilitar las conversiones utilizando el método System.Convert. Ampliando un poco más sobre operadores de conversión, los podemos definir como los que nos van a permitir asignar de otros tipos hacia el nuestro y viceversa directamente. Ahora bien, usaremos la forma implícita si estamos ampliando dado que no vamos a perder precisión, y usaremos la forma explícita cuando por el otro lado se puede dar perdida de precisión. (Para visualizar mejor este concepto, pensemos en que creamos un tipo de datos llamado ”Fracción”, que nos permite guardar números fraccionales en términos de numerador y denominador. Si queremos convertir de nuestro tipo de datos a un tipo de datos float por ejemplo, no hay perdida de precisión -técnicamente-, mientras si lo pasamos a un Integer es todo lo contrario) Pensando ahora en la sobre escritura de métodos, simplemente implementamos la interface IConvertible y dejamos que el IDE implemente automáticamente la interface (él agregará métodos según la interface, de los cuales modificamos los que sean relevantes para nuestro tipo de datos. Ejemplo: probablemente el método ToDateTime sea irrelevante para nuestra clase Fracción).