Кастинг в базовый класс и вызов его виртуального метода приводит к исключению stackoverflow

Рассмотрим этот код:

public class Person
{
    public virtual void UpdateLastLogin()
    {
        // business of last login here
    }
}

public class Student : Person
{
    public override void UpdateLastLogin()
    {
        // logging something specific to person
        ((Person)this).UpdatelastLogin();
    }
}

Почему выше код генерирует исключение STACKOVERFLOW?

Но это не так:

public class Person
{
    public virtual void UpdateLastLogin()
    {
        // business of last login here
    }
}

public class Student : Person
{
    public override void UpdateLastLogin()
    {
        // logging something specific to person
        base.UpdatelastLogin();
    }
}

Ответ 1

Это потому, что ключевое слово base отключает вызов виртуального метода, но кастинг не работает.

Позвольте сломать его:

С virtual и override s, С# имеет алгоритм для поиска правильного метода для вызова. Этот алгоритм называется вызовом виртуального метода.

Когда компилятор С# должен вызывать метод с модификатором override, он пытается найти самый перегруженный метод. То есть, он опускается по иерархии наследования типа времени выполнения, чтобы найти наиболее сложный метод. В этом случае, когда вы кладете Student в его базовый класс Person, в время выполнения, то, что у вас действительно есть, является производным классом от Person. Таким образом, компилятор С# пытается найти самый переопределенный метод, то есть метод, который существует в классе Student, и вызывает этот метод. Но этот метод снова пытается передать Student в Person, и снова цепочка продолжается и продолжается. Таким образом, вы видите StackOverflowException.

Однако, когда вы используете base для вызова переопределенного базового метода, компилятор рассматривает этот метод (Person.UpdateLastLogin) как не виртуальный метод и не будет применять вызов виртуального метода и разрешение на него.

Спецификация С# имеет довольно разумное объяснение в override и virtual. Я рекомендую вам прочитать разделы 17.5.3 Виртуальные методы и 17.5.4 Переопределить методы. Конкретно цитируется из спецификации:

Базовый доступ отключает механизм виртуального вызова и просто обрабатывает базовый метод как не виртуальный метод.

Ответ 2

Если вы хотите вызвать реализацию базового класса, используйте ключевое слово base:

base.UpdatelastLogin();

Передача ссылки на базовый класс действительно ничего не делает. Это красота объектно-ориентированного программирования! Вам не нужно знать, что это за тип - вы просто вызываете метод на объекте, и он выполняет реализацию этого объекта, как вести себя.

Когда вы создаете экземпляр дочернего класса, запись в этом объекте vtable для UpdateLastLogin() всегда будет указывать на дочерний класс, no введите тип ссылки.