2012-12-15 54 views
2

我對非虛方法如何解決的理解(在C#中)是依賴於變量的類型(而不是實例的類型)。非虛方法解析 - 爲什麼會發生這種情況

看看下面的代碼。

class Program 
{ 
    static void Main(string[] args) 
    { 
     Sedan vehicle = new Sedan(); 
     vehicle.Drive(); 
     vehicle.Accelerate(); 
    } 
} 

abstract class VehicleBase 
{ 
    public void Drive() 
    { 
     ShiftIntoGear(); 
     Accelerate(); 
     Steer(); 
    } 

    protected abstract void ShiftIntoGear(); 
    protected abstract void Steer(); 

    public void Accelerate() 
    { 
     Console.WriteLine("VehicleBase.Accelerate"); 
    } 
} 

class Sedan : VehicleBase 
{ 
    protected override void ShiftIntoGear() 
    { 
     Console.WriteLine("Sedan.ShiftIntoGear"); 
    } 

    protected override void Steer() 
    { 
     Console.WriteLine("Sedan.Steer"); 
    } 

    public new void Accelerate() 
    { 
     Console.WriteLine("Sedan.Accelerate"); 
    } 
} 

的控制檯窗口顯示以下內容:

Sedan.ShiftIntoGear 
VehicleBase.Accelerate 
Sedan.Steer 
Sedan.Accelerate 

這沒有意義,我相信會引發許多人的一個循環。如果現在宣佈變量車輛爲類型VehicleBase你

Sedan.ShiftIntoGear 
VehicleBase.Accelerate 
Sedan.Steer 
VehicleBase.Accelerate 

這是我想在前面的情況下,期望以及因爲該方法是加快非虛。

在之前的輸出中(與可變車輛類型爲Sedan一樣,我希望Sedan.Accelerate被調用而不是VehicleBase.Accelerate。現在,根據您從哪裏調用它(從內部或外部)行爲正在改變

在我看來,重新引入方法的重載解析規則優先,但我很難相信這是正確的/預期的行爲

+0

不知道你在這裏找到困惑。你能解釋一下你發現的問題嗎? – Oded

+0

我以爲我做到了。這兩個產出已經提供,我的期望也是如此。我將編輯我的帖子,以更清晰地解釋我的期望 –

+0

如果您在'VehicleBase'中使'Accelerate'虛擬並在'Sedan'中覆蓋它,您將獲得您期望的行爲。 – juharr

回答

1

非虛擬方法根據編譯時表達式的類型進行解析,調用目標在編譯時固定。編譯器在調用的源代碼上下文中的編譯時可用的符號信息中查找方法名稱,併發出調用指令以調用它能夠找到的非虛方法。不管實例的實際類型是什麼,調用總是從靜態類型信息到編譯時計算出來的非虛擬方法。

這是爲什麼調用AccelerateSedan.Accelerate在主要的方面,而是去VehicleBase.Accelerate中的VehicleBase.Drive背景:

在你Main功能的身體,你已經聲明類型的變量Sedan,並且使用該變量進行方法調用。編譯器在用於進行調用的變量類型中查找名爲「Accelerate」的方法,輸入Sedan,並找到Sedan.Accelerate

在方法VehicleBase.Drive內部,編譯時間類型的「自我」是VehicleBase。這是編譯器在此源代碼上下文中唯一可以看到的類型,因此即使運行時對象實例的類型實際上是Sedan,對'VehicleBase.Drive'中的加速的調用始終將轉至VehicleBase.Accelerate

Sedan類型聲明的方法體,編譯器會通過查看Sedan類型的第一個方法,然後看VehicleBase類型如果沒有匹配是在Sedan找到解決非虛方法調用。

如果您希望調用目標根據實際對象實例類型進行更改,則必須使用虛擬方法。使用虛擬方法也會得到更一致的執行結果,因爲調用將始終轉到由對象實例在運行時確定的最具體的實現。

根據編譯時類型選擇非虛擬方法調用目標,但不知道運行時間。在運行時根據實例類型選擇虛擬方法調用目標。

+0

感謝您的解釋並澄清了爲什麼VehicleBase.Accelerate從Drive方法被調用的原因。我得到這個的關鍵是,*編譯時*類型的self/this是VehicleBase而不是變量的類型。所以即使我現在理解了解釋,我也不明白爲什麼在編譯時編譯器無法看到類型是Sedan(在Drive方法中)。 –

+0

德爾福的行爲是否類似?我經常使用靜態方法派發我的優勢,從來沒有絆倒這可能從來沒有重新引入子類中的方法,同時從基類中調用它們。關於Java(你知道嗎?) –

+0

C#和Delphi行爲之間的唯一區別是Delphi在C#之前的6年做了它。 ;> – dthorpe

4

所有這一切都非常有意義 - 當您聲明車輛爲Sedan時,Accelerate的兩個呼叫的解析方式不同:

  • 當呼叫到AccelerateDrive方法制備,它具有不知道有在Sedan一個new方法,所以它使該呼叫到基
  • 的相應方法當呼叫到AccelerateMain方法構成,編譯器知道您正在調用new方法,因爲它知道vehicle變量的確切類型爲Sedan

在另一方面,當呼叫到AccelerateMain方法制造,但變量聲明爲VehicleBase,編譯器不能假定類型是Sedan,所以解決了Accelerate該方法的基類再次。

+0

由於Accelerate是一種非虛方法,並且該變量已被鍵入爲Sedan,編譯器*確實知道該方法已被重新引入(新的),並且應該從兩個地方調用重新引入的Accelerate方法。 –

+0

@ShivKumar只有當你聲明變量爲'Sedan'時,編譯器才知道它,即代碼在你的文章中寫入的方式。然而,當你聲明變量爲'VehicleBase vehicle = new Sedan()'時,編譯器知道'vehicle'的類型是'VehicleBase',所以它不能調用新添加的方法。 – dasblinkenlight

+0

將變量鍵入爲轎車的情況是(在我看來)這種行爲是意外的。因此,如果它在編譯時知道該類型是一輛轎車,並且所討論的方法是非虛擬的,爲什麼它的行爲方式如此?也許如果你再次閱讀我的文章,它會幫助:) –

相關問題