第 36 講:面向?qū)ο缶幊蹋ò耍豪^承控制
上文我們對(duì)繼承的基本概念,以及語法格式作出了說明,其實(shí)還是不算特別難的知識(shí)點(diǎn)。那么下面我們深層次繼續(xù)討論關(guān)于繼承機(jī)制的控制,來靈活處理和約束代碼,達(dá)到良好的編碼習(xí)慣。
Part 1 base
引用
之前我們說過 this
引用。this
引用表達(dá)的是“當(dāng)前這個(gè)類里的成員的使用”。因?yàn)樗鼘懖粚懸话愣紵o所謂,只要不會(huì)和比如參數(shù)、臨時(shí)變量重名必須指定 this.
以外,其它情況是用不到的。
下面我們說說 base
引用的存在。base
和 this
的地位基本上差不多,只是 base
是用來表達(dá)“基類型的成員”。按道理講,我們一旦寫了繼承的語法 : 類名
,那么基類型的成員就可提供給子類型使用。
舉個(gè)例子,假設(shè) Person
類型是基類型,Student
是從 Person
類繼承下來的子類型。那么假設(shè) Person
類含有 Name
屬性的話,子類型使用下面哪一個(gè)(或者哪一些)寫法才是對(duì)的呢?
this.Name
base.Name
Name
如果你能猜到結(jié)果,就說明你基本上對(duì)面向?qū)ο蟮睦^承機(jī)制有一定了解了。
答案是,三個(gè)都對(duì)。而且三個(gè)寫法沒有任何語義上的差別。this.Name
這么寫是有道理的:因?yàn)榛愋偷某蓡T都可以拿來給子類型用了,那當(dāng)然就是自己的東西了。所以,this.Name
是對(duì)的;base.Name
是正統(tǒng)寫法,因?yàn)楸緛砭褪腔愋偷某蓡T拿下來用的,歸結(jié)到底,其實(shí)也是基類型的成員。語法上子類型根本沒有寫 Name
,所以寫 base.Name
肯定是對(duì)的;Name
也是對(duì)的。因?yàn)槭紫任覀儾粫?huì)遇到重名的問題,畢竟我們按照取名的規(guī)范和約定,可能和成員重名的只有參數(shù)和臨時(shí)變量而已。但是參數(shù)和臨時(shí)變量都是小寫開頭的駝峰命名法,而屬性卻是大寫開頭的帕斯卡命名法。你說說,這怎么可能能重名。所以,Name
也是對(duì)的。
是的,這一節(jié)就只是想告訴你,
base.Name
這樣的書寫格式。但是因?yàn)闃O其不常用,因此你當(dāng)相聲聽就完事了。實(shí)際上,這種語法本來不是很常用,但它也不是不重要的東西。下一節(jié)內(nèi)容我們會(huì)講到成員的重寫,就會(huì)大量用到base.
的語法。但是this.
確實(shí)很少用。
Part 2 訪問修飾符在繼承里的效果
2-1 繼承關(guān)系下的 private
訪問修飾符
很高興我們說到這個(gè)知識(shí)點(diǎn)了。上一節(jié)的內(nèi)容我們并沒有說這一點(diǎn)。
假設(shè)我們有一個(gè) Student
類和 Person
類。如果 Person
類包含了一個(gè) private
修飾的方法 Hello
,那么 Student
類的實(shí)例是否可以使用 stu.Hello()
的類似語法呢?
很抱歉,不能。還記得 private
修飾符的規(guī)則嗎?是的,private
修飾的成員僅僅在當(dāng)前這個(gè)類的范圍里可以隨便用。只要出了這個(gè)類,不管在哪里,都是不可以用的。就算是繼承也一樣。因?yàn)槟愕睦^承是把上面的成員直接拿下來用,但 private
修飾符意味著出了這個(gè)類你就看不見了,所以你拿下來的 Hello
成員是不可使用的狀態(tài)。
所以,在繼承關(guān)系里,private
成員是不可繼承下來的;但是,internal
和 public
修飾的成員是可以的。
2-2 protected
關(guān)鍵字
那么,考慮一種情況。假設(shè)我前面設(shè)計(jì)的 Student
類和 Person
類還是保留下來,Person
里的那些字段是被 private
修飾過的,因此 Student
類里無法直接為其賦值。于是,我們只能使用前面那種“通過屬性”的形式來對(duì)字段間接賦值了。
確實(shí)可以,但問題在哪里呢?private
類型的成員只在類里可用,那么我們可以稍作推廣,如果只在往下派生的這個(gè)繼承鏈條上可用,別的地方不可用的話呢?這就是我們 protected
這個(gè)新的修飾符的用途了。
protected
修飾符專門表達(dá)“往下繼承的任何類都可以用”。比如說 A
類型往下派生出 B
類型。A
類型有一個(gè) protected
修飾的成員 _name
,那么 B
類型是可以看到的。但是,除此之外,別的任何地方都看不到。
我們把之前的代碼稍加改動(dòng):
如果我們這么寫了的話,下面的類型 Student
就可以直接對(duì) _name
這些字段直接賦值了。但是,如果你從外部實(shí)例化出來的 Person
或者 Student
類型的實(shí)例,都是不可以直接使用這些 protected
修飾的字段的,因?yàn)槭菑耐獠吭L問的。
2-3 protected internal
組合關(guān)鍵字
C# 是相當(dāng)嚴(yán)謹(jǐn)?shù)恼Z言。訪問修飾符都能玩出花樣。是的,protected
還能和 internal
組合使用你敢信。
因?yàn)榍懊嫖覀冋f過,protected
只用在派生類上,所以別的地方都不可用。我們稍微擴(kuò)大一點(diǎn)范圍,如果整個(gè)項(xiàng)目里使用的話呢?這就要用到這里的組合關(guān)鍵字 protected internal
了。
protected internal
組合關(guān)鍵字是 protected
和 internal
效果的復(fù)合。它既包含有繼承鏈條的用法,還包含項(xiàng)目里隨便用的用法。是的,如果我們把鏈條畫成一條直線,而把項(xiàng)目畫成一個(gè)圓的話,那么 protected internal
大概就長這樣:

