類和繼承
5.1 類繼承
通過繼承我們可以定義一個新類,新類納入一個已經聲明的類並進行擴展
可以使用一個已經存在的類作爲新類的基礎。已存在的類稱爲基類(base class),新類稱爲派生類(derived class)
派生類成員的組成如下:
■ 本身聲明中的成員
■ 基類的成員
要聲明一個派生類,需要在類名後加入基類規格說明。基類規格說明由冒號和後面跟着用作基類的類名稱組成。派生類被描述爲直接繼承自列出的基類
派生類
擴展它的基類,因爲它包含了基類的成員
,加上在它本身聲明中的新增功能。派生類不能刪除它所繼承的任何成員
5.2 訪問繼承的成員
繼承的成員可以被訪問,就像它們是派生類自己聲明的一樣
5.3 所有類都派生自object類
除了特殊的類object,所有的類都是派生類,即使它們沒有基類規格說明。類object是唯一的非派生類,因爲它是繼承層次結構的基礎
沒有基類規格說明的類隱式地直接派生自類object。不加基類規格說明只是指定object爲基類的簡寫。這兩種形式是語義等價的
注意
一個類聲明的基類規格說明中只能有一個單獨的類。這稱爲
單繼承
雖然類只能直接繼承一個基類,但繼承的層次沒有限制。也就是說,作爲基類的類可以派生自另外一個類,而這個類又派生自另外一個類,一直下去,直至最終到達object
基類
和派生類
是相對的術語。所有的類都是派生類,要麼派生自object,要麼派生自其他的類。所以,通常當我們稱一個類爲派生類時,我們的意思是它直接派生自某類而不是object
5.4 隱藏基類成員
雖然派生類不能刪除它繼承的任何成員,但可以用與基類成員名稱相同的成員來屏蔽(mask)基類成員。這是繼承的主要功能之一,非常實用
例如,我們要繼承包含某個特殊方法的基類。該方法雖然適合聲明它的類,但卻不一定適合派生類。在這種情況下,我們希望在派生類中聲明新成員以屏蔽基類中的方法。在派生類中屏蔽基類成員的一些要點如下:
要屏蔽一個繼承的數據成員,需要聲明一個新的相同類型的成員,並使用相同的
名稱
通過在派生類中聲明新的帶有相同簽名的函數成員,可以隱藏
或屏蔽
繼承的函數成員。請記住,簽名由名稱和參數列表組成,不包括返回類型
要讓編譯器知道你在故意屏蔽繼承的成員,使用new
修飾符。否則,程序可以成功編譯,但編譯器會警告你隱藏了一個繼承的成員
也可以屏蔽靜態成員
5.5 基類訪問
如果派生類必須完全地訪問被隱藏的繼承成員,可以使用基類訪問
(base access)表達式訪問隱藏的繼承成員。基類訪問表達式由關鍵字base後面跟着一個點和成員的名稱組成
語法:Console.WriteLine("{0}",base.成員名)
5.6 使用基類的引用
派生類的實例由基類的實例加上派生類新增的成員組成。派生類的引用指向整個類對象,包括基類部分
如果有一個派生類對象的引用,就可以獲取該對象基類部分的引用(使用類型轉換運算符把該引用轉換爲基類類型)。類型轉換運算符放置在對象引用的前面,由圓括號括起的要被轉換成的類名組成
5.6.1 虛方法和覆寫方法
當使用基類引用訪問派生類對象時,得到的是基類的成員。虛方法可以使基類的引用訪問“升至”派生類內
可以使用基類引用調用派生類(derivedclass)的方法,只需滿足下面的條件:
⬛ 派生類的方法和基類的方法有相同的簽名和返回類型
⬛ 基類的方法使用virtual
標註
⬛ 派生類的方法使用override
標註
其他關於
virtual
和override
修飾符的重要信息如下:
⬛ 覆寫和被覆寫的方法必須有相同的可訪問性。換一種說法,被覆寫的方法不能是private等,而覆寫方法是public
⬛ 不能覆寫static方法或非虛方法
⬛ 方法、屬性和索引器(在前一章闡述),以及另一種成員類型事件(將在後面闡述),都可以被聲明爲virtual
和override
5.6.2 覆寫標記爲override的方法
覆寫方法可以在繼承的任何層次出現
⬛ 當使用對象基類部分的引用調用一個覆寫的方法時,方法的調用被沿派生層次上溯執行,一直到標記爲override的方法的最高派生(most-derived)版本
⬛ 如果在更高的派生級別有該方法的其他聲明,但沒有被標記爲override
,那麼它們不會被調用
5.7 構造函數的執行
要創建對象的基類部分,需要隱式調用基類的某個構造函數作爲創建實例過程的一部分
繼承層次鏈中的每個類在執行它自己的構造函數體之前執行它的基類構造函數
5.7.1 構造函數初始化語句
默認情況下,在構造對象時,將調用基類的無參數構造函數。但構造函數可以重載,所以基類可能有一個以上的構造函數。如果希望派生類使用一個指定的基類構造函數而不是無參數構造函數,必須在構造函數初始化語句中指定它
有兩種形式的構造函數初始化語句。
⬛ 第一種形式使用關鍵字base並指明使用哪一個基類構造函數。
⬛ 第二種形式使用關鍵字this並指明應該使用當前類的哪一個構造函數。
基類構造函數初始化語句放在冒號後面,冒號緊跟着類的構造函數聲明的參數列表。構造函數初始化語句由關鍵字base
和要調用的基類構造函數的參數列表組成
5.7.2 類訪問修飾符
類可以被系統中其他類看到並訪問。這一節闡述類的可訪問性。雖然我會在解說和示例中使用類,因爲類是我們在書中一直闡述的內容,但可訪問性規則也適用於以後將會闡述到的其他類型
可訪問的
(accessible)有時也稱爲可見的
(visible),它們可以互換使用。類的可訪問性有兩個級別:public
和internal
5.8 程序集間的繼承
C#允許從一個在不同的程序集內定義的基類來派生類
要從不同程序集中定義的基類派生類,必須具備以下條件:
⬛ 基類必須被聲明爲public,這樣才能從它所在的程序集外部訪問它
⬛ 必須在Visual Studio工程中的References節點中添加對包含該基類的程序集的引用。可以在Solution Explorer中找到該標題
要使引用其他程序集中的類和類型更容易,不使用它們的完全限定名稱,可以在源文件的頂部放置一個using
指令,並帶上將要訪問的類或類型所在的命名空間
5.9 成員訪問修飾符
類的可訪問性描述了類的可見性;成員的可訪問性描述了類成員的可見性
聲明在類中的每個成員對系統的不同部分可見,這依賴於類聲明中指派給它的訪問修飾符。你已經看到private
成員僅對同一類的其他成員可見,而public成員對程序集外部的類也可見。在這一節,我們將再次觀察public
和private
訪問級別,以及其他3個可訪問性級別。
所有顯式聲明在類聲明中的成員都是互相可見的,無論它們的訪問性如何
繼承的成員不在類的聲明中顯式聲明,所以,如你所見,繼承的成員對派生類的成員可以是可見的,也可以是不可見的
5個成員訪問級別的名稱 |
---|
public、private、protected、 internal、 protected internal |
敲黑板啦
必須對每個成員指定成員訪問級別。如果不指定某個成員的訪問級別,它的隱式訪問級別爲private
成員不能比它的類有更高的可訪問性。也就是說,如果一個類的可訪問性限於它所在的程序集,那麼類的成員個體也不能從程序集的外部看到,無論它們的訪問修飾符是什麼,public
也不例外
5.9.1 公有成員的可訪問性
public訪問級別是限制性最少的。所有的類,包括程序集內部的類和外部的類都可以自由地訪問成員
5.9.2 私有成員的可訪問性
私有訪問成員級別是限制最嚴格的
⬛private
類成員只能被它自己的類的成員訪問。它不能被其他的類訪問,包括繼承它的類
⬛ 然而,private
成員能被嵌套在它的類中的類成員訪問
5.9.3 受保護成員的可訪問性
protected
訪問級別如同private
訪問級別,除了一點,它允許派生自該類的類訪問該成員
注意,即使程序集外部繼承該類的類也能訪問該成員
5.9.4 內部成員的可訪問性
標記爲internal
的成員對程序集內部的所有類可見,但對程序集外部的類不可見
5.9.5 受保護內部成員的可訪問性
標記爲protected internal
的成員對所有繼承該類的類以及所有程序集內部的類可見
注意,允許訪問的集合是protected
修飾符允許的類的集合加上internal
修飾符允許的類的集合
注意,這是protected
和internal
的並集,不是交集
5.9.6 成員訪問修飾符小結
修飾符 | 說明 |
---|---|
public | 對任何類可訪問 |
private | 只在類的內部可訪問 |
protect | 對所有繼承該類的類可訪問 |
internal | 對該程序集內所有類可訪問 |
protect internal | 對所有繼承該類的類或程序集內聲明類可訪問 |
5.10 抽象成員
抽象成員是指設計爲被覆寫的函數成員。抽象成員有以下特徵:
⬛ 必須是一個函數成員。也就是說,字段和常量不能爲抽象成員
⬛ 必須用abstract修飾符標記
⬛ 不能有實現代碼塊。抽象成員的代碼用分號表示
抽象成員
只可以在抽象類
中聲明
有4種類型可以聲明爲抽象的:
方法;屬性;事件;索引
關於抽象成員的其他重要事項如下:
⬛ 儘管抽象成員必須在派生類中用相應的成員覆寫,但不能把virtual
修飾符附加到abstract
修飾符
⬛ 類似虛成員,派生類中抽象成員的實現必須指定override
修飾符
5.11 抽象類
抽象類就是指設計爲被繼承的類。抽象類只能被用作其他類的基類
⬛ 不能創建抽象類的實例
⬛ 抽象類使用abstract
修飾符聲明
⬛ 抽象類可以包含抽象成員或普通的非抽象成員。抽象類的成員可以是抽象成員和普通帶實現的成員的任意組合
⬛ 抽象類自己可以派生自另一個抽象類
⬛ 任何派生自抽象類的類必須使用override關鍵字實現該類所有的抽象成員,除非派生類自己也是抽象類
5.12 密封類
抽象類必須用作基類,它不能像獨立的類那樣被實例化。密封類與它相反
⬛ 密封類只能被用作獨立的類,它不能被用作基類
⬛ 密封類使用sealed修飾符標註
5.13 靜態類
靜態類中所有成員都是靜態的。靜態類用於存放不受實例數據影響的數據和函數。靜態類的一個常見的用途可能就是創建一個包含一組數學方法和值的數學庫
關於靜態類需要了解的重要事情如下:
⬛ 類本身必須標記爲static
⬛ 類的所有成員必須是靜態的
⬛ 類可以有一個靜態構造函數,但不能有實例構造函數,不能創建該類的實例
⬛ 靜態類是隱式密封的,也就是說,不能繼承靜態類
可以使用類名和成員名,像訪問其他靜態成員那樣訪問它的成員
5.14 擴展方法
目前爲止每個方法都和聲明它的類關聯。擴展方法特性擴展了這個邊界,允許編寫的方法和聲明它的類之外的類關聯
擴展方法的重要要求如下:
⬛ 聲明擴展方法的類必須聲明爲static
⬛ 擴展方法本身必須聲明爲static
⬛ 擴展方法必須包含關鍵字this
作爲它的第一個參數類型,並在後面跟着它所擴展的類的名稱
語法:訪問修飾符 static 數據類型 擴展方法名 (this 類型 )
5.15 命名約定
編寫程序時會出現很多名稱:類的名稱、變量名稱、方法名稱、屬性名稱和許多其他名稱。閱讀程序時,使用命名約定是爲要處理的對象種類提供線索的重要方法
風格名稱 | 推薦使用 |
---|---|
Pascal (帕斯卡) | 類、方法、命名空間、屬性和公共字段 |
駝峯式 | 局部變量的名稱、方法聲明的形參名稱 |
_下劃線+駝峯式 | 私有和受保護的字段 |