2012-08-14 17 views
1
class Program 
{ 
    static void Main(string[] args) 
    { 
     Parent p = new Child(); 
     p.Print(); 
    } 
} 
class Parent 
{ 
    public virtual void Print() 
    { 
     Console.WriteLine("This is parent."); 
    } 
} 
class Kid:Parent 
{ 
    public override void Print() 
    { 
     Console.WriteLine("This is Kid."); 
    } 
} 
class Child : Kid 
{ 
    public new virtual void Print() 
    { 
     Console.WriteLine("This is Child."); 
    } 
} 

爲什麼輸出「這是孩子」,而不是「這是孩子」?爲什麼標識符重用不會生效,如果一個成員是從一個上調對象調用?

Print() in class Child is virtual。
我試圖理解發生了什麼。

+0

你的問題(他們做什麼)不匹配標題(它是如何工作的)。但都是(很多時候)重複。你不清楚你不明白什麼。 – 2012-08-14 10:14:06

+0

@ user1559463我編輯了你的問題標題。現在適合你的話題。 – 2012-08-14 10:26:57

回答

1

您打電話覆蓋Print方法Parent

new如果您將該變量鍵入Kid,則運算符將工作。在這種情況下,new不是重寫,而是標識符重用

+0

Kid p = new Child();仍然給我「這是孩子」。 – user1559463 2012-08-14 10:20:38

+0

@ user1559463仔細檢查我的答案。除非你把變量輸入爲'Child',否則你不會得到'This is Child'。難道你不明白'新'操作符不等同於重寫方法嗎? – 2012-08-14 10:22:51

1

p的類型爲Parent。因此,編譯器在Parent類中查找Print方法。由於此方法是虛擬的,因此它會在Kid類中找到重寫的方法。因爲你沒有覆蓋,但替換Print方法在Child,編譯器不使用此方法。

+0

編譯器看到的類型是「Parent」,而Pointed類型是「Child」?然後他找到虛擬方法,並且看到指針指向一個不同的類型,所以他正在尋找一種可以忽略當前方法的方法?如果他沒有找到,他會在「父母」中使用該方法? – user1559463 2012-08-14 10:20:16

+0

@ user1559463編譯器認爲變量的類型是「Parent」,因此會嘗試調用「Parent」類型的「Print」方法。如果你的方法不是虛擬的,你的程序會顯示「This is parent」。但是,由於它是虛擬的,編譯器會查找重寫方法並調用最具體的重寫 - 它在'Kid'中。這是因爲您的「覆蓋鏈」停止在「Kid」中,因爲您使用'new'關鍵字作爲'Child.Print()'方法。 – fero 2012-08-14 11:06:53

+0

@ user1559463如果要顯示「This is Child」,則必須將'new virtual'替換爲另一個'override',以告訴編譯器該方法是從'Kid'或'Parent'繼承的方法的更具體的版本,而不是一個全新的。 – fero 2012-08-14 11:14:47

0

那麼因爲您使用了新關鍵字,這意味着您隱藏了繼承的方法並提供了新的實現。這通常被稱爲隱藏父母成員。

0

從MSDN(http://msdn.microsoft.com/en-us/library/6fawty39(v=vs.100).aspx):

「當DoWork的調用上而衍生的情況下,C#編譯器將首先嚐試讓與DoWork的版本兼容的電話原本在衍生聲明覆蓋方法都沒有。如果C#編譯器無法將方法調用與Derived上的原始方法匹配,它將嘗試將調用與具有相同方法的重寫方法進行匹配名稱和兼容參數「。

看起來像您在Kid中使用'new'但將p聲明爲Parent意味着p無法在Child中看到Print,因爲它不是繼承層次結構的一部分。

static void Main(string[] args) 
{ 
    Parent p = new Child(); 
    p.Print(); 

    Child c = (Child) p; 
    c.Print(); 
} 

......明顯改變了事情。

0

以下內容都將過度簡化並將一種可能的實現視爲事實,但應該有足夠的工作心智模型。

當調用代碼「知道」一類就知道下面的事情:

  1. 字段可以在一個特定的從對象的位置偏移進行訪問。因此,例如,如果對象位於地址120處,並且具有兩個整數字段,則它可能能夠在地址124處訪問它們中的一個。如果同一類型的另一對象在地址140處,則等效字段將在144處。

  2. 非虛擬方法(和屬性可以被認爲是一個或兩個方法上的語法糖)是在一個特定地址的函數,它引用了您要調用的對象(方法中爲this),另一個該功能的參數。

  3. 虛擬方法就像上面那樣,但是可以通過查看與該類關聯的表中的特定偏移量來找到它們的地址,其地址也將是與該類地址的特定偏移量。

在此,Kid有方法表是那的Parent一個超集(它可以增加更多的方法),並且具有相同功能的地址,這些方法也沒有過度騎(在它上面調用Equals使用與在Parent上調用Equals相同的功能),但對於它覆蓋的地址使用不同的地址(在這種情況下爲Print())。

因此,如果你有一個Kid那麼你是否有它通過Parent引用或參考Kid,呼籲Print()會尋找到同一個表,查找了Print()方法的位置,並調用它。

Child的情況下,在Print方法中使用new。這告訴編譯器我們特別需要一個不同的表。因此,如果我們通過Child引用調用Print(),它會查找Child特定表,並調用它找到的方法。然而,如果我們通過KidParent參考來調用它,那麼我們甚至不知道我們可以使用的具有Child的特定表格,並且我們在表格中查找我們知道KidParent分別具有的函數,並且調用找到的函數(在Kid中定義)。

通常,要避免使用new。它的使用是在兩個地方:

一個是向後兼容。例如,如果Child擁有Name屬性,並且後來對Parent的代碼進行了更改,以致它也具有Name屬性,我們發生衝突。由於ChildName不是一個覆蓋,它被視爲如果它有new,但給我們一個警告,因爲這是唯一的方法代碼使用舊的方式和知道Parent上的新Name可以co -存在。如果我們回來重新編譯Child,我們可能要麼重構,所以它沒有自己的Name(如果Parent做我們想要的),重構,所以它是重寫,重構爲完全不同的東西,或添加new表示這是我們想要的東西,儘管它不理想。

另一種是當new允許基類的方法允許的相同行爲的更具體形式,但在邏輯上兼容(所以用戶不會感到驚訝)。後者應該放在半高級技術框中,不要輕易做。它也應該這樣評論,因爲大多數時候看到new意味着你處理的東西至多是妥協,應該可以改進。

(題外話:我是誰後,看到有ChildKid思想小報的唯一的人?)

相關問題