2011-03-15 117 views
3

我明白虛擬函數是什麼。但我沒有得到的是他們如何在內部工作?虛擬函數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包含一個地址,它將指向人類或狗對象。它如何決定調用哪個函數。在第一種情況下,我重寫,因此調用了孩子的實現,但在第二種情況下,我使用新的,因此調用了父代的實現。你能解釋一下發生了什麼?

在此先感謝 尼克

+1

你知道Eric Lippert正在撰寫關於這個主題的博客系列嗎?請參閱http://blogs.msdn.com/b/ericlippert/archive/2011/03/17/implementing-the-virtual-method-pattern-in-c-part-one.aspx – Learner 2011-03-23 08:56:15

+0

謝謝學習者。我正在關注它:) – Nishant 2011-04-05 03:03:14

回答

17

它是這樣工作的。試想一下,編譯器改寫了你的類變成這樣:

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.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由內存分配器設置。 (我的素描是這方面的一個謊言,因爲虛函數表設置前的構造函數被調用,而不是之後。)

的「本」的虛方法實際上是祕密爲一種無形的形式參數來傳遞方法。

有意義嗎?

+7

@Eric:嗨,Eric,我注意到你已經從高級開發人員更新爲主要開發人員。我在3天內沒有參加,所以我假設你在週末更新了:O只是想對你的新職位/晉升表示祝賀,如果我可以這樣說的話,你應該得到它。這是否意味着任何事情和C#都會通過你?我希望如此,因爲你是IMO上最好的開發人員之一。 – 2011-03-15 17:08:44

+0

@Joan:感謝客氣的話。當然,我對C#上的最後一句話還遠遠不夠。我是這支球隊中較爲年輕的一員,我只有15年的設計和實現編程語言。我和Anders Hejlsberg,Neal Gafter和Peter Golde一起工作,僅舉幾例。那些傢伙遠比我高。 – 2011-03-15 18:56:05

+3

@Eric:如果你仍然認爲自己是初中生,這很有趣,我不知道你下面的人會是什麼樣的人:O我從哪裏工作,即使不是軟件公司,校長也很高。但我猜想像微軟這樣的軟件巨頭,其間的成績要高得多。無論哪種方式,雖然這將是一個非常寶貴的經驗,能夠與你們交談。期待看到你在行列和幸福中上升。 – 2011-03-15 19:33:25

0
我的記憶_animal理解包含將指向人或犬對象的地址。它如何決定調用哪個函數。

和數據一樣,代碼也有一個地址。

因此,此問題的典型方法是使用HumanDog對象來包含其方法的代碼地址。有時這被稱爲使用vtable。在像C或C++這樣的語言中,這個概念也被直接公開爲所謂的function pointer

現在,您已經提到了C#,它有一個非常高級的類型系統,其中類型的對象在運行時也是可辨別的....因此實現細節可能與傳統方法有所不同。但是,至於你的問題,函數指針/ v-table概念是實現它的一種方式,如果.NET偏離了這一點,它會讓我感到驚訝。

+0

謝謝。你能幫我解決內存分配問題嗎?你說代碼也有一個地址。在這種情況下如何分配內存?在Dog類中,它僅僅隱藏了基類方法,而在Human類中它卻覆蓋了它。 – Nishant 2011-03-15 04:45:01

+0

@nick - 當您的EXE或DLL被加載時,操作系統會爲其代碼管理內存。 (在C#的情況下,也包含JIT,因此.NET運行時也會管理涉及的內存。) – asveikau 2011-03-15 05:03:52

+0

爲了解決最後一句中的問題:您可能會對接口的工作方式略感意外。抖動通常爲類層次上的虛擬方法調用生成「經典」vtables。但是,爲接口方法上的虛擬調用生成的代碼稍微複雜一些。它與C++編譯器生成的典型間接vtable不同。 – 2011-03-15 06:59:57

0

在C#中,派生類必須爲從基類繼承的任何重寫方法提供重寫修飾符。

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()被稱爲虛擬並且調用機制被確定爲運行時間。綜上所述,虛擬調用機制是一種運行時機制。