從CLR的角度看,只有值類型和引用類型兩種類型,可是從框架設計的角度咱們把類型從邏輯上分了更多的組。以下所示:編程
類是引用類型的通常狀況,佔了框架中的大多狀況,類的流行歸於它支持面向對象的特徵,以及它的廣泛的適用性,基類和抽象類是兩個特殊的邏輯分組,它們與擴張性有關。數組
因爲CLR不支持多繼承,接口類型能夠用來模擬多繼承,既能被引用類型實現,也能被值類型實現。框架
結構是值類型的通常狀況,應該用於小而簡單的類型,就像編程語言的基本類型同樣。編程語言
枚舉是值類型的一個特例,它用來定義一小組值。函數
靜態類是那些用來容納靜態成員的類型,經常使用來提供對其餘操做的快速訪問。性能
委託、異常、Attribute、數據、集合都是引用類型的特例,各有各自的用途。操作系統
在設計大型框架以前,應該決定如何將功能劃分到一組功能域中,這些功能域由名字空間表示,爲了確保一組有條理的名字空間包含的類型能很好的集成,不發生衝突,以及不會重複,自頂向下的設計頗有必要。致使了下面的規範:設計
要用名字空間把類型組織成一個相關的特性域的層次結構。(主要是爲了把類型組織成一個有條理、易於瀏覽的、易於理解的層次結構)orm
避免很是深的名字空間層次(難於瀏覽,須要常常回溯)對象
避免有太多的名字空間。
避免把爲高級場景而設計的類型和常見的編程任務而設計的類型放在同一個名字空間中。(方便用戶更容易理解框架的基本概念,容易在常見的場景中使用框架)
不要指定名字空間就定義類型。(把相關的類型組織到一個層次結構中,有助於解決可能存在的名字衝突)
標準子名字空間的命名
不多使用的類型應該放在子名字空間中,以避免擾亂主名字空間,咱們肯定了幾組類型,應該把它們從主名字空間中區分離出來。
1. .Design子名字空間
僅用於設計時的類型應該放在名爲.Design的子名字空間。如:System.Windows.Forms.Design;
System.Messaging.Design;
要用帶「.Design」後綴的名字空間來容納那些爲基本名字空間提供設計時的功能的類型。
2. .Permissions子名字空間
許可類型應該放在名爲「.Permissions」子名字空間中。
要用帶「.Permissions」後綴的名字空間來容納那些基本名字空間提供自定義許可的類型。
3. .Interop子命名空間
許多框架須要支持與舊系統的互操做性(interoperability)。
要用帶「.Interop」後綴的名字空間來容納那些爲基本名字空間提供相互操做功能的類型。
要用帶「.Interop」後綴的名字空間來容納全部位於PIA中的代碼。
引用類型在堆上分配,由垃圾收集器管理;而值類型要麼在棧上分配並在棧展開時釋放,要麼內聯在容納它的類型中並在容納它的類型被釋放時釋放。所以,與引用類型的分配與釋放相比,值類型的分配與釋放開銷更低。
引用類型的數組不是非內聯分配的,意爲數組元素只是一些引用,指向那些位於堆中的引用類型的實例。而值類型的分配是內聯的,數組的元素就是值類型的真正實例。所以值類型的分配和釋放的開銷要比引用類型的大的多,在大多狀況下,值類型數組具備更好的局部性。
值類型在被強制轉換爲對象或裝箱由於裝箱和對象是在堆上分配的,且由垃圾收集器管理,因此太多的裝拆箱操做會對堆、垃圾收集器,並對系統性能形成影響。
引用類型的賦值是複製引用,而值類型的賦值複製整個值,對大的引用類型複製開銷要比值類型小的多。
引用類型是引用傳遞,值類型是值傳遞。改變引用類型的一個實例會影響其餘的實例,改變值類型的實例,不會影響到它的副本。
框架中的大多數類型應該是類,可是在某些特殊狀況下,因爲值類型所具備的特徵,使用結構更合適。
考慮定義結構而不是類(若是該類型的實例比較小,生命週期比較短,常常被內嵌在其餘對象中)
不要定義結構,除非該類型具備如下特徵:
它在邏輯上表明一個獨立的值,與基本類型類似(int)
它的實例大小小於16字節
它是不可變的
它不須要被常常裝箱
在全部的其餘狀況下,應該將類型定義爲類。
通常來講,類是用來暴漏抽象的優先選擇。
點在於當須要容許API不斷演化時,它的靈活性不如類,一旦你發佈了一個接口,它的成員就永遠固定了,給接口添加任何東西都會破壞已經實現該接口的已有類型。
類提供了更多的靈活性,你能夠給一個已發佈的類添加成員。只要添加的方法不是抽象的,任何已有的派生類無需改變仍能繼續使用。
要優先採用類而不是接口
與基於接口的API相比,基於類的API容易演化得多,由於能夠給類型添加成員而不會破壞已有的代碼。
要用抽象類而不是接口來解除協定與實現之間的耦合。
抽象類通過正確的設計,一樣可以解除協定與實現之間的耦合,與接口能達到的程度不相上下。
要定義接口,若是須要提供一個多態的值類型層次結構的話。(值類型不能自其它類型繼承,可是她們能夠實現接口)
考慮經過定義接口來達到與多重繼承相相似的效果。
不要在抽象類型中定義公有的或內部受保護的構造函數。
只有當用戶須要建立一個類型的實例時,該類型的構造參數纔是公有的,因爲你沒法建立一個抽象類的實例,所以若是抽象類型具備公有構造函數
要爲抽象類定義受保護的構造函數或內部構造函數。
受保護的構造函數僅僅是容許子類型被建立時,基類可以作本身的初始化。
內部構造函數能夠用來把該抽象類的具體實現限制在定義該抽象類的程序集中。
要爲發佈的抽象類提供至少一個繼承自該類的具體類型。
有助於驗證該抽象類的設計是否正確。
靜態類被定義爲一個只包含靜態成員的類。若是一個類被定義爲靜態,那麼它就是密封的、抽象的,不能覆蓋或者聲明任何實例成員。
靜態類是在純面向對象設計和簡單性之間的一個權衡,它們被普遍用來提供一下訪問其餘操做的快捷方式,或者不須要完整的面向對象封裝器的時候提供一些功能。(System.Enviroment)
要儘可能少用靜態類
靜態類僅被用做輔助類,來支持框架的面向對象的核心。
不要把靜態類當作雜物箱。
每個靜態類都應該有其明確的目的。
不要在靜態類中聲明或覆蓋實例成員。
要把靜態類定義爲密封的、抽象的,並添加一個私有的實例構造函數。
雖然大多數狀況下API用類或結構來構建最好,可是在有些狀況下,接口更合適。
CLR 不支持多繼承,但容許類型實現一個或多個接口,所以一般用接口來實現多繼承。
在建立可以爲多種類型(包括值類型)所支持的公共接口時。
要定義接口,若是你須要包括值類型在內的一組類型支持一些公共的API。
考慮定義接口,若是須要讓已經自其它類型繼承的類型支持該接口提供的功能。
避免使用記號接口(沒有成員的接口)
要爲接口提供至少一個實現該接口的類型。
要爲你定義的每一個接口提供至少一個使用該接口的API(一個以接口爲參數的方法或是一個類型爲該接口的屬性)
不要給已發行的接口再添加成員。
這樣作會破壞該接口的實現,爲了不版本的問題,應該建立一個新的接口。
通常來講,在爲託管代碼設計可重用的程序庫時,你應該選擇類而不是接口。
通用目的的值類型一般稱爲struct(結構)。
不要爲結構提供默認的構造函數。(C#不容許結構有默認的構造函數)
要確保全部的實例數據都爲零,false,或null時,結構仍處於有效狀態。(能夠防止在建立一個結構時建立出無效的實例)
要爲值類型實現IEquatable<T>
值類型的Object.Equals方法會致使裝箱,默認的實現並不高效,由於使用了反射,IEquatable<T>.Equals性能好的多,不會致使裝箱。
不要顯示的擴展System.ValueType
枚舉是一種特殊的值類型,有兩種類型的枚舉:簡單枚舉和標記枚舉。
簡單枚舉表明小型的、閉合的一組選擇。例如(一組顏色):
Public enumColor{
Red,
Green,
Blue,
……
}
標記枚舉的設計是爲了支持對枚舉值進行按位操做。標記枚舉的常見例子是一個選擇列表
[Flags]
Public enumAttributeTargets
{
Assembly=0x0001,
Module=0x0002,
Cass=0x0004,
Struct=0x0008
}
要用枚舉來增強那些表示值的集合的參數、屬性以及返回值的類型性。
要優先使用枚舉而不要使用靜態常量。(枚舉是一個包含一組靜態常量的結構)
不要把枚舉用於開發的集合(好比操做系統版本等)
不要提供爲了從此使用而保留的枚舉值。
避免顯示的暴漏只有一個值的枚舉。
不要把sentinel值包含在枚舉值中。
要爲簡單枚舉類型提供零值。(應該考慮把該值稱爲None之類的東西,若是這樣的值不適合用於某個特定的枚舉,那麼應該把該枚舉中最經常使用的默認值賦值爲0)
考慮用Int32做爲枚舉的基本實現類型。
要用複數名詞或者名詞短語來命名標記枚舉,用單數名詞或者名詞短語來命名簡單枚舉。
不要直接擴充System.Enum
要對標記枚舉使用System.FlagsAttribute,不要把該attribute用於簡單枚舉。
[Flags]
Public enumAttributeTargets
{
…..
}
要用2 的冪次方做爲標記枚舉的值,這樣就能夠經過按位或操做自由組合他們。
[Flags]
Public enumWatcherChangeTypes
{
Created=0x0002,
Deleted=0x0004,
Changed=0x0008,
Renamed=0x00010,
}
考慮爲經常使用的標記組合提供特殊的枚舉值。(位操做是一個高級概念,對應簡單任務來講不是必須的,FileAccess.ReadWrite就是這樣一個例子)
[Flags]
Public enumFileAccess
{
Read=1,
Write=2,
ReadWrite=Read|Write,
}
避免讓建立的標記枚舉包含某些無效的組合。
避免把零用做標記枚舉的值,除非該值表示「全部標記都被清除「,並且按下一條規範進行了適當的命名。(CLR規定任何值類型的默認值「全部的位都清零「)
要把標記枚舉的零值命名爲None,對其標枚舉來講,該值必須始終意味着「全部標記均被清除」。
[Flags]
Public enumBorderStyle
{
Fixed3D=0x1,
FixedSingle=0x2,
None=0x0
}
可是,該規則只適用於標記枚舉,對於非標記枚舉的狀況,避免使用0值其實是不利的,全部的枚舉類型一開始都爲零值。
常會發如今發現以後須要給一個枚舉添加值,若是新添加的值是一個已有API的返回值,那麼就存在潛在的應用程序兼容性問題。
考慮給枚舉添加值,儘管有那麼一點兼容性的風險。
若是有實際數據,代表給枚舉添加值會致使應用程序的不兼容,能夠考慮添加一個新的API來返回新老枚舉值,這樣就能確保仍然兼容現有的應用程序。
嵌套類型是一個定義在另外一個類型的做用域內的類型。另外一個類型被稱爲外層類型。嵌套類型可以訪問外層類型的全部成員。能夠訪問定義在外層類型的私有字段以及定義在外層類型的全部父類的受保護字段。
通常來講,儘可能少用嵌套類型,嵌套類型與外層類型緊密耦合,不適合將它們做爲通用類型。嵌套類型適合用來對它們的外層類型的實現細節建模。
要想讓一個類型可以訪問外層類型的成員時才使用嵌套類型。
不要用嵌套類型進行邏輯分組,應該用名字空間來達到此目的。
避免公開的暴漏嵌套類型,惟一的例外是若是隻須要在極少數的場景中聲明嵌套類型的變量,好比派生子類,或者其餘高級自定義場景中。
不要使用嵌套類型,若是該類型可能會被除了它的外層類型以外的類型引用。
不要使用嵌套類型,若是它們須要被客戶代碼實例化。
不要把嵌套類型定義爲接口的成員。
通常來講儘可能少用嵌套類型,並且應該避免將嵌套類型公開暴漏給外界。