虚函数C#

我理解虚函数是什么。 但我没有得到的是他们如何在内部工作?

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包含一个指向Human或Dog对象的地址。 它如何决定调用哪个函数。 在第一种情况下,我覆盖,因此调用了子的实现,但在第二种情况下,我使用new,因此调用了父实现。 你能解释一下引擎盖下发生了什么吗?

在此先感谢尼克

它的工作原理如下。 想象一下,编译器将您的类重写为:

 class VTable { public VTable(Func eat) { this.AnimalEat = eat; } public readonly Func 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,那么对Eat的调用必须是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

在创建对象时,内存分配器会设置vtable。 (我的草图在这方面是谎言,因为vtable是调用ctor 之前设置的,而不是之后的。)

虚拟方法的“this”实际上是作为方法的不可见forms参数秘密传递的。

合理?

我理解在内存中_animal包含一个指向Human或Dog对象的地址。 它如何决定调用哪个函数。

与数据一样,代码也有一个地址。

因此,解决此问题的典型方法是使HumanDog对象包含其方法代码的地址。 有时使用vtable调用它。 在像C或C ++这样的语言中,这个概念也直接暴露为所谓的函数指针 。

现在,您已经提到了C#,它具有相当高级的类型系统,其中对象类型在运行时也是可辨别的……因此,实现细节可能在某种程度上与传统方法不同。 但是,关于你的问题,函数指针/ v-table概念是一种方法,如果.NET偏离了这一点,它会让我感到惊讶。

在C#中,派生类必须为从基类inheritance的任何重写方法提供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 ,派生类Dog没有Eat()重写方法。 因此,调用基类方法本身。 此外,这种检查方法也已完成,因为在基类中, Eat()被称为虚拟,并且在运行时决定调用机制。 综上所述, 虚拟调用机制是一种运行时机制。