domingo, 9 de noviembre de 2008

Sobre el casting ¿Carencia del compilador?

Hola a todos.

Bueno, seguro que cuando leáis este post lo primero que vais a pensar es "¿Qué hace el
friki este escribiendo un post un sábado a las 4 de la mañana? ¿No tiene nada mejor que hacer?"
Vale. Soy un frikazo ¿Y qué?

En fin, he estado dando vueltas a una cosa y quería comentarla con los máquinas de mis colegas de gremio.
No voy a entrar en el origen de mi problema. Voy a ir directamente a la esencia del mismo: Hacer casting entre clase padre e hija.

Voy a hacer un ejemplo que simplifica al máximo el problema.
Tenemos una clase base y una clase derivada.

public class BaseClass
{
private int property1;
public int Property1
{
get { return property1; }
set { property1 = value; }
}
}

public class DerivedClass : BaseClass
{
private int property2;
public int Property2
{
get { return property2; }
set { property2 = value; }
}
}

Vamos a ver todos los castings que podemos hacer.

De hija a base:

static void Main()
{
DerivedClass obj1 = new DerivedClass();
BaseClass obj2 = obj1;
}

Chupado. Como un objeto DerivedClass ya es un BaseClass, se puede hacer una conversión implítica.

De base a hija:

static void Main()
{
BaseClass obj1 = new DerivedClass();
DerivedClass obj2 = (DerivedClass) obj1;
}

Perfecto. Hacemos un casting de la clase base a la derivada. Es necesario hacer un casting explícito porque un objeto de la clase base no tiene por qué ser de la clase derivada. En nuestro caso sí lo es porque obj1 contiene un DerivedClass. No fallará ni en tiempo de complilación ni de ejecución. El compilador nos deja hacer esto sabiendo que nos arriesgamos a un petardazo en tiempo de ejecución, como ocurre en el siguiente ejemplo.

static void Main()
{
BaseClass obj1 = new BaseClass();
DerivedClass obj2 = (DerivedClass) obj1;
}

Compila pero da un petardazo lógico y normal en tiempo de ejecución. obj1 no contiene un DerivedClass.

En mi aplicación necesito hacer este casting. La solución es muy sencilla y a todos se nos ocurre enseguida. Haz un constructor de DerivedClass que reciba un BaseClass por parámetro. De esa forma la clase derivada queda así.

public class DerivedClass : BaseClass
{
public DerivedClass()
{
}

public DerivedClass(BaseClass parent)
{
this.Property1 = parent.Property1;
}

private int property2;
public int Property2
{
get { return property2; }
set { property2 = value; }
}
}

y el siguiente ejemplo funciona:

static void Main()
{
BaseClass obj1 = new BaseClass();
DerivedClass obj2 = new DerivedClass(obj1);
}

Hemos pasado de un objeto de la clase base a un objeto de la clase derivada.
Mi idea fue: Yo kiero ser más chulo y hacer eso con un casting (como el ejemplo que falla) en lugar de con el constructor. Pues me implemento un conversor propio (por ejemplo en la clase derivada) y a correr.
La clase derivada quedaría así:

public class DerivedClass : BaseClass
{
private int property2;
public int Property2
{
get { return property2; }
set { property2 = value; }
}

public static explicit operator DerivedClass(BaseClass parent)
{
DerivedClass result = new DerivedClass();
result.Property1 = parent.Property1;
}
}
Hago el conversor explícito en lugar de implícito para no ser demasiado chulo.

El código que lo prueba queda:

static void Main()
{
BaseClass obj1 = new BaseClass();
DerivedClass obj2 = (DerivedClass) obj1;
}

Pues bien. Esto, que a mi me parecía lógico, no compila. Da el siguiente error en la clase DerivedClass:
"user defined conversion to/from base class"
El número de error es CS0553
He investigado un poco y he visto la página de Microsoft donde está documentado el error. http://msdn.microsoft.com/en-us/library/031a701z(VS.80).aspx
Según ellos, no puedes hacerlo porque no lo necesitas.
Estamos de acuerdo en que no necesito un conversor propio para pasar de una clase hija a una clase padre, pero para pasar de una clase padre a una hija sí estaría bien. De hecho en el link que os he mandado hay un comentario de uno al que le pasa lo mismo que a mi.
Nadie le ha dado respuesta desde Microsoft...

Espero vuestros comentarios.

Saludos.

1 comentario:

Anónimo dijo...

En primer lugar confirmar que eres un friki, ;).
Por otro lado no tengo más que maldecir el momento que posteaste ya que llevo perdido unas cuantas horas. Por si os sirve de algo os comento mis pruebas (omitiré las obvias) que se resumen en las siguientes:
- Lo primero que probe es limitar el error de compilacion con las directivas de compilacion y preprocesador. El resultado fue que pude compilar correctamente pero el fallo saltó en tiempo de ejecución. (La próxima vez, tomate unas copas como todo el mundo).
- Comprobe si pasando un puntero al operador, este no lo identificaba como clase base. Otro error (Mario, me caes mal).
- Con mi mente preclara pensé: "Si no funciona ni una ni otra, seguro que en conjunto funciona". Ayyy, si Bool levantase la cabeza le pega algo con mi afirmación, (falso and falso) = verdadero. El resultado... pues claro que no.

Sin mas me despido que tengo que recuperar el tiempo perdido