是的,假設(shè)紅色線條是派生的子類型的存儲(chǔ)位置,而圓圈表達(dá)的是范圍的話,protected internal
就是“項(xiàng)目圓圈 + 線條”這個(gè)范圍的總和。但是,除了這個(gè)“線條 + 項(xiàng)目圓圈”的范疇,都是看不見這個(gè)成員的。
Part 3 繼承鏈控制
和 readonly
這類修飾符一樣,C# 還有一些提供用來專門規(guī)范化處理調(diào)用和繼承這樣行為的約束性質(zhì)關(guān)鍵字。代碼加不加它們其實(shí)無所謂,因?yàn)樗⒉挥绊懘a的執(zhí)行,但添加了它們可以讓代碼更為規(guī)范化。
下面我們要說兩個(gè)關(guān)鍵字類型,這兩個(gè)關(guān)鍵字可以直接用在類的上面修飾,用來表達(dá)特殊的限制和約束。
3-1 abstract
類修飾符
我們知道,Student
類是走 Person
類派生出來的類型。而 Person
作為對(duì)象的抽象體現(xiàn)和提取,我們有時(shí)候也沒有必要單獨(dú)對(duì) Person
類型直接實(shí)例化。比如我寫了三種不同的類,同時(shí)走 Person
類派生:Student
、Teacher
和 Worker
。它們?nèi)慷技由狭?: Person
的語法來繼承。
那么,既然我們有了這么多子類型,顯然子類型既然創(chuàng)建出來,就是為了提供類型的使用和實(shí)例化的。那么此時(shí)的 Person
就成了“數(shù)據(jù)的提供方”了。顯然此時(shí)我們有了這些細(xì)致的類型后,這個(gè) Person
就變得沒有必要實(shí)例化。
此時(shí),我們可以為 Person
上追加 abstract
關(guān)鍵字,以表示這個(gè)類型不再可以使用實(shí)例化的語句而產(chǎn)生實(shí)例。
注意第 1 行,我們追加了 abstract
后,就不再可以使用 Person p = new Person()
的類似語法了。取而代之的是,我們只能為下面 Student
、Teacher
和 Worker
使用實(shí)例化語句 Student s = new Student(...)
之類的。
按照規(guī)范化的要求,我們建議所有的“數(shù)據(jù)提供方”都用 abstract
修飾。比如說我們建議這里的 Person
要用 abstract
修飾,來避免別人在使用的時(shí)候,對(duì) Person
這個(gè)純粹是為了提供數(shù)據(jù)用的類來實(shí)例化。
new
語句),因此我們無法讓抽象類從實(shí)例類型進(jìn)行派生。比如假設(shè) Human
是一個(gè)實(shí)例類型(可以實(shí)例化的那種),而 Person
從 Human
類型派生,但 Person
如果是抽象類的話,就不行。
3-2 sealed
類修飾符
同理,有阻止實(shí)例化,就有阻止繼承。顯然,Student
是最下面的類型了。要知道類的繼承是可以不限制層數(shù)的,A
可以從 B
派生,而 B
很可能還從 C
派生。按照這種道理,Student
這樣的類型顯然就已經(jīng)不再需要給下面繼續(xù)提供繼承關(guān)系的使用了。比如我難道還要給個(gè)類型從 Student
類派生嗎?沒有必要了吧。這個(gè)時(shí)候我們可以對(duì) Student
類的聲明上添加 sealed
關(guān)鍵字以表達(dá)“繼承關(guān)系到我這里就結(jié)束了”。
sealed
程序都可以跑起來,但是添加這個(gè)關(guān)鍵字可以讓別人在使用的時(shí)候明白和知道這個(gè)類不是拿來繼承的。
Part 4 我不要基類型的成員了:new
成員修飾符
我們往往在繼承的時(shí)候會(huì)遇到這么一種情況:基類型提供的某個(gè)(或某些)成員對(duì)于現(xiàn)在這個(gè)類型來說完全就是沒必要的,此時(shí)原來的成員就需要我們給完全覆蓋掉。然后我們可以對(duì)這個(gè)成員標(biāo)記 new
關(guān)鍵字來覆蓋掉原來的成員。
舉個(gè)例子。
基類型有一個(gè) ShowDetails
方法用來輸出一行固定的文字“標(biāo)準(zhǔn)的交通工具”。可問題是我現(xiàn)在買了一輛敞篷跑車(指的是這里的 ConvertibleCar : Car
的繼承關(guān)系),車頂是可以打開的。我總不能還說我這個(gè)車是標(biāo)準(zhǔn)交通工具吧。于是,原來的 ShowDetails
就得給爺爬。
可是,我直接寫 public void ShowDetails()
的時(shí)候,編譯器會(huì)提供警告信息,告訴你“你在隱藏基類型的 ShowDetails
方法”。

