Conversiones definidas
Con la sobrecarga de operadores es posible
sumar objetos de clases distintas e incompatibles. Las conversiones definidas
vienen a abundar un poco más sobre estos conceptos, permitiendo hacer compatibles
tipos que antes no lo eran.
El fragmento de código a continuación es extraño
al tener que usar constructores y propiedades:
Metros m=new Metros(10);
Centimetros c=new Centimetros(10);
Metros SumaMetros=m+c;
Centimetros SumaCentimetros=c+m;
Console.WriteLine(SumaMetros.Cantidad);
Console.WriteLine(SumaCentimetros.Cantidad);
Las conversiones definidas permiten
manejarlo de un modo más natural:
Metros m=(Metros) 10;
Centimetros c=(Centimetros) 10;
Metros SumaMetros=m+c;
Centimetros SumaCentimetros=c+m;
Console.WriteLine((double) SumaMetros);
Console.WriteLine((double) SumaCentimetros);
Ya no es necesario complicar el código con
constructores ni con la propiedad Cantidad para escribir su valor con
WriteLine, pues se han creado conversiones definidas que han hecho compatibles
esas clases con el tipo double. Gracias a esto es posible convertir a metros un
número (primera línea) y también se puede convertir a double un objeto de la
clase Metros, dentro de WriteLine. A continuación el código de estas
conversiones definidas en la clase Metros:
public static explicit operator Metros(double cant)
{
Metros retValue=new Metros(cant);
return retValue;
}
public static explicit operator double(Metros m)
{
return m.Cantidad;
}
En la primera conversión definida, la
conversión que es Metros, de modo que es
posible convertir en objetos de esta clase cualquier número de tipo double con
la sintaxis (Metros) numero. En la segunda se hace justo lo contrario, se define
la conversión double para convertir en datos de este tipo objetos de la clase
Metro, mediante la sintaxis (double) ObjetoMetros. En la primera conversión
definida el argumento es de tipo double, y en la segunda es de tipo Metros.
La palabra explicit hace que sea
obligatorio usar el operador de conversión para convertir entre estos dos
tipos. No se puede asignar un valor de tipo double a un objeto de la clase
Metros sin usar el operador de conversión (Metros). También es posible definir
estas conversiones como implícitas, de modo que el operador de conversión no
fuera necesario. Para ello, bastaría con poner la palabra implicit en lugar de
explicit, esto es, así:
public static implicit operator Metros(double cant)
{
Metros retValue=new Metros(cant);
return retValue;
}
public static implicit operator double(Metros m)
{
return m.Cantidad;
}
El resultado todavía es mucho más
manejable. El código cliente de las clases Metros y Centimetros quedaría así.
Metros m=10;
Centimetros c=10;
Metros SumaMetros=m+c;
Centimetros SumaCentimetros=c+m;
Console.WriteLine(SumaMetros);
Console.WriteLine(SumaCentimetros);
Ni siquiera se ha necesitado hacer las
conversiones explícitas, ya que las conversiones definidas en ambas clases se
han hecho implícitas con la palabra clave implicit en lugar de explicit. Ahora
es posible asignar el número 10, que es
de tipo int al no tener el sufijo D, aunque la conversión definida está creada
solamente para el tipo double, esto es así, porque el tipo double es compatible
con el tipo int, de modo que basta con hacer la conversión definida con el tipo
double para que sirva con todos los tipos numéricos.
Si se intenta asignar un objeto de la
clase centímetros a otro de la clase metros y viceversa:
Metros cEnMetros=c;
Centimetros mEnCentimetros=m;
El compilador genera otro error. Antes de
hacer eso es necesario crear también las conversiones definidas en Metros y
Centímetros para hacerlas compatibles y, también, para que se asignen los
valores adecuadamente:
En la clase Metros.
public static implicit operator Metros(Centimetros
c)
{
Metros retValue=new Metros();
retValue.Cantidad=c.Cantidad/100;
return retValue;
}
En la clase Centímetros:
public static implicit operator Centimetros(Metros
m)
{
Centimetros retValue=new Centimetros();
retValue.Cantidad=m.Cantidad*100;
return retValue;
}
No basta con escribir las conversiones
para hacerlas compatibles, sino que también hay que escribirlas correctamente
para que se asignen valores adecuados, es decir, si a "m" (que es de
tipo Metros) le asignamos 10 centímetros, "m" tiene que valer,
necesariamente, 0.1 metros.
Otro detalle, sería reescribir el método
ToString (este método se hereda siempre de System.Object). Dicho método tendría
que definirse así tanto en la clase Metros como en la clase Centímetros:
public override string ToString()
{
return this.cantidad.ToString();
}
La sobrecarga de operadores y las
conversiones definidas se deben utilizar únicamente cuando el resultado final
sea más natural e intuitivo que sin utilizarlas.
Si a través de la sobrecarga de operadores
y conversiones definidas se convierten dos tipos como compatibles, se debe
intentar que sean compatibles a todos los niveles posibles. Como se acaba de
ver, en las clases Metros y Centimetros, habría que sobrecargar también los
operadores /, *, %, >, >=, <, <=, == y != para hacer posibles las
divisiones, multiplicaciones, el cálculo de restos y las comparaciones (es
decir, la comparación 1 metro == 100 centímetros debería retornar true).
Estructuras
Las clases representan tipos referencia,
cuando se instancia un objeto de una determinada clase, lo que hace, es crear
un objeto en la memoria, y la variable devuelve internamente un puntero al
comienzo de dicho objeto. Para crear un tipo valor, se hace con las
estructuras.
Usar clases para almacenar algo que
realmente son valores va a restar eficiencia y también funcionará de un modo
distinto y peor.
Se ha diseñado una clase Moneda. Para hacer
una asignación para crear una copia de un objeto de esta clase hacemos esto:
Moneda m
m=new Moneda(10);
Moneda m2=m.Clone();
Resulta bastante raro, no por invocar el
método Clone, sino por tener que invocarlo para crear una simple copia de su
valor en otra variable, puesto que la clase Moneda puede ser considerada, como
un valor.
Moneda m
m=10;
Moneda m2=m;
Este último código es más fácil y natural,
para ello se ha utilizado una estructura en lugar de una clase:
public struct Moneda
{
private double valor;
public static implicit operator Moneda(double d)
{
Moneda m;
m.valor=Math.Round(d,2);
return m;
}
public static implicit operator double(Moneda m)
{
return m.valor;
}
public static Moneda operator++(Moneda m)
{
m.valor++;
return m;
}
public static Moneda operator--(Moneda m)
{
m.valor--;
return m;
}
public override string ToString()
{
return this.valor.ToString();
}
}
Se parece mucho a una clase, pero aquí se ha utilizado la palabra struct en lugar de class. La mayoría de los
conceptos de las clases sirven también para las estructuras. Pero, no todos,
pues hay cosas habituales en las clases que no se pueden hacer con las
estructuras:
Se pueden
escribir constructores, pero estos han de tener argumentos. No se puede escribir un constructor sin
argumentos para una estructura.
Al tratarse de un tipo valor que se
almacena en la pila, las estructuras no admiten destructores.
No soportan
herencia, por lo que no pueden ser utilizadas como clases base ni como clases
derivadas. Pero sí pueden implementar interfaces, y lo hacen igual que las
clases.
Los campos de
una estructura no pueden ser inicializados en la declaración. Por ejemplo, no es
posible decir int a=10; porque daría un error de compilación.
Las estructuras, si no se especifican
los modificadores ref o out, se
pasan por valor, mientras que las clases se pasan siempre por referencia.
No es necesario
instanciar una estructura para poder usar sus miembros, en el segundo
fragmento de código del ejemplo no se ha instanciado el objeto m.
No hay comentarios:
Publicar un comentario