一、無參屬性
對於字段,強烈建議將所有的字段都設為private。如果允許用戶或類型獲取或設置狀態信息,就公開一個針對該用途的方法。封裝了字段訪問的方法通常稱為訪問器(accessor)方法。訪問器方法可選擇對數據的合理性進行檢查,確保對象的狀態永遠不被破壞。如下代碼:
= (value <= ArgumentOutOfRangeException(, =CLR提供了屬性(Property)的機制,它緩解了第一個缺點所造成的的影響,同事完全消除了第二個缺點。如下面代碼:
{ { m_Name = value; } { (value <= ) ArgumentOutOfRangeException(, =
Employee emp = = = , emp.Name, emp.Age);每個屬性都有一個名稱和一個類型(類型不能為void).屬性不能重載。也就是說不能定義名稱相同,類型不同的屬性。 定義屬性時,取決屬性的定義,編譯器在最後的托管程序集中生成以下兩項或三項: *)代表屬性的get訪問器方法的一個方法。僅在屬性定義了get訪問器方法時生成。 *)代表屬性的set訪問器方法的一個方法。僅在屬性定義了set訪問器方法時生成。 *)托管程序集元數據中的一個屬性定義。這一項是肯定要生成的。 以前面的Employee類為例,編譯器將會生成4個方法定義。如圖: 或 編譯器在你指定的屬性名之前會附加get_或set_前綴,從而自動生成這些方法的名稱。C#內建了對屬性的支持。當編譯器發現代碼試圖獲取或設置一個屬性時,它實際會生成對上述某個方法的一個調用。 1. 自動實現屬性 如果只是為了封裝一個私有字段而創建一個屬性,C#還提供了一種更簡單的語法,稱為自動實現的屬性(Automatically implemented Property,AIP)。下面是Name屬性的一個例子:
String Name {;1)屬性可以是只讀或只寫的,字段訪問確總是可讀和可寫。如果定義一個屬性,最好同時為它提供get和set訪問器方法。 2)一個屬性方法可能拋出異常;字段訪問永遠不會拋出異常。 3)屬性不能作為out或ref參數傳給方法;字段卻可以。 4)屬性方法可能花費較長時間執行;字段的訪問總是立即完成的。 5)如果連續多次調用,屬性方法每次都可能返回一個不同的值;而字段每次調用都返回相同的值。 6)屬性方法可能造成明顯的side effect(指訪問屬性時,除了單純的設置或獲取屬性,還會造成對象狀態的改變);字段訪問永遠不會。 7)屬性方法可能需要額外的內存,或者返回一個不正確的引用,指向不屬於對象狀態一部分的某個東西,這樣一來,對返回對象的修改就作用不到原始對象身上了。相反,查詢字段返回的總是正確的引用,它指向的東西保證是原始對象狀態的一部分。 現在的開發人員對屬性的依賴有過之而無不及,經常有沒有必要都使用屬性,仔細看下上面的比較,你會發現在極少數的情況下,才有必要定義屬性。屬性的唯一好處就是提供了簡化的語法,和調用普通方法相比,屬性不僅不會提高代碼性能,還會妨礙對代碼的理解。建議就是讓開發人員老老實實的寫Getxxx和Setxxx方法,希望編譯器提供一種特殊的,簡化的,有別於字段訪問的語法,是開發人員知道他們實際上是在調用一個方法。 3.對象和集合初始化器 我們經常要構造一個對象,然後設置對象的一些公共屬性(或字段)。為了簡化這個常見的編程模式,C#語言支持一種特殊的對象初始化語法。比如:[()]代表"()"可要可不要。
Employee e = Employee[()] { Name = , Age = }對象初始化器語法真正的好處在於,它允許在表達式的上下文(相對於語句的上下文)中編碼,允許組合多個函數,進而增強了代碼的可讀性。於是,就可以這麼寫了:
s = Employee() {Name = , Age = }.ToString().ToUpper();4.匿名類型 利用C#的匿名類型,可以使用非常簡潔的語法來聲明一個不可變的元組類型。元組(Tuple)類型是含有一組屬性的類型,這些屬性通常以某種方式相互關聯。
o1 = { Name = , Year = , o1.Name, o1.Year);
第一行代碼創建了一個匿名類型,沒有在new 關鍵字後制定類型名稱,所以編譯器會為我自動創建一個類型名稱,而且不會告訴我這個名稱是什麼(這正是匿名類型一詞的由來),但編譯器是知道的。雖然我不知道變量o1聲明的是什麼類型,但可以利用C#的"隱式推斷類型局部變量"功能(var)。
編譯器支持用另外兩種語法聲明匿名類型中的屬性,它根據變量推斷出屬性名和類型:
String Name = = o2 = { Name, dt.Year };
在這個例子中,編譯器判斷第一個屬性名為Name。由於Name是一個局部變量的名稱,所以編譯器將屬性類型設為與局部變量相同的類型:String。對於第二個屬性,編譯器使用字段/屬性的名稱:Year。Year是DateTime類的一個Int32屬性,所以匿名類型中的Year屬性也是一個Int32。
如果編譯器看見你在源代碼中定義了多個匿名類型,而且這些類型具有相同的結構,那麼它只會創建一個匿名類型定義,但可以創建該類型的多個實例。相同的結構,指在這些匿名類型中,每個屬性都有相同的類型和名稱,而且這些屬性的指定順序相同。 匿名類型經常和LINQ技術配合使用。可用LINQ進行查詢,從而生成由一組對象構成的集合,這些對象都是相同的匿名類型。然後,可以對結果集中的對象進行處理。所有的這些都在一個方法中完成。 5.System.Tuple類型 在System命名空間,Microsoft定義了幾個泛型Tuple(元組)類型。它們全部從Object派生,區別只在於元數(泛型參數的個數)。 在計算機編程中,一個函數或運算的元數是指函數獲取的實參或操作數的個數。Tuple<T1> Tuple(T1 item1) { m_Item1 = item1 {
編程語言還支持所謂的有參屬性,它的get訪問器方法接收一個或多個屬性,set訪問器方法接收兩個或多個參數。 C#使用數組風格的語法來公開有參屬性(索引器)。換句話說,可將索引器看作C#開發人員重載[]操作符的一種方式。下面是一個實例BitArray類,它允許用數組風格的語法來索引由該類的一個實例維護的一組二進制位。
(numBits <= ArgumentOutOfRangeException( m_numBits = m_byteArray = Byte[(m_numBits + ) / Boolean ((bitPos < ) || (bitPos >= ArgumentOutOfRangeException(, + ((m_byteArray[bitPos / ] & ( << (bitPos % ))) != ((bitPos < ) || (bitPos >= ArgumentOutOfRangeException(, + m_byteArray[bitPos / ] =/ ] | ( << (bitPos % m_byteArray[bitPos / ] =/ ] & ~( << (bitPos %
BitArray ba = BitArray( (Int32 x = ; x < ; x++= (x % == (Int32 x = ; x < ; x++ + x + + (ba[x] ? :
CLR本身並不區分無參屬性和有參屬性。對CLR來說,每個屬性都只是類型中定義的一對方法和一些元數據。將this[...]作為表達一個索引器的語法,這純粹是C#團隊自己的選擇,正因如此,C#只允許在對象的實例上定義索引器, 對於簡單的get和set訪問器方法,JIT編譯器會將代碼內聯。這樣一來,使用屬性(而不使用字段)就沒有性能上的損失。 內聯是指將一個方法的代碼直接編譯到它的方法中。這樣能避免在運行時發出調用所產生的開銷,代價是編譯好的方法的額代碼會變得更大。 由於屬性訪問器方法通常只包含及少量代碼,所以對它們進行內聯,反而會使最終生成的本地代碼更小,執行更快。 JIT編譯器在調試代碼時不會內聯屬性,因為這會變得難以調試。
我們有時希望為get訪問器方法指定一種可訪問性,為set訪問器方法指定另一種可訪問性。如下:
{ { m_name =
定義一個屬性時,如果兩個訪問器方法需要具有不同的可訪問性,C#語法要求必須為屬性本身指定限制最不大的那一種可訪問性。然後,在兩個訪問器中,只能選擇一個來應用限制較大的那一種可訪問性。如前面例子中,屬性本身聲明為public,set訪問器方法聲明為protected(限制比public大)。
既然屬性本質是方法,而且C#和CLR允許方法是泛型的,但是C#不允許定義泛型屬性。這從概念上講不通。屬性本用來表示一項可供查詢的或設置的對象特征。從概念上講,屬性是不具有行為的。