1. 程式人生 > >第六章 型別和成員基礎

第六章 型別和成員基礎

目錄:

6.1 型別的各種成員

6.2 型別的可見性

6.3 成員的可見性

6.4 靜態類

6.5 分部類,結構和介面

6.6 元件,多型和版本控制

 

6.1 型別的各種成員

常量:資料值恆定不變的符號。常亮總與型別管理,不與型別的例項關聯。邏輯上總是靜態成員

欄位:只讀或可讀/可寫的資料值。

例項構造器:將物件的例項欄位初始化為良好初始狀態的特使方法。

型別構造器:將型別的靜態欄位初始化為良好初始狀態的特殊方法。

方法:更改或查詢型別或物件狀態的函式。作用於型別稱為靜態方法,作用於物件稱為例項方法。

操作符過載:實際是方法,定義了當操作符作用於物件時,應該如何操作該物件。

轉換操作符:定義如何隱士或顯示將物件從一種型別轉型為另一種型別的方法。

屬性:允許用簡單的,欄位風格的語法設定或查詢型別或物件的邏輯狀態,同時保證狀態不被破壞。

事件:靜態事件允許型別向一個或多個靜態或例項方法傳送通知。例項事件允許物件向一個或多個靜態或例項方法傳送通知。引發事件通常是為了響應提供事件的型別或物件的狀態的改變。事件包含兩個方法,允許靜態或例項方法登記或登出對該事件的關注。還有一個委託欄位來維護已登記的方法集。

型別:型別可定義其他巢狀型別。

6.2 型別的可見性

public:不僅對定義程式集中的程式碼可見,還對其他程式集中的程式碼可見。

internal:僅對定義程式集中的程式碼可見。

友元程式集:能夠使定義為internal的型別給另一個程式集的程式碼訪問。 生程式集時,可用”InternalsVisibleTo“特性:該特性獲取友元程式集名稱和公鑰的字串引數。

 

6.3 成員的可見性

CLR術語 C#術語 描述
Private private 成員只能由定義型別或任何巢狀型別中方法訪問
Family protected 成員只能由定義型別,任何巢狀型別或者不管在任何程式集中的派生型別中的方法訪問
Family and Assembly (不支援) 成員只能由定義型別,任何巢狀型別或者同一程式集中定義的任何派生型別中的方法訪問
Assembly internal 成員只能由定義程式集中的方法訪問。
Family or Assembly protected internal 成員可由任何巢狀型別,任何派生型別或者定義程式集中的任何方法訪問
Public public 成員可由任何程式集的任何方法訪問

在C#中,如果沒有顯式宣告成員的可訪問性,編譯器通常預設選擇private。CLR要求介面型別的所有成員都具有Public可訪問性。編譯器禁止開發人員顯式指定介面成員的可訪問性,它會自動將所有成員的可訪問性設為public。

派生類重寫基型別定義的成員時,C#編譯器要求原始成員和重寫成員具有相同的可訪問性。

6.4 靜態類

靜態類的作用是組合一組相關的成員。在C#中,要用static關鍵字定義不可例項化的類。該類只能應用於類,不能應用於結構。因為CLR總是允許值型別例項化,這是沒辦法阻止的。

限制:

靜態類必須直接從基類System.Object派生,從其他任何基類派生都沒有意義。

靜態類不能實現任何介面,這是因為只有使用類的例項時,才可呼叫類的介面方法。

靜態類只能定義靜態成員(欄位,方法,屬性和事件),任何例項成員都會導致編譯器報錯。

靜態成員不能作為欄位,方法引數或區域性變數使用,因為它們都代表引用了例項的變數,而這是不允許的。

6.5 分部類,結構和介面

partial關鍵字告訴編譯器:類,結構或介面的定義原始碼可能要分散到一個或多個原始碼檔案中。

將型別原始碼分散到多個檔案的原因有三:

原始碼控制

在同一個檔案中或結構分解成不同的邏輯單元

程式碼拆分

6.6 元件,多型和版本控制

元件軟體程式設計(Component SoftWare Programming,CSP)是OOP發展到極致的成果。下面列舉元件的一些特點。

元件(.Net Framework稱為程式集)有“已經發布”的意思。

元件有自己的標誌(名稱,版本,語言文化和公鑰)。

元件永遠維持自己的標誌(程式集中的程式碼永遠不會靜態連結到另一個程式集中;.NET總是使用動態連結)

元件清楚指明它所依賴的元件(引用元資料表)。

元件應編擋它的類和成員。C#語言通過原始碼內的XML文件和編譯器的/doc命令列開關提供這個共功能。

