類型能夠定義如下種類的成員程序員
1 常量算法
常量是指出數據值恆定不變的符號。這種符號使代碼更易閱讀和維護。常量總與類型管理,不與類型的實例管理。常量邏輯上老是靜態成員。編程
2 字段c#
字段表示只讀或可讀、可寫的數據值。字段能夠是靜態的,這種字段被認爲是類型狀態的一部分。字段也能夠是實例(非靜態),這種字段被認爲是對象狀態的一部分。強烈建議將字段聲明爲私有,防止類型或對象的狀態被類型外部的代碼破壞安全
3 實例構造器編程語言
實例構造器是將新對象的實例字段初始化爲良好初始狀態的特殊方法。ide
4 類型構造器函數
類型構造器是將類型的靜態字段初始化爲良好初始狀態的特殊方法oop
5 方法性能
方法時更改或查詢類型或對象狀態的函數。做用域類型稱爲靜態方法,做用於對象稱爲實例方法。方法一般要讀寫類型或對象的字段
6 操做符重載
操做符重載實際是方法,定義了當操做符做用域對象時,應該如何操做該對象。
7 轉換操做符
轉換操做符是定義如何隱式或顯式將對象從一種類型轉型爲另外一種類型的方法。和操做符重載方法同樣,並非全部編程語言都支持轉換操做符。
8 屬性
屬性容許用簡單的、字段風格的語法設置或查詢類型或對象的邏輯狀態,同時保證狀態不被破壞。做用域類型稱爲靜態屬性,做用域對象稱爲實例屬性。屬性能夠無參(很是廣泛),也能夠有多個參數(比較少見,集合類用得多)
9 事件
靜態事件容許類型向一個或多個靜態或實例方法發送通知。實例(非靜態)事件容許對象向一個或多個靜態或實例方法發送通知。引起事件一般是爲了響應提供事件的類型或對象的狀態的改變。事件包含兩個方法,容許靜態或實例方法登記或註銷對該事件的關注。除了這兩個方法,事件一般還用一個委託字段來維護已登記的方法集。
10 類型
類型可定義其餘嵌套類型。一般用這個辦法將大的、複雜的類型分解成更小的構建單元以簡化實現。
默認的可見性爲internal,若是不加修飾詞,就意味着可見性爲internal。
友元程序集可使不一樣項目團隊定義的程序集間互相訪問internal的類型。
定義類型的成員(包括嵌套類型)時,可指定成員的可訪問性。在代碼中引用成員時,成員的可訪問性指出引用是否合法。clr本身定義了一組可訪問性修飾符,但每種編程語言在向成員應用可訪問性時,都選擇了本身的一組術語以及相應的語法。
下表總結了6個用於成員的可訪問性修飾符。從第一行到最後一行,按照從限制最大到限制最小的順序排列。
有一些永遠不須要實例化的類,例如Console,Math,Environment和ThreadPool。這些類只有static成員。事實上,這種類惟一的做用就是組合一組相關的成員。例如,Math類就定義了一組執行數學運算的方法。在C#中,要用static關鍵字定義不可實例化的類。
c#編譯器對靜態類進行了以下限制。
1 靜態類必須直接從基類system.object派生,從其餘任何基類派生都沒有意義。繼承只適用於對象,而你不能建立靜態類的實例。
2 靜態類不能實現任何藉口,這是由於只有使用類的實例時,纔可調用類的接口方法。
3 靜態類只能定義靜態成員(字段、方法、屬性和事件),任何實例成員都會致使編譯器報錯。
4 靜態類不能做爲字段、方法參數或局部變量使用,由於它們都表明引用了實例的變量,而這是不容許的。編譯器檢測到任何這樣的用法都會報錯。
使用關鍵字static定義類,將致使C#編譯器將該類標記爲abstract和sealed。另外,編譯器再也不類型中生成實例構造器方法(.ctor)。
partial關鍵字告訴C#編譯器:類、結構或接口的定義源代碼可能要分散到一個或多個源代碼文件中。這種分散的緣由有三
1 源代碼控制
假定類型定義包含大量源代碼,一個程序員把他從源代碼控制系統簽出已進行修改。沒有其餘程序員能同時修改這個類型,除非以後執行合併。使用partial關鍵字可將類型的代碼分散到多個源代碼文件中,每一個文件均可單獨簽出,多個程序員能同時編輯類型。
2 在同一個文件將類或結構分解成不一樣的邏輯單元
3 代碼拆分
組件軟件編程是OOP發展到極致的成功,下面列舉組件的一些特色。
1 組件(.NET Framework稱爲程序集)有已發佈的意思
2 組件有本身的標識(名稱、版本、語言文化和公鑰)
3 組件永遠維持本身的標識(程序集中的代碼永遠不會靜態連接到另外一個程序集中,.NET老是使用動態連接)
4 組件清楚指明它所依賴的組件(引用元數據表)
5 組件應編檔它的類和成員。C#語言經過源代碼內的XML文檔和編譯器的/doc命令行開關提供這個功能。
6 組件必須制定它須要的安全權限。CLR的代碼訪問安全性(code access security)機制提供這個功能。
7 組件要發佈在任何「維護版本」中都不會改變的接口(對象模型)。
將一個組件(程序集)中定義的類型做爲另外一個組件(程序集)中的一個類型的基類使用時,變回發生版本控制問題。
c#提供了5個能影響組件版本控制的關鍵字,可將它們應用於類型以及類型的成員。這些關鍵字直接對應clr用於支持組件版本控制的功能。
本方法表明在類型或類型的實例上執行某些操做的代碼。在類型上執行操做,稱爲靜態方法;在類型的實例上執行操做,稱爲非靜態方法。全部方法都有名稱、簽名和返回類型(可爲void)。clr容許類型定義多個同名方法,只要每一個方法都有一組不一樣的參數或者一個不一樣的返回樂行。因此,徹底能定義兩個同名、同參數的方法,只要二者返回類型類型不一樣。但除了IL彙編語言,大多數語言(包括c#)在判斷方法的惟一性時,除了方法名以外,都只以參數爲準,方法返回類型會被忽略。
一下employee類定義了3種不一樣的方法
Internal class Employee{ //非虛實例方法 public int32 GetYearsEmployed(){………} //非虛實例方法 public virtual string GetProgressReport(){………} //非虛實例方法 public static Employee Lookup(string name){………} }
編譯上述代碼,編譯器會在程序集的方法定義表中寫入3個記錄向,每一個記錄項都用一組標誌(flag)指明方法時實例方法、虛方法仍是靜態方法。
寫代碼調用這些方法,生成調用代碼的編譯器會檢查方法定義的標誌(flag),判斷應如何生成il代碼來正確調用方法。clr提供兩個方法調用指令。
Call
改il指令可調用靜態方法、實例方法和虛方法。用call指令調用靜態方法,必須指定方法的定義類型。用call指令調用實例方法或虛方法,必須指定引用了對象的變量。call 指令鑑定該變量不爲null。換言之,變量自己的類型指明瞭方法的定義類型。若是變量的類型沒有定義該方法,就檢查基類型來查找匹配方法。call指令常常用於以非虛方式調用虛方法。
callvirt
該il指令可調用實例方法或虛方法,不能調用靜態方法。與call命令的區別是,callvirtual會先檢查調用變量是否爲空,因此執行速度比call稍慢。
編譯器有時用call而不是callvirt調用虛方法,好比有時候子類使用base.tostring(),這是c#編譯器生成call指令來確保以非虛方式調用基類的tostring方法。若是以虛方式調用tostring,調用會遞歸執行,直至線程棧溢出,這顯然不是預期的。
不管用call仍是callvirt調用實例方法或虛方法,這些方法一般接收隱藏this實參做爲方法第一個參數。this實參引用要操做的對象。
設計類型時應儘可能減小虛方法數量。首先,調用虛方法的速度比調用非虛方法慢。其次,jit編譯器不能內嵌(inline)虛方法,這進一步影響性能。第三。虛方法使組件版本控制變得更脆弱。第四,定義基類型時,常常要提供一組重載的簡便方法。若是但願這些方法時多態的,最好的辦法就是使最複雜的方法稱爲虛方法,使全部重載的簡便方法稱爲非虛方法。
C#編譯器在內的許多編譯器都默認生成非密封類,只容許開飯人員使用關鍵字sealed將類顯式標記爲密封。我認爲如今的編譯器使用了錯誤的默認設定。密封類之因此比非密封類更好,有如下三個方面的緣由
1 版本控制
若是類最初密封,未來可在不破壞兼容性的前提下更改成非密封。但若是最初非密封,未來就不可能更改成密封,由於這將中斷派生類。
2 性能
調用虛方法在性能上不及調用非虛方法,由於clr必須在運行時查找對象的類型,判斷要調用的方法由哪一個類型定義。可是,若是jit編譯器看到使用密封類型的虛方法調用,就可採用非虛方式調用虛方法。
3 安全性和可預測性
類必須保護本身的狀態,不容許被破壞。當類處於非密封狀態時,只要它的任何數據字段或者在內部對這些字段進行處理的方法時能夠訪問的,而不是私有的,派生類就能訪問和更改基類的狀態。
如下是我本身定義類時遵循的原則。
1 定義類時,除非肯定要將其做爲基類,並容許派生類對它進行特化,不然老是顯式地指定爲sealed類。如前所述,這與c#以及其餘許多編譯器的默認方式相反。另外,我默認將類指定爲internal類,除非我但願在程序集外部公開這個類。若是我真的要定義一個可由其餘人繼承的類,同時不但願容許特化,那麼我會重寫並密封繼承的全部虛方法(sealed override)。
2 類的內部,我老是堅決果斷地將數據字段定義爲private。幸虧,c#默認就將字段標記爲private。
3 oop有一條古老的格言,大意是當事情變得過於複雜時,就搞更多的類型出來。 當算法的實現開始變得複雜時,我會定義一些輔助類型來封裝獨立的功能。
常量是值從不變化的符號。定義常量符號時,它的值必須能在編譯時肯定。肯定後,編譯器將常量值保存到程序集的元數據中。這意味着只能定義編譯器識別的基元類型的常量。·····
因爲常量值從不變化,因此常量老是被視爲類型定義的一部分。換而言之,常量老是被視爲靜態成員,而不是實例成員。定義常量將致使建立元數據。
代碼引用常量符號時,編譯器在定義常量的程序集的元數據中查找該符號,提取常量的值,將值簽入生成的il代碼中。因爲常量的值直接嵌入代碼,因此在運行時不須要爲常量分配任何內存。除此以外,不能獲取常量的地址,也不能以傳引用的方式傳遞常量。這些限制意味着常量不能很好地支持跨程序集的版本控制。所以,只有肯定一個符號的值從不變化才應定義常量。若是但願在運行時從一個程序集中提取另外一個程序集的值,那麼不該該使用常量,而應該使用readonly字段。
字段是一種數據成員,其中容納了一個值類型的實例或者一個引用類型的引用。下表總結了可應用於字段的修飾符。
如上表所示,clr支持類型(靜態)字段和實例(非靜態)字段。若是是類型字段,容納字段數據所需的動態內存是在類型對象中分配的,而類型對象是在類型加載到一個AppDomain時建立的。那麼,何時講類型加載到一個AppDomain中呢?這一般是在引用了該類型的任何方法首次進行jit編譯的時候。若是是實例字段,容納字段數據所須要的動態內存是在構造類型的實例時候分配的。
因爲字段存儲在動態內存中,因此他們的值在運行時才能獲取。字段還解決了常量存在的版本控制問題。此外,字段能夠是任何數據類型,不像常量那樣僅僅侷限於編譯器內置的基元類型。
clr支持readonly字段和read\write字段。大多數字段都是read\write字段,意味着在代碼執行過程當中,字段值能夠屢次改變。但readonly字段只能在構造器方法中寫入。(構造器方法只能調用一次,即對象首次建立時)編譯器和驗證機制確保readonly字段不會被構造器意外的任何方法寫入。注意,可利用反射修改readonly字段。