构造函数或析构函数中调用的虚函数的行为

时间:2011-11-09 00:01:59

标签: c# c++ constructor virtual

我已经阅读了一些关于在c ++和c#之间构造函数或析构函数中调用的虚函数的不同行为的资料。我测试下面的代码,以确认c#可以调用虚拟dirived虚函数,因为它的对象存在于构造函数之前。但是我发现结果与c ++中的类似代码相同。任何人都可以告诉我为什么c#无法显示“22”但只显示“12”的原因。

C#代码

public class Base
{
    public Base() { fun(); }
    public virtual void fun() { Console.WriteLine(1); }
}
public class Derived : Base
{
    public Derived() { fun(); }
    public virtual void fun() { Console.WriteLine(2); }
}

C ++代码

class Base
{
public:
    Base(){fun();}
    virtual void fun(){cout<<1;}
};
class Derived : Base
{
public:
    Derived(){fun();}
    virtual void fun(){cout<<2;}
};

c ++和C#的输出结果都是“12”。

3 个答案:

答案 0 :(得分:2)

您的C#代码出错。

要覆盖C#功能,您必须指定override关键字。 如果再次编写virtual,则会影响基本功能,但如果没有new关键字,则会收到警告。

如果两者都被声明为虚拟,则您有两个具有相同名称的函数!

我们举个例子......

public class Base
{
    public Base() { fun(); }
    public virtual void fun() { Console.Write(1); }
}

public class Derived : Base
{
    public Derived() { fun(); }
    public override void fun() { Console.Write(2); }
}

public class DerivedWithError : Base
{
    public DerivedWithError() { fun(); }
    public new virtual void fun() { Console.Write(3); }
}

...

// This will print "22".
Base normal = new Derived();
Console.WriteLine();

// This will print "13" !!!
Base withError = new DerivedWithError ();
Console.WriteLine();

// Let's call the methods and see what happens!

// This will print "2"
normal.fun();
Console.WriteLine();

// This will print "1" !!!
withError.fun();
Console.WriteLine(); 

阴影意味着“在不使用多态性的情况下添加具有相同名称的新方法”。 如果没有override关键字,则会禁用多态性。

所以一切都应该干净,易于理解。

DerivedWithError.fun()是一个全新的虚方法。它与基类中的函数名称相同,但它们不相关!

从虚拟表(或虚拟方法表,如果您更喜欢其他名称)的角度来讲,通过遮蔽基本函数和派生函数占用虚拟表中的两个不同条目,如果它们具有相同的名称。 如果改为使用override,则强制派生类中的func方法覆盖Base.func占用的虚拟表项,这就是多态性。

危险但.NET允许,请务必小心语法!

您可以在C#中的构造函数中调用虚函数,但通常在派生类中如果在基础构造函数中调用方法,则应该注意如何在派生类中使用字段。 现在,为了保持清洁并避免混淆和风险,你应该避免在构造函数中调用虚方法,但实际上它在几次非常有用。

例如所谓的“工厂方法”,它不能访问任何变量。

public abstract class MyClass
{
    public IList<string> MyList { get; private set; }

    public MyClass()
    {
         this.MyList = this.CreateMyList();
    }

    protected abstract IList<string> CreateMyList();
}

public class MyDerived : MyClass
{
    protected override IList<string> CreateMyList()
    {
        return new ObservableCollection<string>();
    }
}

这是完全合法的,它有效!

答案 1 :(得分:1)

问题在于,即使函数具有相同的名称“fun()”,每个函数都是声明它的类的成员。因此,当您从基类ctor调用fun()时,您正在调用Base.func(),当您从派生类调用fun()时,它就像Derived.fun();如果您想输出“22”,则需要覆盖fun()类中的Derived

     public class Derived : Base
    {
        public Derived() { fun(); }
        public override void fun() { Console.WriteLine(2); }
    }

答案 2 :(得分:0)

我假设您在每种情况下都在创建派生实例。

您的部分问题是建议不要在构造函数中调用虚方法。

  1. 调用基础构造函数。这是第一件事 构造函数做的是初始化VTable fun点 因此,当您致电base::fun
  2. 时,会打印1 fun()
  3. 然后 派生构造函数初始化vtable以覆盖fun 指向derived::fun。下次拨打fun()时,它会打印2
  4. MSDN强烈建议不要这样做:Do not call overridable methods in constructors

    有效的C ++提出了更强有力的建议:Never Call Virtual Functions during Construction or Destruction

    这是一个类似的question