Menú de navegaciónMenú
Categorías

La mejor forma de Aprender Programación online y en español www.campusmvp.es

?id=bc80851f-a817-4430-8e7e-0a3a9fcd1d34

Diferencias entre conversiones implícitas y explícitas en C# y cómo crear conversiones propias

Foto ornamental por Suzanne D. Williams en unsplash.com

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:

La imagen muestra que el tipo de dato que nos indica Visual Studio para el valor 10 es System.Int32

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:

La imagen muestra el error de compilación que genera la línea de código anterior

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:

La imagen muestra el ejemplo del código anterior

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

Fecha de publicación:
Jorge Turrado Jorge lleva en el mundo de la programación desde los tiempos de .Net Framework 3.0. Es experto en la plataforma .NET, en Kubernetes y en técnicas de integración continua entre otras cosas. Actualmente trabaja como Staff SRE en la empresa SCRM Lidl International Hub. Microsoft lo ha reconocido como MVP en tecnologías de desarrollo, es CNCF Ambassador y maintainer oficial de KEDA, el autoescalador de Kubernetes basado en eventos. Puedes seguirlo en Twitter: @JorgeTurrado o en su blog FixedBuffer Ver todos los posts de Jorge Turrado
Archivado en: Lenguajes y plataformas

Boletín campusMVP.es

Solo cosas útiles. Una vez al mes.

🚀 Únete a miles de desarrolladores

DATE DE ALTA

x No me interesa | x Ya soy suscriptor

La mejor formación online para desarrolladores como tú

Agregar comentario

Los datos anteriores se utilizarán exclusivamente para permitirte hacer el comentario y, si lo seleccionas, notificarte de nuevos comentarios en este artículo, pero no se procesarán ni se utilizarán para ningún otro propósito. Lee nuestra política de privacidad.