元件必須指定它需要的安全許可權。CLR的程式碼訪問安全性(Code Access Security,CAS)機提供這個功能。

元件要釋出在任何“維護版本”中都不會改變的介面(物件模型)。“維護版本”代表元件的新版本,它向後相容元件的原始版本。

.NET Framework 中的版本包含四個部分:主版本號(major version),次版本號(minor version),內部版本號(build number)和修訂號(revision)。major/minor部分通常程式碼程式集的一個連續的,穩定的功能集,而build/revision部分通常代表對這個功能集的一次維護。

將一個元件(程式集)中定義的型別作為另一個元件(程式集)中的一個型別的基型別使用時,便會發生版本控制問題。顯然,如果基類的版本(被修改得)低於派生類,派生類的行為也會改變,這可能造成類的行為失常。 

與元件版本控制相關的C#關鍵字

C#關鍵字 型別 方法/屬性/事件 常量/欄位
abstract 表示不能構造該型別的例項 表示為了構造派生型別的例項,派生型別必須重寫並實現這個成員 (不允許)
virtual (不允許) 表示這個成員可由派生型別重寫 (不允許)
override (不允許) 表示派生型別正在重寫基型別的成員 (不允許)
sealed 表示該型別不能用作基型別 表示這個成員不能被派生型別重寫,只能將該關鍵字應用於重寫虛方法的方法 (不允許)
new 應用於巢狀型別,方法,屬性,常量或欄位時,表示該成員與基類中相似的成員無任何關係

 

6.6.1 CLR如何呼叫虛方法,屬性和事件

編譯程式碼,編譯器會在程式集的方法定義表中寫人記錄項,每個記錄項都用一組標誌(flag)指明方法是例項方法,虛方法還是靜態方法。

寫程式碼呼叫方法,生成呼叫程式碼的編譯器會檢查方法定義的標誌,判斷應如何生成IL程式碼來正確呼叫方法。CLR提供兩個方法呼叫指令。

call:該IL指令可呼叫靜態方法,例項方法和虛方法。用call指令呼叫靜態方法,必須指定方法的定義型別。用call指令呼叫例項方法或虛方法,必須指定引用了物件的變數。call指令假定該變數不為null。call指令經常用於以非虛方式呼叫虛方法。

callvirt:該IL指令可呼叫例項方法和虛方法,不能呼叫靜態方法。用callvirt指令呼叫例項方法或虛方法,必須指定引用了物件的變數。用callvirt指令呼叫非虛例項方法,變數的型別指明瞭方法的定義型別。用callvirt指令呼叫虛例項方法,CLR調查發出呼叫的物件的實際型別,然後以多型方式呼叫方法。為了確定型別,發出呼叫的變數絕不能是null。換言之,編譯這個呼叫時,JIT編譯器會生成程式碼來驗證變數的值是不是null。如果是,callvirt指令造成CLR丟擲NullReferenceException異常。

設計型別時應儘量減少虛方法數量。首先,呼叫虛方法的速度比呼叫非虛方法慢。其次,JIT編譯器不能內嵌虛方法,這進一步影響效能。第三,虛方法使元件版本控制變得更脆弱。第四,定義基型別時,經常要提供一組過載的簡便方法。如果希望這些方法時多型的,最好的方法是使最複雜的方法成為虛方法,使所有過載的簡便方法成為非虛方法。

6.6.2 合理使用型別的可見性和成員的可訪問

密封類相對於非密封類的優勢:

版本控制

效能

安全性和可預測性

定義類時遵循的原則:

定義類時,除非確定要將其作為基類,並允許派生類對它進行特化,否則總是顯式地指定為sealed類。

類的內部,將資料欄位定義為private。

在類的內部,將自己的方法,屬性和事件定義為priavate和非虛。如果要公開就定義為public,儘量避免定義為protected或internal,因為這會使型別面臨更大的安全風險。最後才考慮使用virtual,因為虛成員會放棄許多控制,喪失獨立性,變得徹底依賴於派生型別的正確行為。

OOP有一條古老的格言,大意時當事情變得過於複雜時,就搞更多的型別出來。當演算法的實現開始變得複雜時,我會定義一些輔助型別來封裝獨立的功能。如果定義的輔助型別只由一個“超型別”使用,我會在“超型別”中巢狀這些輔助型別。這樣除了可以限制範圍,還允許巢狀的輔助型別中的程式碼引用“超型別”中定義的私有成員。

6.6.3 對型別進行版本控制時的虛方法的處理

使用override重寫基類虛方法。