2. EFFICIENCY AND EFFECTIVENESS OF EC CO-OPERATION
2.5. Co-operation with other partners
En el Tema 4: Aspectos Léxicos ya vimos que para convertir objetos de un tipo de dato en otro se puede usar un operador de conversión que tiene la siguiente sintaxis:
(<tipoDestino>) <expresión>
Lo que este operador hace es devolver el objeto resultante de convertir al tipo de dato de nombre <tipoDestino> el objeto resultante de evaluar <expresión> Para que la conversión pueda aplicarse es preciso que exista alguna definición de cómo se ha de convertir a <tipoDestino> los objetos del tipo resultante de evaluar <expresión> Esto puede indicarse introduciendo como miembro del tipo de esos objetos o del tipo <tipoDestino> una redefinición del operador de conversión que indique cómo hacer la conversión del tipo del resultado de evaluar <expresión> a <tipoDestino>
Las redefiniciones de operadores de conversión puede ser de dos tipos:
• Explícitas: La conversión sólo se realiza cuando se usen explícitamente los operadores de conversión antes comentado.
• Implícitas: La conversión también se realiza automáticamente cada vez que se asigne un objeto de ese tipo de dato a un objeto del tipo <tipoDestino>. Estas conversiones son más cómodas que las explícitas pero también más peligrosas ya que pueden ocurrir sin que el programador se dé cuenta. Por ello, sólo deberían definirse como implícitas las conversión seguras en las que no se puedan producir excepciones ni perderse información al realizarlas.
En un mismo tipo de dato pueden definirse múltiples conversiones siempre y cuando el tipo origen de las mismas sea diferente. Por tanto, no es válido definir a la vez en un mismo tipo una versión implícita de una cierta conversión y otra explícita.
La sintaxis que se usa para hacer redefinir una operador de conversión es parecida a la usada para cualquier otro operador sólo que no hay que darle nombre, toma un único parámetro y hay que preceder la palabra reservada operator con las palabras reservadas explicit o implicit según se defina la conversión como explícita o implícita. Por ejemplo, para definir una conversión implícita de Complejo a float podría hacerse:
El lenguaje de programación C# Tema 11: Redefinición de operadores
public static implicit operator float(Complejo op) {
return op.ParteReal; }
Nótese que el tipo del parámetro usado al definir la conversión se corresponde con el tipo de dato del objeto al que se puede aplicar la conversión (tipo origen), mientras que el tipo del valor devuelto será el tipo al que se realice la conversión (tipo destino) Con esta definición podrían escribirse códigos como el siguiente:
Complejo c1 = new Complejo(5,2); // c1 = 5 + 2i float f = c1; // f = 5
Nótese que en la conversión de Complejo a float se pierde información (la parte imaginaria), por lo que sería mejor definir la conversión como explícita sustituyendo en su definición la palabra reservada implicit por explicit. En ese caso, el código anterior habría de cambiarse por:
Complejo c1 = new Complejo(5,2); // c1 = 5 + 2i float f = (float) c1; // f = 5
Por otro lado, si lo que hacemos es redefinir la conversión de float a Complejo con: public static implicit operator Complejo(float op)
{
return (new Complejo(op, 0)); }
Entonces se podría crear objetos Complejo así: Complejo c2 = 5; // c2 = 5 + 0i
Véase que en este caso nunca se perderá información y la conversión nunca fallará, por lo que es perfectamente válido definirla como implícita. Además, nótese como redefiniendo conversiones implícitas puede conseguirse que los tipos definidos por el usuario puedan inicializarse directamente a partir de valores literales tal y como si fuesen tipos básicos del lenguaje.
En realidad, cuando se definan conversiones no tiene porqués siempre ocurrir que eltipo destino indicado sea el tipo del que sea miembro la redefinición, sino que sólo ha de cumplirse que o el tipo destino o el tipo origen sean de dicho tipo. O sea, dentro de un tipo de dato sólo pueden definirse conversiones de ese tipo a otro o de otro tipo a ese. Sin embargo, al permitirse conversiones en ambos sentidos hay que tener cuidado porque ello puede producir problemas si se solicitan conversiones para las que exista una definición de cómo realizarlas en el tipo fuente y otra en el tipo destino. Por ejemplo, el siguiente código provoca un error al compilar debido a ello:
class A {
static void Main(string[] args) {
A obj = new B(); // Error: Conversión de B en A ambigua }
public static implicit operator A(B obj) {
return new A(); }
} class B {
public static implicit operator A(B obj) {
return new A(); }
}
El problema de este tipo de errores es que puede resulta difícil descubrir sus causas en tanto que el mensaje que el compilador emite indica que no se pueden convertir los objetos A en objetos B pero no aclara que ello se deba a una ambigüedad.
Otro error con el que hay que tener cuidado es con el hecho de que puede ocurrir que al mezclar redefiniciones implícitas con métodos sobrecargados puedan haber ambiguedades al determinar a qué versión del método se ha de llamar. Por ejemplo, dado el código:
using System; class A {
public static implicit operator A(B obj) {
return new A(); }
public static void MétodoSobrecargado(A o) {
Console.WriteLine("Versión que toma A"); }
public static void MétodoSobrecargado(C o) {
Console.WriteLine("Versión que toma C"); }
static void Main(string[] args) { MétodoSobrecargado(new B()); } } class B {
public static implicit operator C(B obj) {
return new C(); }
} class C
El lenguaje de programación C# Tema 11: Redefinición de operadores
{}
Al compilarlo se producirá un error debido a que en la llamada a MétodoSobrecargado() el compilador no puede deducir a qué versión del método se desea llamar ya que existen conversiones implíctas de objetos de tipo B en cualquiera de los tipos admitidos por sus distintas versiones. Para resolverlo lo mejor especificar explícitamente en la llamada la conversión a aplicar usando el operador () Por ejemplo, para usar usar la versión del método que toma como parámetro un objeto de tipo A se podría hacer:
MétodoSobrecargado ( (A) new B());
Sin embargo, hay que tener cuidado ya que si en vez del código anterior se tuviese: class A
{
public static implicit operator A(B obj) {
return new A(); }
public static void MétodoSobrecargado(A o) {
Console.WriteLine("Versión que toma A"); }
public static void MétodoSobrecargado(C o) {
Console.WriteLine("Versión que toma C"); }
static void Main(string[] args) { MétodoSobrecargado(new B()); } } class B {
public static implicit operator A(B obj) {
return new A(); }
public static implicit operator C(B obj) { return new C(); } } class C {}
Entonces el fuente compilaría con normalidad y al ejecutarlo se mostraría el siguiente mensaje que demuestra que se ha usado la versión del método que toma un objeto C.
Finalmente, hay que señalar que no es posible definir cualquier tipo de conversión, sino que aquellas para los que ya exista un mecanismo predefinido en el lenguaje no son válidas. Es decir, no pueden definirse conversiones entre un tipo y sus antecesores (por el polimorfismo ya existen), ni entre un tipo y él mismo, ni entre tipos e interfaces por ellos implementadas (las interfaces se explicarán en el Tema 16: Interfaces)
El lenguaje de programación C# Tema 12: Delegados y eventos
TEMA 12: Delegados y eventos
Concepto de delegado
Un delegado es un tipo especial de clase cuyos objetos pueden almacenar referencias a uno o más métodos de tal manera que a través del objeto sea posible solicitar la ejecución en cadena de todos ellos.
Los delegados son muy útiles ya que permiten disponer de objetos cuyos métodos puedan ser modificados dinámicamente durante la ejecución de un programa. De hecho, son el mecanismo básico en el que se basa la escritura de aplicaciones de ventanas en la plataforma .NET. Por ejemplo, si en los objetos de una clase Button que represente a los botones estándar de Windows definimos un campo de tipo delegado, podemos conseguir que cada botón que se cree ejecute un código diferente al ser pulsado sin más que almacenar el código a ejecutar por cada botón en su campo de tipo delegado y luego solicitar la ejecución todo este código almacenado cada vez que se pulse el botón. Sin embargo, también son útiles para muchísimas otras cosas tales como asociación de código a la carga y descarga de ensamblados, a cambios en bases de datos, a cambios en el sistema de archivos, a la finalización de operaciones asíncronas, la ordenación de conjuntos de elementos, etc. En general, son útiles en todos aquellos casos en que interese pasar métodos como parámetros de otros métodos.
Además, los delegados proporcionan un mecanismos mediante el cual unos objetos pueden solicitar a otros que se les notifique cuando ocurran ciertos sucesos. Para ello, bastaría seguir el patrón consistente en hacer que los objetos notificadores dispongan de algún campo de tipo delegado y hacer que los objetos interesados almacenen métodos suyos en dichos campos de modo que cuando ocurra el suceso apropiado el objeto notificador simule la notificación ejecutando todos los métodos así asociados a él.
.