Como programador del ecosistema .Net, seguramente te has dado cuenta de que el framework nos permite hacer conversiones entre tipos de datos que ya están implementadas. Estas conversiones pueden ser de dos tipos:
- Conversiones implícitas
- Conversiones explícitas
Las conversiones implícitas son aquellas para las que no hace falta indicar entre paréntesis (...)
la conversión:
double variable = 10;
En este código estamos asignando a una variable de tipo double
un valor de tipo int
:
Pero el compilador no nos dice nada y nos permite trabajar sin más novedad. Esto es lo que se llama una conversión implícita. En cambio, si lo que hacemos es:
int variable = 10.0;
Vamos a ver un error en el compilador que nos indica que no puede hacer una conversión implícita de double
a int
, y que utilicemos la conversión explícita:
No es ningún secreto precisamente, que este problema se soluciona poniendo (int)
delante de 10.0
:
int variable = (int)10.0;
Precisamente colocar entra paréntesis el tipo de dato al que queremos convertirlo, es lo que se llama hacer una conversión explícita, es decir, mediante esa sintaxis estamos indicando de manera explícita e inequívoca al compilador que queremos convertir un tipo de dato a otro diferente.
Llegados a este punto, me podrías decir que también funciona una línea como esta:
double variable = (double)10;
que hace lo mismo que si no le ponemos el (double)
delante. Así que: ¿dónde está la diferencia?.
La única diferencia de significado entre las conversiones explícitas e implícitas, tiene que ver con el hecho de si existe riesgo o no de perder en información.
El primer caso (sin los paréntesis) es una conversión implícita. Da igual el valor del literal int
que escribamos: un double
siempre va a poder contenerlo sin riesgo de perder información ya que es un tipo de mayor capacidad.
Por el contrario, en el segundo caso estamos obligados a hacer una conversión explícita. Esto es así porque un objeto de tipo double
sí puede contener valores que un tipo int
, de menor capacidad, no puede representar. Por ejemplo:
int variable = (int)10.5;
Un tipo entero no puede contener la parte decimal, por lo que va a truncar 10.5
convirtiéndolo en 10
, y por lo tanto perdiendo información:
Puede que por la manera en la que funciona nuestro programa nunca pueda darse ese caso en el que se pierde información, o simplemente nos dé igual que esos decimales se pierdan (imagina la poca importancia que pueden tener los decimales al hablar de los millones de litros de agua que tiene un embalse, por ejemplo). Si este es nuestro caso, con la conversión explícita le decimos al compilador que asumimos la posible pérdida de información, pero que aun así queremos hacerlo.
Cómo crear nuestras propias conversiones
Normalmente las propias conversiones del framework serán más que suficientes para nuestras necesidades. Sin embargo pueden darse muchos casos en los que será útil definir nuestras propias conversiones implícitas y explícitas. La plataforma .NET nos provee de una manera simple de hacerlo.
Cuando hagamos conversiones, es nuestra responsabilidad como desarrolladores decidir si existe una posibilidad de pérdida de información para hacer la conversión implícita o explícita.
Para crear una conversión entre tipos basta con escribir el operador estático que corresponda (implicit
o explicit
). Por ejemplo, imagina un escenario en el que tienes que gestionar temperaturas que pueden cambiar entre grados Celsisus y Fahrenheit. Partes de unas clases en tu código para gestionar este tipo de datos, que podrían ser algo como esto:
class Grado
{
public float Grados { get; set; }
}
class Celsius : Grado
{
public Celsius(float temp)
{
Grados = temp;
}
}
class Fahrenheit : Grado
{
public Fahrenheit(float temp)
{
Grados = temp;
}
}
Con este código, una posibilidad sería la de escribir métodos que nos hagan la conversión entre ellos, y a los que llamemos cada vez, por ejemplo, en la clase base (aunque podría estar en la propia clase o en cualquier otro sitio, como una extensión, por ejemplo):
class Grado
{
public float Grados { get; set; }
public Celsius ToCelsius()
{
return new Celsius(((5.0f / 9.0f) * (this.Grados - 32)));
}
public Fahrenheit ToFahrenheit()
{
return new Fahrenheit(((9.0f / 5.0f) * this.Grados + 32));
}
}
//-------- Ejemplos de conversión
Celsius cel = new Celsius(10);
Fahrenheit far = cel.ToFahrenheit();
Celsius cel2 = far.ToCelsius();
Pero cada vez queramos realizar la conversión vamos a tener que llamar al método correspondiente. Esto "ensucia" mucho el código, sobre todo si tenemos en cuenta que sería mucho más limpio y directo hacer conversiones implícitas entre ambos tipos de datos.
Como decíamos antes, para hacer una conversión implícita, basta con que en la clase desde la cual queremos convertir, definamos un operador estático con static implicit operator
. El ejemplo anterior con conversiones implícitas quedaría así:
//CONVERSION IMPLÍCITA
//====================
class Grado
{
public float Grados { get; set; }
}
class Celsius : Grado
{
public Celsius(float temp)
{
Grados = temp;
}
public static implicit operator Fahrenheit(Celsius c)
{
return new Fahrenheit((9.0f / 5.0f) * c.Grados + 32);
}
}
class Fahrenheit : Grado
{
public Fahrenheit(float temp)
{
Grados = temp;
}
public static implicit operator Celsius(Fahrenheit fahr)
{
return new Celsius((5.0f / 9.0f) * (fahr.Grados - 32));
}
}
//----- Ejemplos de conversión
Celsius cel = new Celsius(10);
Fahrenheit far = cel;
Celsius cel2 = far;
Como vemos, a cada clase le añadimos un operador de conversión implícita para la otra clase relacionada, y luego podemos realizar las conversiones de manera implícita como vemos en las últimas líneas de ejemplo. Esto nos permite obtener un código mucho más limpio y que cumple con las condiciones para hacer conversiones implícitas.
¿Sabías que toda conversión que sea declarada como implícita se puede utilizar de manera explícita y funcionará correctamente, pero el compilador nos indicará una advertencia?
Por contra, ahora vamos a imaginar que tenemos un programa que gestiona los alumnos y profesores de un centro académico. Podríamos tener unas clases como estas:
public class Persona
{
public string Name { get; set; }
}
public class Alumno : Persona
{
public string Facultad { get; set; }
public List<int> IdsCursos { get; set; }
}
public class Profesor : Persona
{
public string Facultad { get; set; }
public int IdContrato { get; set; }
}
En el caso de que un profesor pasase a ser alumno por el motivo que sea o al revés, parece directo que utilicemos una conversión para reutilizar los datos de los que ya disponemos. Como en este caso sí vamos a tener una pérdida de información entre las diferentes clases puesto que no manejan exactamente los mismos datos, la conversión tiene que ser explícita:
public class Persona
{
public string Name { get; set; }
}
public class Alumno : Persona
{
public string Facultad { get; set; }
public List<int> IdsCursos { get; set; }
public static explicit operator Profesor(Alumno alum)
{
return new Profesor { Name = alum.Name, Facultad = alum.Facultad, IdContrato = -1 };
}
}
public class Profesor : Persona
{
public string Facultad { get; set; }
public int IdContrato { get; set; }
public static explicit operator Alumno(Profesor prof)
{
return new Alumno { Name = prof.Name, Facultad = prof.Facultad, IdsCursos = new List<int>() };
}
}
//-----
Profesor teacher = new Profesor { Name = "Jorge", Facultad = "Ingenieros", IdContrato = 10 };
Alumno student = (Alumno)teacher;
Profesor teacher2 = (Profesor)student;
Un último aviso
El hecho de marcar una conversión como implícita o explícita tiene que cumplir estrictamente con el criterio de si existe riesgo de pérdida de información. Puede darse el caso de que hoy consideres despreciable la pérdida de información que se produce, pero que en algún momento no lo sea. Si has marcado la conversión como implícita, cualquiera que la utilice asume directamente que no hay pérdida de información. Si posteriormente la hay y el fallo está en esa conversión, puede suponer un gran quebradero de cabeza, más aun si el código forma parte de una librería y la persona que lo utiliza no puede ver el código, así que piénsalo bien siempre antes de definir la conversión.
Te dejamos un archivo ZIP con el código de ejemplo utilizado en este artículo: campusMVP-Conversiones-Implicito-Explicito.zip