Últimamente me estoy mirando mucho C# y la verdad es que todo lo que he leído me encanta, pero hay algo que no acabo de captar. Todo lo que he leído lleva a pensar que todo el diseño lo han hecho pensando en que haya la menor parte de errores difíciles de resolver por el programador, pero luego me encuentro con esto:
- Tengo una clase A con un metodo virtual foo(), y otra clase B que deriva de A i redefine ese método:
- En el constructor/destructor de la clase base, si llamo a foo() se llamará al de B(). ¿eso como se come? En C++ se llamaría al de A y me parece lo más obvio porque, en el constructor, la parte de B no estaría todavía inicializada (ya que C# inicializa los campos a 0 y demás), y en el destructor la parte de B ya se habría destruido.
¿alguien sabe decirme porqué lo han hecho así?
En C++ seria suficiente con A::foo(); para llamar al foo() de la clase base A, o eso creo.... :unsure:
PD:seguro que si lo llamas desde el constructor/destructor de B, se llama al de A??? diria que no...(en C++)
Hola.
Si te refieres a esto:
namespace Test
{
public class A
{
public A()
{
foo();
}
protected virtual void foo()
{
System.Console.WriteLine("A::foo()");
}
}
public class B : A
{
public B() : base()
{
}
protected override void foo()
{
System.Console.WriteLine("B::foo()");
}
}
public class Program
{
private static void Main(string[] args)
{
A a = new A();
B b = new B();
}
}
}
el objeto del tipo A llama al método foo() de A y el B al método foo() de B.
Saludos.
CitarPD:seguro que si lo llamas desde el constructor/destructor de B, se llama al de A??? diria que no...(en C++)
creo que no me has entendido o no me he explicado.. es justo lo contrario: si lo llamo desde A se llama al de B (desde un método normal es lo deseable, pero no desde el constructor/destructor).
@BeRSeRKeR: me refería a cuando se utiliza poliformismo; usando tus mismas clases A y B:
public class Program
{
private static void Main(string[] args)
{
A a = new B();
}
}
Aquí se llamaría a B::foo() cuando todavía no se ha construído la parte del objeto correspondiente a B, por lo que si yo hago uso en ese método de campos o métodos de B, lo que ocurriría digamos que es "undefined" :P
¿Entonces nadie sabe nada sobre porqué lo han hecho así?
En C++ no funciona como tu dices tio :S
las funciones virtuales sirven para cuando una clase hereda de ti, y se construya a partir de la clase base, pueda ser invocada la funcion de la clase heredada y no de la clase base.
en el ejemplo anterior, si foo es
virtual y haces esto:
CitarA *objeto = new B();
objeto ->foo();
se llamará a la funcion foo de B
en cambio si
no defines la funcion foo como virtual y haces lo mismo
CitarA *objeto = new B();
objeto ->foo();
se llamará a la funcion foo de la clase A y no de la clase B
me he sabido explicar?
Cita de: "CoLSoN2"¿Entonces nadie sabe nada sobre porqué lo han hecho así?
para mi entender era la forma más lógica de hacerlo :S
virtual -> preferencia hijos
no virtual -> funcion propia de la clase del primer tipo
esto ahora lo abré dejado más lioso :P
ejemplo
tenemos la clase cPrimitiva que tiene una función dibujar que escribe por pantalla esto: "cPrimitiva::dibujar" y tenemos 2 clases derivadas de cPrimitiva llamadas
que implementan esa funcion dibujando en una ventana un triangulo y un cuadrado respectivamente, y no escriben nada.
que pasa si esta funcion es:
virtual
Citar
el siguiente codigo:
cPrimitiva *objeto = new cTriangulo();
objeto -> dibujar();
//dibujamos por pantalla un triangulo
objeto = new cRectangulo();
objeto -> dibujar();
//dibujamos un cuadrado por pantalla
objeto = new cPrimitiva();
objeto -> dibujar();
//escribimos por linea de comandos "cPrimitiva::dibujar"
no virtual
Citar
el siguiente codigo:
cPrimitiva *objeto = new cTriangulo();
objeto -> dibujar();
//escribimos por linea de comandos "cPrimitiva::dibujar"
objeto = new cRectangulo();
objeto -> dibujar();
//escribimos por linea de comandos "cPrimitiva::dibujar"
objeto = new cPrimitiva();
objeto -> dibujar();
//escribimos por linea de comandos "cPrimitiva::dibujar"
/** Hasta aquí el código de antes con las supuestas diferencias */
cTriangulo triangulo();
triangulo -> dibujar();
//dibuja por pantalla un triangulo
espero que haya quedado claro ahora por si antes me expliqué mal :S
esta es la gracia del POLIMORFISMO :) poder usar el Objeto Base como si fuera el Objeto heredado para poder compartir funciones :) y tener clases comunes para diversos tipos de datos "medianamente diferentes".
;)
@Buffon: lo que dices es totalmente válido, pero te olvidas del detalle de que yo hablo de llamadas a esos métodos
en los constructores. Prueba a ejecutar este código en C++ (si tiene algún fallo sintáctico corrígelo):
#include <stdio.h>
class A
{
public:
A()
{
foo();
}
virtual void foo()
{
printf("A");
}
};
class B : public A
{
public:
B() : A()
{
foo();
}
virtual void foo()
{
printf("B");
}
};
void main()
{
A* a = new B();
delete a;
};
Verás que muestra por pantalla: "AB", mientras que si lo portas a C#, verás que muestra "BB", es decir desde A::A() también llama a B::foo(), lo cual es estúpido porque la parte correspondiente a B del objeto aún no ha sido creada (también puedes probar la versión análoga con el destructor, y pasa lo mismo). Como supongo que sabes, en C# los campos de las clases se inicializan antes de llamar al constructor (numericos a 0 y demás), pero en este caso, al llamar a B::foo() desde A::A() (lo que ocurre en C#), como es aparte del objeto aún no se ha construido, no estarían inicializados los campos, y hacer uso de ellos (pensando que tendrían un valor determinado) podría dar lugar a errores difíciles de detectar.
He aquí un extracto del ebook "El Lenguaje de Programación C#", de José Antonio González Seco, respecto al tema:
Citar
Llamadas polimórficas en constructores
Es conveniente evitar en la medida de lo posible la realización de llamadas a métodos virtuales dentro de los constructores, ya que ello puede provocar errores muy difíciles de detectar debido a que se ejecuten métodos cuando la parte del objeto que manipulan aún no se ha sido inicializado. Un ejemplo de esto es el siguiente:
using System;
public class Base
{
public Base()
{
Console.WriteLine("Constructor de Base");
this.F();
}
public virtual void F()
{
Console.WriteLine("Base.F");
}
}
public class Derivada:Base
{
Derivada()
{
Console.WriteLine("Constructor de Derivada");
}
public override void F()
{
Console.WriteLine("Derivada.F()");
}
public static void Main()
{
Base b = new Derivada();
}
}
La salida por pantalla mostrada por este programa al ejecutarse es la siguiente:
Constructor de Base
Derivada.F()
Constructor de Derivada
Lo que ha ocurrido es lo siguiente: Al crearse el objeto Derivada se ha llamado a su constructor sin parámetros, que como no tiene inicializador implícitamente llama al constructor sin parámetros de su clase base. El constructor de Base realiza una llamada al método virtual F(), y como el verdadero tipo del objeto que se está construyendo es Derivada, entonces la versión del método virtual ejecutada es la redefinición del mismo incluida en dicha clase. Por último, se termina llamando al constructor de Derivada y finaliza la construcción del objeto.
Nótese que se ha ejecutado el método F() de Derivada antes que el código del constructor de dicha clase, por lo que si ese método manipulase campos definidos en Derivada que se inicializasen a través de constructor, se habría accedido a ellos antes de inicializarlos y ello seguramente provocaría errores de causas difíciles de averiguar.
Si está muy claro lo que es el polimorfismo, lo raro es que el destructor de la clase base llame a un método de una clase derivada, cuando se supone que si se está destruyendo la clase base (al ser el destructor virtual) ya se han destruido los objetos de la clase derivada. Este comportamiento (en teoria) puede provocar que el dtor de la clase base intente usar de manera indirecta miembros de clase derivada, con lo que algo petará ahí seguramente.
A ese respecto, C# funciona de manera diferente a C++, la cuestión es ¿por qué?
De acuerdo con Ffelagund. Ahí tiene que fallar algo. Da ganas de abrir el C# y probar :ph34r:
sync
Hola,
usa new en vez de override y ya está solucionado (te referias a esto no?)
#region Using directives
using System;
using System.Collections.Generic;
using System.Text;
#endregion
namespace ConsoleApplication1
{
public class A
{
public A()
{
Console.WriteLine("1");
foo();
}
public virtual void foo()
{
Console.WriteLine("A");
}
}
public class B : A
{
public B() : base()
{
Console.WriteLine("2");
foo();
}
public new void foo()
{
Console.WriteLine("B");
}
}
class Program
{
static void Main(string[] args)
{
A a = new B();
Console.ReadLine();
}
}
}
Un saludo,
Vicente
P.D.: con override pasa lo que tu dices, sale BB. He usado el visual 2005 con .NET 2.0, pero supongo que en el 1.1 pasa lo mismo.
no se yo lo veo bastante claro :blink:
fijaos en los pasos que sigue durante el run-time:
Base b = new Derivada();
esto lo primero que hace es llamar al Constructor de la clase Derivada, que por defecto siempre llama, antes de todo al constructor de sus padres, veis un ejemplo en java, que cualquier constructor hace super(); intrinsicamente antes de nada.
con lo cual descomponemos en pasos:
Base b = new Derivada();
llama al constructor que el compilador traduce por:
public Derivada()
{
super();
bla bla bla
}
entonces que ocurre? primero se llama al constructor del padre, este al no ser "hijo de nadie" ejecuta la primera orden, que es mostrar por pantalla el mensaje:
Constructor de Base
y justo después llama a la función foo, que como es virtual, se ejecuta la que tenga asociado el "this." que en este caso, y siendo virtual al ejecutarse la funcion, y sólo repito por ser virtual se mira si el puntero "this." es de la clase actual o de algun hijo.
al ser de un hijo se llama a la funcion foo del hijo "Derivada" que muestra por pantalla el mensaje :)
Derivada.F()
por que está como override, como dice nuestro compañero la única forma de que no ocurra es usando new, pero entonces tampoco tendrias que declarar la funcion de arriba como virtual, sería una tontería.
una vez se ha acabado el super(); se llama a la propia constructora de Derivada y como podeis ver se ejecuta normalmente.
De C# tengo que investigar más, es un lenguaje que me interesa, pero donde esté C++ que se kiten las "mariconadas" de los formularios jeje, sólo que le cogí rabia al visual basic, pero weno C# es otro mundo ^^ aunque se ejecute sobre el mismo framework grrr
Hola,
Citar
¡De C# tengo que investigar más, es un lenguaje que me interesa, pero donde esté C++ que se kiten las "mariconadas" de los formularios jeje, sólo que le cogí rabia al visual basic, pero weno C# es otro mundo ^^ aunque se ejecute sobre el mismo framework grrr
C# es a Java lo que Visual Basic.NET a Visual Basic 6: una forma de conseguir que esos grupos de programadores se pasen a .NET. Son casi iguales, no hay muchas diferencias de cosas que puedas hacer en uno si y en otro no y en la versión 2005 unas cuantas han desaparecido. Un saludo!
Vicente
Gallifante para Vicente, que ha resuelto el enigma XD
Vicente, dices que C# y Java sonm iguales... y que VB.NET y VB6 son iguales?, o hablas de C# y VB.NET?
PD: VB.NET y VB6 tienen en común tanto como AnsiC y Java
Hola,
lo mismo no me he explicado muy bien: me refería a que C# y VB.NET son muy parecidos respecto a capacidades (trabajan sobre el mismo framework) y que las diferencias de lenguaje las van igualando poco a poco. Y además tienen objetivos muy parecidos: el de C# es atraer a los programadores Java a .NET y el de VB.NET atraer a los programadores VB6 a .NET. A ver si así queda más claro ;) Un saludo,
Vicente
Así queda mucho más claro y correcto :)
PD: De todas formas ... la sintaxis de VB sux