поведение виртуальной функции, вызываемой в конструкторе или деструкторе - PullRequest
3 голосов
/ 09 ноября 2011

Я прочитал некоторые материалы о различном поведении виртуальной функции, вызываемой в конструкторе или деструкторе между c ++ и c #. И я тестирую приведенный ниже код, чтобы подтвердить, что c # может вызывать виртуальную виртуальную функцию, поскольку ее объект существует до конструктора. Однако я обнаружил, что результат такой же, как аналогичный код в 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 ]

2 голосов
/ 09 ноября 2011

В вашем коде 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 () - это совершенно новый виртуальный метод. У него то же имя, что и у функции 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 голос
/ 09 ноября 2011

Проблема в том, что даже если функции имеют одинаковое имя "fun()", каждая из них является членом класса, в котором она объявлена.Поэтому, когда вы вызываете fun() из базового класса ctor, вы звоните Base.func(), когда вы вызываете fun() из производного класса, это похоже на Derived.fun();Если вы хотите вывести «22», вам нужно переопределить fun() в Derived классе.

     public class Derived : Base
    {
        public Derived() { fun(); }
        public override void fun() { Console.WriteLine(2); }
    }
0 голосов
/ 09 ноября 2011

Я предполагаю, что вы создаете экземпляр производного в каждом случае.

Часть вашей проблемы заключается в том, что во время конструктора не рекомендуется вызывать виртуальные методы.

  1. Базовый конструктор называется. Часть первая это конструктор инициализирует VTable fun указывает на base::fun, поэтому при вызове fun()
  2. Тогда производный конструктор инициализирует таблицу для переопределения fun в указать на derived::fun. В следующий раз, когда вы позвоните fun(), он напечатает 2

MSDN настоятельно рекомендует против этого: Не вызывать переопределенные методы в конструкторах

Эффективный C ++ делает и еще более сильное предложение: Никогда не вызывайте виртуальные функции во время строительства или разрушения

Это похожий вопрос

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...