Виртуальные функции в C#

#c# #inheritance #virtual-functions

#c# #наследование #виртуальные функции

Вопрос:

 public class Base1
{
    public virtual void f()
    {
        Console.WriteLine("Base1.f()");
    }
}

public class Derived1 : Base1
{
    // Hides Base1.f() because 'override' was not specified
    public new virtual void f()
    {
        Console.WriteLine("Derived1.f()");
    }
}

public class Derived2 : Derived1
{
    // Overrides Derived1.f()
    public override void f()
    {
        Console.WriteLine("Derived2.f()");

        // Call base method
        base.f();
    }
}

class Program
{
    static void Main(string[] args)
        Base1 ob1 = new Derived1();
        ob1.f();

        Base1 ob2 = new Derived2();
        ob2.f();

        Derived1 ob3 = new Derived2();
        ob3.f();

        Derived2 ob4 = new Derived2();
        ob4.f();
    }
}


// Calls Derived2.f() because Derived2 overrides Derived1().f()
        Derived1 ob3 = new Derived2();
        ob3.f();
  

ожидалось, что

 Base1 ob2 = new Derived2();
ob2.f();
  
  1. Будет вызвана функция derived2, но функция базового класса была
    вызывается, в чем причина этого.
  2. Использует ли .net vtables

Комментарии:

1. Пожалуйста, отредактируйте свой вопрос, чтобы отформатировать его соответствующим образом — я бы сделал это сам, но я не могу сказать, что вы действительно пытаетесь сделать в конце.

2. @Jon Пожалуйста, придерживайтесь свадебного Джона, который вы наверняка сможете приобрести в кемпинге!

3. @David: Некоторое время назад наблюдал за свадьбой. Теперь возвращаемся в палатку.

4. @Джон, Насколько я могу судить, это все еще продолжается. На самом деле я путешествовал через Атлантику в США, чтобы избежать этого, но это на каждом телеканале здесь! Американцы просто в восторге от британской королевской свадьбы, вы не поверите.

5. @David: Это то, о чем я серьезно задумывался. Я могу понять, почему британцы могут быть заинтересованы в этом, и я могу понять, почему британско-американца это может волновать. Но я просто не могу понять, почему обычного старого американца волнует британская королевская свадьба. Я, конечно, этого не делаю… Минуту или две я почти волновался, что это может сделать меня плохим человеком, но я справился с этим.

Ответ №1:

Слот метода, используемый статическим анализом во время компиляции, зависит от типа переменной (или выражения), а не от фактического объекта. Переменная ob2 вводится как Base1 , поэтому используется Base1 слот метода. И затем выбирается правильное переопределение на основе введенного (по существу, vtable в этом слоте). Итак, используется базовая функция.

Чтобы использовать функцию derived2, переменная (или выражение) должна быть введена как Derived1 или подкласс.

Ответ №2:

В принципе, если тип переменной, которую вы используете для вызова во время компиляции, f() равен Base1 , то будет вызван базовый метод — потому что на самом деле его ничто не переопределяет.

Если типом времени компиляции является Derived1 или Derived2 , он вызовет соответствующий метод либо в Derived1 , либо Derived2 на основе типа объекта во время выполнения… потому что в этот момент компилятор будет просто выполнять виртуальный вызов Derived1.f() , и переопределение произойдет во время выполнения.

И да, .NET использует vtables.

Ответ №3:

Проблема здесь в том, что вы слишком много всего смешиваете.

В принципе, вот что вы сделали:

  1. Вы определяете виртуальный метод f в базовом классе
  2. Вы производите от этого базового класса и создаете новый виртуальный f метод
  3. Вы происходите из второго класса и переопределяете f , это переопределяет функцию из второго класса, а не из базового класса.

Итак, когда вы говорите:

 Base1 b = new Derived2();
b.f();
  

тогда вы всегда (в данном случае) будете вызывать базовую реализацию f , поскольку переопределенный f в Derived2 это другой f метод. Название то же, но это все еще другой метод.

Причина этого в том, что компилятор увидит, что вызываемая f вами функция исходит из Base1 класса, и поэтому он вызовет это.

Поскольку ни один класс не переопределяет Base1.f , это тот, который вы вызываете.


В ответ на вопрос в комментарии, строго говоря, класс будет иметь два виртуальных метода, оба с именами f .

Однако одна из них затеняется новой, введенной в Derived1.

Вы можете внутри класса выбрать, какие вызывать:

 public void MethodInDerived1()
{
    f();                            // calls Derived1.f()
    base.f();                       // calls Base1.f()
}
  

Однако извне вам нужно «выбирать» путем приведения.

Другими словами:

 Derived1 d = new Derived1();
d.f();                              // calls Derived1.f()
((Base1)d).f();                     // calls Base1.f()
  

Вы также можете наблюдать за методами с помощью отражения. Если вы выполните следующий код в LINQPad, вы увидите, что существует два метода, оба названных f :

 void Main()
{
    typeof(Derived1).GetMethods().Dump();
}

public class Base1
{
    public virtual void f()
    {
        Debug.WriteLine("Base1.f");
    }
}

public class Derived1 : Base1
{
    public virtual new void f()
    {
        Debug.WriteLine("Derived1.f");
    }
}

public class Derived2 : Derived1
{
    public override void f()
    {
        Debug.WriteLine("Derived2.f");
        base.f();
    }
}
  

Выходные данные этого скрипта усечены (справа есть дополнительная информация):

Вывод из скрипта LINQPad

Комментарии:

1. Ну, строго говоря, их будет 2, но лексически вы можете ссылаться только на одну из них, новую, поскольку у них одинаковое имя. Однако вы можете найти их с помощью отражения. Вы также можете вызвать base.f , чтобы явно выбрать базовый метод.

2. в vtables как хранится эта информация, потому что у нее есть 2 виртуальные функции