Виртуальные функции C # - PullRequest
3 голосов
/ 15 марта 2011

Я понимаю, что такое виртуальная функция. Но чего я не понимаю, так это как они работают внутри?

class Animal
{
    virtual string Eat()
    {
        return @"Eat undefined";
    }
}

class Human : Animal
{
    override string Eat()
    {
         return @"Eat like a Human";
    }
}


class Dog : Animal
{
    new string Eat()
    {
         return @"Eat like a Dog";
    }
}

static void Main()
{
    Animal _animal = new Human();
    Console.WriteLine(_animal.Eat());
    _animal = new Dog();
    Console.WriteLine(_animal.Eat());
}

Выход для вышеупомянутого дает:

Eat like a Human
Eat undefined

В приведенном выше коде _animal имеет тип Animal, который ссылается на объект Human или объект Dog. Что это значит? Я понимаю, что в памяти _animal содержится адрес, который будет указывать на человека или собаку. Как он решает, какую функцию вызывать. В первом случае я переопределяю, и, следовательно, вызывается реализация child, но во втором случае я использую new, и, следовательно, вызывается реализация parent. Не могли бы вы объяснить, что происходит под капотом?

Заранее спасибо Ник

Ответы [ 3 ]

17 голосов
/ 15 марта 2011

Это работает так.Представьте, что компилятор переписал ваши классы так:

class VTable
{
    public VTable(Func<Animal, string> eat)
    {
        this.AnimalEat = eat;
    }
    public readonly Func<Animal, string> AnimalEat;
}

class Animal
{
    private static AnimalVTable = new VTable(Animal.AnimalEat);
    private static string AnimalEat(Animal _this)
    { 
        return "undefined"; 
    }
    public VTable VTable;
    public static Animal CreateAnimal() 
    { 
        return new Animal() 
            { VTable = AnimalVTable }; 
    }
}

class Human : Animal
{
    private static HumanVTable = new VTable(Human.HumanEat); 
    private static string HumanEat(Animal _this)
    {
        return "human"; 
    }
    public static Human CreateHuman()
    {
        return new Human() 
            { VTable = HumanVTable };
    }
}

class Dog : Animal
{
    public static string DogEat(Dog _this) { return "dog"; }
    public static Dog CreateDog()
    {
        return new Dog() 
            { VTable = AnimalVTable } ;
    }
}

Теперь рассмотрим эти вызовы:

Animal animal;
Dog dog;
animal = new Human();
animal.Eat();
animal = new Animal();
animal.Eat();
dog = new Dog();
dog.Eat();
animal = dog;
animal.Eat();

Компилятор рассуждает следующим образом: Если тип получателя - Animal, тогда вызовКушать должно быть на animal.VTable.AnimalEat.Если тип получателя - Dog, то вызов должен быть DogEat.Таким образом, компилятор записывает их так:

Animal animal;
Dog dog;
animal = Human.CreateHuman(); // sets the VTable field to HumanVTable
animal.VTable.AnimalEat(animal); // calls HumanVTable.AnimalEat
animal = Animal.CreateAnimal(); // sets the VTable field to AnimalVTable
animal.VTable.AnimalEat(animal); // calls AnimalVTable.AnimalEat
dog = Dog.CreateDog(); // sets the VTable field to AnimalVTable
Dog.DogEat(dog); // calls DogEat, obviously
animal = dog;
animal.VTable.AnimalEat(animal); // calls AnimalVTable.AnimalEat

То есть в точности как это работает.Компилятор генерирует vtables для вас за кулисами, и решает во время компиляции, вызывать ли через vtable или нет, основываясь на правилах разрешения перегрузки .

vtables устанавливаются распределителем памяти при создании объекта.(Мой набросок - ложь в этом отношении, поскольку vtable настроен до , вызывается ctor, а не после.)

«this» виртуального метода фактически передается тайнокак невидимый формальный параметр метода.

Имеет смысл?

0 голосов
/ 15 марта 2011

В C # производные классы должны предоставлять модификатор override для любого переопределенного метода, унаследованного от базового класса.

Animal _animal = new Human();

Это не просто объект Human, который был построен. Это два подобъекта. Один из них - Animal подобъект, а другой - Human подобъект.

Console.WriteLine(_animal.Eat());

Когда выполняется вызов _animal.Eat();, среда выполнения проверяет, переопределен ли метод базового класса (т. Е. Eat()) в производном классе. Поскольку он переопределяется, вызывается соответствующий производный метод класса . Отсюда вывод -

Eat like a Human

Но, в случае -

_animal = new Dog();
Console.WriteLine(_animal.Eat());

В Dog нет переопределенного метода Eat() в производном классе Dog. Итак, сам метод базового класса вызывается. Также этот метод проверки выполняется, потому что в базовом классе Eat() упоминается как виртуальный, а механизм вызова определяется во время выполнения . Подводя итог, можно сказать, что механизм виртуальных вызовов является механизмом выполнения.

0 голосов
/ 15 марта 2011
Я понимаю, что в памяти _animal содержится адрес, который будет указывать на человека или собаку.Как он решает, какую функцию вызывать.

Как и данные, у кода также есть адрес.

Поэтому типичный подход к этой проблеме состоит в том, чтобы объекты Human или Dog содержали адрес кода своих методов.Иногда это вызывается с помощью vtable .В таких языках, как C или C ++, эта концепция также прямо представлена ​​как то, что называется указатель на функцию .

Теперь вы упомянули C #, который имеет довольно высокоуровневую систему типов,в которых типы объектов также различимы во время выполнения .... Поэтому детали реализации могут в некотором смысле отличаться от традиционного подхода.Но, что касается вашего вопроса, концепция указателя функции / v-таблицы - один из способов сделать это, и меня удивило бы, если .NET слишком отклонился от этого.

...