那么這句話是啥意思呢?這句話是在說,“因?yàn)榛愋鸵灿幸粋€(gè)完全同名的方法 ShowDetails
,因此如果你不用它的話,需要追加 new
關(guān)鍵字來表達(dá)‘基類型的 ShowDetails
被我忽略掉了’”。
所以,如果需要忽略不需要基類型的內(nèi)容的話,干脆我們直接加個(gè) new
來覆蓋掉原本的 ShowDetails
。
new
關(guān)鍵字除了用來實(shí)例化,這里還可以用來表達(dá)“覆蓋成員”,所以這是 new
的第二個(gè)用法。
Part 5 重新審視 static
修飾符對(duì)成員的修飾
要說繼承,我們就得說到靜態(tài)和實(shí)例成員。之前我們一直都是在說實(shí)例成員的基本用法(因?yàn)槔^承會(huì)被自動(dòng)復(fù)制下來提供使用)。那么靜態(tài)的成員是不用實(shí)例化就可以使用的(往往寫成 類名.成員名
),因此,追加了 static
的關(guān)鍵字的成員,在繼承這個(gè)機(jī)制下又有什么新鮮玩意兒呢?
你只需要記住唯一的繼承原則就好:所有時(shí)候,繼承都是直接把基類型的所有成員都拿下來用的,不論是不是 private
的;只不過訪問修飾符會(huì)限制你可以訪問和使用的級(jí)別。這句話我覺得你應(yīng)該看得懂,就是說所有成員都會(huì)被復(fù)制下來,這包括字段、屬性、方法等等。不論它用了什么修飾符修飾。唯一體現(xiàn)出差別的地方只是在于訪問修飾符會(huì)限制你可使用的范圍和級(jí)別而已。
那么,static
的成員也會(huì)被繼承下來。
假如我有這么一個(gè)例子。我在上面使用了 static
修飾了一個(gè)專門用來顯示一個(gè)人的基本信息的方法;而下面 Student
里也有相同的成員。我在里面寫上了三個(gè)不同的代碼:
Person.OutputInfo(person)
;Student.OutputInfo(person)
;OutputInfo(person)
。
這三個(gè)寫法到底都是怎么調(diào)用和定位的呢?Person.OutputInfo
方法的調(diào)用會(huì)自動(dòng)去找原本 Person
類里的方法;而 Student.OutputInfo
方法在 Student
里有實(shí)現(xiàn),因此會(huì)定位到 Student
里的這個(gè)方法;而 OutputInfo
是沒有寫類型名的,這暗示應(yīng)該是同一個(gè)類型里的成員才是,因此它和第二種寫法等價(jià)。
Part 6 我可以從多個(gè)類派生嗎?
問題很好。我可能有這么一個(gè)需求,就是我可能需要實(shí)現(xiàn)一個(gè)類,這個(gè)類型想從多個(gè)類的里面去拷貝復(fù)制成員來使用,這樣 C# 是允許的嗎?
很遺憾,并不允許,因?yàn)檫@破壞了面向?qū)ο蟮幕驹瓌t:“是什么”的關(guān)系。像是“車是交通工具”,那你還能想到車還能是別的什么玩意兒嗎?一個(gè)事物只能屬于一個(gè)東西的一個(gè)子類型。
有人說,那不對(duì)啊。就比如說一個(gè)小孩子,ta 肯定屬于“人類”這個(gè)范疇,但是 ta 也屬于“動(dòng)物”(指高級(jí)動(dòng)物)這個(gè)范疇啊。是的,但是你想過沒,“‘小孩子’屬于‘人類’”是對(duì)的說法,但“‘人類’本身就屬于‘高級(jí)動(dòng)物’的一個(gè)子類型”。因此繼承關(guān)系實(shí)際上是這樣的:“小孩子→人類→動(dòng)物”(一條鏈),而并不是“人類←小孩子→動(dòng)物”(兩個(gè)分支)。
世間萬物都有一個(gè)歸屬的關(guān)系,且這個(gè)關(guān)系應(yīng)當(dāng)是唯一的。不論怎么往下派生多個(gè)情況,往上面倒回去,總能找到唯一的一個(gè)“根”,這就是面向?qū)ο蟆?/span>