Search code examples
c#methodsvirtual

Virtual Functions C#


I understand what a virtual function is. But what I don't get is how do they work internally?

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());
}

Output for the above gives:

Eat like a Human
Eat undefined

In the above code _animal is of type Animal which references a Human object or Dog object. What does this mean? I understand in the memory _animal contains an address which will point to Human or Dog object. How does it decide which function to invoke. In the first case I override and hence child's implementation is called, but in second case I use new and hence the parent's implementation is called. Can you please explain me what happens under the hood?

Thanks in advance Nick


Solution

  • It works like this. Imagine the compiler rewrote your classes into this:

    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 } ;
        }
    }
    

    Now consider these calls:

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

    The compiler reasons as follows: If the type of the receiver is Animal then the call to Eat must be to animal.VTable.AnimalEat. If the type of the receiver is Dog then the call must be to DogEat. So the compiler writes these as:

    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
    

    That is exactly how it works. The compiler generates vtables for you behind the scenes, and decides at compile time whether to call through the vtable or not based on the rules of overload resolution.

    The vtables are set up by the memory allocator when the object is created. (My sketch is a lie in this regard, since the vtable is set up before the ctor is called, not after.)

    The "this" of a virtual method is actually secretly passed as an invisible formal parameter to the method.

    Make sense?