Conversiones de tipos personalizadas (VB y C#)
Una de las novedades que introdujo VB.NET en su version 2005 fue la sobrecarga de operadores, hasta entonces exclusiva de C# (siempre hablando de lenguajes .NET).
A pesar del tiempo que ha transcurrido desde que VB.NET soporta la sobrecarga de operadores seguramente será una desconocida para muchos. Resumiendo - muchisimo -, diré que es una caracteristica que nos permite definir como se va a comportar el programa cuando se ejecute un operador (suma, resta ...).
De este forma podríamos tener una clase - Punto2D, con dos coordenadas x, y - y definir que cuando se sumen dos puntos se sumen sus coordenadas. El siguiente ejemplo muestra como se hace:
Public Class Punto2D Public X As Integer Public Y As Integer Public Shared Operator +(ByVal punto1 As Punto2D, _ ByVal punto2 As Punto2D) As Punto2D Dim resultado As New Punto2D resultado.X = punto1.X + punto2.X resultado.Y = punto1.Y + punto2.Y Return resultado End Operator End Class
|
Cuando se sumen dos instancias de Punto2D, se ejecutará el código definido en el operador, sumando las coordenadas X e Y de cada punto, y retornando una nueva instancia de Punto2D con los valores asignados.
Dim p1 As New Punto2D With {.X = 5, .Y = 1500}Dim p2 As New Punto2D With {.X = 10, .Y = 100}'Aqui se ejecuta la sobrecarga del operador + Dim p3 As Punto2D = p1 + p2 Console.WriteLine( "{0} {1}", p3.X, p3.Y)Console.ReadLine()
|
Bien, volvamos ahora al tema principal del articulo - como realizar conversiones de tipos personalizadas -. Otra de las opciones que nos ofrece la sobrecarga de operadores es la de definir como se convierte un tipo en otro.
Vamos a verlo con un ejemplo: tenemos dos clases - Cliente y Empleado - y nuestro objetivo es convertir una instancia de cliente en empleado.
Las clases del ejemplo son las siguientes:
Public Class Empleado Private _nombre As String Public Property Nombre() As String Get Return _nombre End Get Set(ByVal value As String) _nombre = value End Set End Property Private _apellidos As String Public Property Apellidos() As String Get Return _apellidos End Get Set(ByVal value As String) _apellidos = value End Set End Property Private _fechaNacimiento As DateTime Public Property FechaNacimiento() As DateTime Get Return _fechaNacimiento End Get Set(ByVal value As DateTime) _fechaNacimiento = value End Set End Property End Class
|
y la clase Cliente:
Public Class Cliente Private _nombre As String Public Property Nombre() As String Get Return _nombre End Get Set(ByVal value As String) _nombre = value End Set End Property Private _apellidos As String Public Property Apellidos() As String Get Return _apellidos End Get Set(ByVal value As String) _apellidos = value End Set End Property Private _fechaNacimiento As DateTime Public Property FechaNacimiento() As DateTime Get Return _fechaNacimiento End Get Set(ByVal value As DateTime) _fechaNacimiento = value End Set End Property End Class
|
Si intentamos hacer lo siguiente:
Dim empleado As New Empleado With _ {.Nombre = "Pedro", _ .Apellidos = "Herrarte", _ .FechaNacimiento = New DateTime(1975, 5, 13)}Dim cliente As Clientecliente = CType(empleado, Cliente)
|
Obtendremos el siguiente error:
Error 1 Value of type 'ConsoleApplication.Empleado' cannot be converted to 'ConsoleApplication.Cliente'.
Para definir la conversión debemos emplear el siguiente código donde definimos el operador CType .Especial atencion al uso de la palabra clave Narrowing - también podríamos haber usado Widening -, posteriormente explicaré las diferencias.
Public Class Cliente 'El cuerpo de la clase cliente ... Public Shared Narrowing _ Operator CType(ByVal empleado As Empleado) As Cliente Dim cliente As New Cliente cliente.Nombre = empleado.Nombre cliente.Apellidos = empleado.Apellidos cliente.FechaNacimiento = empleado.FechaNacimiento Return cliente End Operator End Class
|
Ahora ya podemos ejecutar la conversion anterior.
Dim empleado As New Empleado With _ {.Nombre = "Pedro", _ .Apellidos = "Herrarte", _ .FechaNacimiento = New DateTime(1975, 5, 13)} Dim cliente As Cliente cliente = CType(empleado, Cliente) Console.WriteLine("{0} {1} {2:dd/MM/yyyy}", _ cliente.Nombre, _ cliente.Apellidos, _ cliente.FechaNacimiento) Console.ReadLine()
|
Del mismo modo podríamos escribir el código de conversión te tipo en C# - la sintaxis cambia pero no la funcionalidad:
class Cliente{ // ... public static explicit operator Cliente(Empleado empleado) { Cliente cliente = new Cliente(); cliente.Nombre = empleado.Nombre; cliente.Apellidos = empleado.Apellidos; cliente.FechaNacimiento = empleado.FechaNacimiento; return cliente; } }
|
Cuando hemos definido la conversion hemos utilizado la palabra la clave Narrowing.Narrowing indica que la conversion debe realizarse de forma explicita, es decir es necesario usar CType -siempre y cuando "Option Strict" está activado.
Dim empleado As New Empleado With ...Dim cliente As Clientecliente = CType(empleado, Cliente)
|
Widening implica conversion implicita, es decir NO es necesario usar CType de forma que este código sería válido:
Dim empleado As New Empleado With ...Dim cliente As Clientecliente = empleado
|
Como ejercicio de reflexion nos podemos preguntar porque se han utilizado Narrowing y Widening en lugar de explicit e implicit como se hace en C#.
Un último apunte, es reconmendable forzar siempre la conversión explicita y tener siempre en cuenta que la conversion de un tipo en otro puede ocasionar la perdida de datos si el rango de destino es inferior al original - ver este enlace http://msdn.microsoft.com/en-us/library/k1e94s7e(VS.80).aspx -, (por ejemplo si convertimos en dato long a integer).
Saludos , DJK