前言
這篇文章出自我個人對C#虛函數特性的研究和理解,未參考、查閱第三方資料,因此很可能存在謬誤之處。我在這裡只是為了將我的理解呈現給大家,也希望大家在看到我犯了錯誤後告訴我。
用詞約定
“方法的簽名”包括返回類型、方法名、參數列表,這三者共同標識了一個方法。
“聲明方法”,即指出該方法的簽名。“定義方法”,則是指定調用方法時執行的代碼。
“同名方法”是指方法的簽名相同的兩個方法。
“重寫”一個方法,意味著子類想繼承父類對方法的聲明,卻想重新定義該方法。
單獨使用“使用”一詞時,包括“顯式”或“隱式”兩種使用方式:前者是指在代碼中指明,後者是根據語句的上下文推斷。
某個類的方法,包括了在該類中定義的方法,以及由繼承得到的直接父類的方法。注意這條規則的遞歸性質。
理論部分
在父類與子類裡,除了類之間的繼承鏈,還存在方法之間的繼承鏈。
C#裡,在一個類中聲明一個方法時,有四個和方法的繼承性有關的關鍵字:new、virtual、sealed、override。
virtual 表示允許子類的同名方法與其①建立繼承鏈。
override 表示其①與父類的同名方法之間建立了繼承鏈,並隱式使用 virtual 關鍵字。
new 表示其切斷了其①與父類的同名方法之間的繼承鏈。
sealed 表示將其①與父類的同名方法建立繼承鏈(注意這個就是 override 關鍵字的特性),並且不允許子類的同名方法與其建立繼承鏈。在使用 sealed 關鍵字時,必須同時顯式使用 override 關鍵字。
以及:
在定義方法時,若不使用以上關鍵字,方法就會具有new關鍵字的特性。對於這一點,如果父類中沒有同名方法,則沒有任何影響;如果父類中存在一個同名方法,編譯器會給出一個警告,詢問你是否是想隱藏父類的同名方法,並推薦你顯式地為其指定new關鍵字。
①其:指代正在進行聲明的方法。
依照上述的說明,在調用類上的某個方法時,可以為該方法構建出一個或多個“方法繼承鏈”。首先列出從子類②一直到父類③的類繼承鏈,並列出這些類對該方法的最初定義或重定義。然後從父類到子類,逐個檢查每個類對該方法的定義,按以下規則構造方法繼承鏈:
任何一個沒有使用 override 或 sealed 關鍵字的方法定義都將成為繼承鏈的開端;
如果該類在定義方法時使用了 virtual 關鍵字,則會被附加到繼承鏈中。
繼承鏈的結束取決於兩個因素:若子類中存在使用了 new 關鍵字的同名方法,則之前的繼承鏈立刻結束(該方法不會被添加到繼承鏈中);若子類中存在使用了 sealed 關鍵字的同名方法,則在將該方法添加到繼承鏈後,然後結束繼承鏈。
當你拿到一個子類②的實例,卻使用父類③的對象引用調用一個方法時(例如“A instanceRef = new C(); instanceRef.Foo1()”,這時類型A的引用就指向了類型C的對象),C#會先檢查該方法是否為一個虛方法(使用了 virtual 關鍵字):如果不是,則簡單地調用該方法的父類③版本即可;如果是,則沿著方法的繼承鏈向下尋找,找到位於繼承鏈底部的那個方法。
②子類:指該實例的實際類型。
③父類:指在調用方法時,使用的對象引用的類型;該類型必然是子類的父類型。