一、無(wú)參屬性
對(duì)于字段,強(qiáng)烈建議將所有的字段都設(shè)為PRivate。如果允許用戶(hù)或類(lèi)型獲取或設(shè)置狀態(tài)信息,就公開(kāi)一個(gè)針對(duì)該用途的方法。封裝了字段訪問(wèn)的方法通常稱(chēng)為訪問(wèn)器(accessor)方法。訪問(wèn)器方法可選擇對(duì)數(shù)據(jù)的合理性進(jìn)行檢查,確保對(duì)象的狀態(tài)永遠(yuǎn)不被破壞。如下代碼:
private sealed class Employee { private String m_Name; private Int32 m_Age; public String GetName(){ return m_Name; } public void SetName(String value){ m_Name = value; } public Int32 GetAge(){ return m_Age; } public void SetAge(Int32 value){ if (value <= 0) throw new ArgumentOutOfRangeException("value", "must be >0"); m_Age = value; } }想這樣的數(shù)據(jù)封裝有兩個(gè)缺點(diǎn)。第一:不得不實(shí)現(xiàn)額外的方法;第二、用戶(hù)必須調(diào)用方法。
CLR提供了屬性(Property)的機(jī)制,它緩解了第一個(gè)缺點(diǎn)所造成的的影響,同事完全消除了第二個(gè)缺點(diǎn)。如下面代碼: private sealed class Employee { private String m_Name; private Int32 m_Age; public String Name { get { return (m_Name); } set { m_Name = value; } // 關(guān)鍵字'value' 總是代表新值 } public Int32 Age { get { return (m_Age); } set { if (value <= 0) // 關(guān)鍵字'value' 總是代表新值 throw new ArgumentOutOfRangeException("value", "must be >0"); m_Age = value; } } }于是就可以這樣調(diào)用:
Employee emp = new Employee(); emp.Name = "Jeffrey Richter"; emp.Age = 45; Console.WriteLine("Employee info: Name = {0}, Age = {1}", emp.Name, emp.Age);可將屬性想象成智能字段,即背后有額外邏輯的字段。CLR支持靜態(tài)、實(shí)例、抽象和虛屬性。屬性可以使用任意可訪問(wèn)性修飾符修飾。
每個(gè)屬性都有一個(gè)名稱(chēng)和一個(gè)類(lèi)型(類(lèi)型不能為void).屬性不能重載。也就是說(shuō)不能定義名稱(chēng)相同,類(lèi)型不同的屬性。定義屬性時(shí),通常要同時(shí)指定get和set兩個(gè)方法。但是,可以省略set方法來(lái)定義一個(gè)只讀屬性,或者省略get方法定義一個(gè)只寫(xiě)屬性?! 《x屬性時(shí),取決屬性的定義,編譯器在最后的托管程序集中生成以下兩項(xiàng)或三項(xiàng): *)代表屬性的get訪問(wèn)器方法的一個(gè)方法。僅在屬性定義了get訪問(wèn)器方法時(shí)生成。 *)代表屬性的set訪問(wèn)器方法的一個(gè)方法。僅在屬性定義了set訪問(wèn)器方法時(shí)生成。 *)托管程序集元數(shù)據(jù)中的一個(gè)屬性定義。這一項(xiàng)是肯定要生成的?! ∫郧懊娴腅mployee類(lèi)為例,編譯器將會(huì)生成4個(gè)方法定義。如圖:
或
編譯器在你指定的屬性名之前會(huì)附加get_或set_前綴,從而自動(dòng)生成這些方法的名稱(chēng)。C#內(nèi)建了對(duì)屬性的支持。當(dāng)編譯器發(fā)現(xiàn)代碼試圖獲取或設(shè)置一個(gè)屬性時(shí),它實(shí)際會(huì)生成對(duì)上述某個(gè)方法的一個(gè)調(diào)用。 1. 自動(dòng)實(shí)現(xiàn)屬性 如果只是為了封裝一個(gè)私有字段而創(chuàng)建一個(gè)屬性,C#還提供了一種更簡(jiǎn)單的語(yǔ)法,稱(chēng)為自動(dòng)實(shí)現(xiàn)的屬性(Automatically implemented Property,Aip)。下面是Name屬性的一個(gè)例子: private sealed class Employee { //這是一個(gè)自動(dòng)實(shí)現(xiàn)屬性 public String Name {get;set;} }2.合理定義屬性
屬性和字段的比較:
1)屬性可以是只讀或只寫(xiě)的,字段訪問(wèn)確總是可讀和可寫(xiě)。如果定義一個(gè)屬性,最好同時(shí)為它提供get和set訪問(wèn)器方法。 2)一個(gè)屬性方法可能拋出異常;字段訪問(wèn)永遠(yuǎn)不會(huì)拋出異常。 3)屬性不能作為out或ref參數(shù)傳給方法;字段卻可以?! ?)屬性方法可能花費(fèi)較長(zhǎng)時(shí)間執(zhí)行;字段的訪問(wèn)總是立即完成的?! ?)如果連續(xù)多次調(diào)用,屬性方法每次都可能返回一個(gè)不同的值;而字段每次調(diào)用都返回相同的值。 6)屬性方法可能造成明顯的side effect(指訪問(wèn)屬性時(shí),除了單純的設(shè)置或獲取屬性,還會(huì)造成對(duì)象狀態(tài)的改變);字段訪問(wèn)永遠(yuǎn)不會(huì)。 7)屬性方法可能需要額外的內(nèi)存,或者返回一個(gè)不正確的引用,指向不屬于對(duì)象狀態(tài)一部分的某個(gè)東西,這樣一來(lái),對(duì)返回對(duì)象的修改就作用不到原始對(duì)象身上了。相反,查詢(xún)字段返回的總是正確的引用,它指向的東西保證是原始對(duì)象狀態(tài)的一部分?! ‖F(xiàn)在的開(kāi)發(fā)人員對(duì)屬性的依賴(lài)有過(guò)之而無(wú)不及,經(jīng)常有沒(méi)有必要都使用屬性,仔細(xì)看下上面的比較,你會(huì)發(fā)現(xiàn)在極少數(shù)的情況下,才有必要定義屬性。屬性的唯一好處就是提供了簡(jiǎn)化的語(yǔ)法,和調(diào)用普通方法相比,屬性不僅不會(huì)提高代碼性能,還會(huì)妨礙對(duì)代碼的理解。建議就是讓開(kāi)發(fā)人員老老實(shí)實(shí)的寫(xiě)Getxxx和Setxxx方法,希望編譯器提供一種特殊的,簡(jiǎn)化的,有別于字段訪問(wèn)的語(yǔ)法,是開(kāi)發(fā)人員知道他們實(shí)際上是在調(diào)用一個(gè)方法。 3.對(duì)象和集合初始化器 我們經(jīng)常要構(gòu)造一個(gè)對(duì)象,然后設(shè)置對(duì)象的一些公共屬性(或字段)。為了簡(jiǎn)化這個(gè)常見(jiàn)的編程模式,C#語(yǔ)言支持一種特殊的對(duì)象初始化語(yǔ)法。比如:[()]代表"()"可要可不要。Employee e = new Employee[()] { Name = "Jeff", Age = 45 } 對(duì)象初始化器語(yǔ)法真正的好處在于,它允許在表達(dá)式的上下文(相對(duì)于語(yǔ)句的上下文)中編碼,允許組合多個(gè)函數(shù),進(jìn)而增強(qiáng)了代碼的可讀性。于是,就可以這么寫(xiě)了:string s = new Employee() {Name = "Jeff", Age = 45}.ToString().ToUpper(); 4.匿名類(lèi)型 利用C#的匿名類(lèi)型,可以使用非常簡(jiǎn)潔的語(yǔ)法來(lái)聲明一個(gè)不可變的元組類(lèi)型。元組(Tuple)類(lèi)型是含有一組屬性的類(lèi)型,這些屬性通常以某種方式相互關(guān)聯(lián)。//定義一個(gè)類(lèi)型,后再它的一個(gè)實(shí)例,并初始化它的屬性var o1 = new { Name = "Jeff", Year = 1964 };Console.WriteLine("Name={0}, Year={1}", o1.Name, o1.Year);第一行代碼創(chuàng)建了一個(gè)匿名類(lèi)型,沒(méi)有在new 關(guān)鍵字后制定類(lèi)型名稱(chēng),所以編譯器會(huì)為我自動(dòng)創(chuàng)建一個(gè)類(lèi)型名稱(chēng),而且不會(huì)告訴我這個(gè)名稱(chēng)是什么(這正是匿名類(lèi)型一詞的由來(lái)),但編譯器是知道的。雖然我不知道變量o1聲明的是什么類(lèi)型,但可以利用C#的"隱式推斷類(lèi)型局部變量"功能(var)。
編譯器支持用另外兩種語(yǔ)法聲明匿名類(lèi)型中的屬性,它根據(jù)變量推斷出屬性名和類(lèi)型:
String Name = "Grant";DateTime dt = DateTime.Now;// 有兩個(gè)屬性的一個(gè)匿名類(lèi)型// 1. String Name 屬性設(shè)為"Grant"http:// 2. Int32 Year 屬性設(shè)為dt中的年份var o2 = new { Name, dt.Year };在這個(gè)例子中,編譯器判斷第一個(gè)屬性名為Name。由于Name是一個(gè)局部變量的名稱(chēng),所以編譯器將屬性類(lèi)型設(shè)為與局部變量相同的類(lèi)型:String。對(duì)于第二個(gè)屬性,編譯器使用字段/屬性的名稱(chēng):Year。Year是DateTime類(lèi)的一個(gè)Int32屬性,所以匿名類(lèi)型中的Year屬性也是一個(gè)Int32。
如果編譯器看見(jiàn)你在源代碼中定義了多個(gè)匿名類(lèi)型,而且這些類(lèi)型具有相同的結(jié)構(gòu),那么它只會(huì)創(chuàng)建一個(gè)匿名類(lèi)型定義,但可以創(chuàng)建該類(lèi)型的多個(gè)實(shí)例。相同的結(jié)構(gòu),指在這些匿名類(lèi)型中,每個(gè)屬性都有相同的類(lèi)型和名稱(chēng),而且這些屬性的指定順序相同。 匿名類(lèi)型經(jīng)常和LINQ技術(shù)配合使用。可用LINQ進(jìn)行查詢(xún),從而生成由一組對(duì)象構(gòu)成的集合,這些對(duì)象都是相同的匿名類(lèi)型。然后,可以對(duì)結(jié)果集中的對(duì)象進(jìn)行處理。所有的這些都在一個(gè)方法中完成?! ?.System.Tuple類(lèi)型 在System命名空間,Microsoft定義了幾個(gè)泛型Tuple(元組)類(lèi)型。它們?nèi)繌腛bject派生,區(qū)別只在于元數(shù)(泛型參數(shù)的個(gè)數(shù))?! ≡谟?jì)算機(jī)編程中,一個(gè)函數(shù)或運(yùn)算的元數(shù)是指函數(shù)獲取的實(shí)參或操作數(shù)的個(gè)數(shù)。//這是最簡(jiǎn)單的public class Tuple<T1> { private T1 m_item1; public Tuple(T1 item1) { m_Item1 = item1;} public item1 { get { retuen m_Item1; } }}和匿名類(lèi)型相似,一旦創(chuàng)建好了一個(gè)Tuple,他就不可變了(所有屬性都只讀)。Tuple類(lèi)還提供了CompareTo,Equals,GetHashCode和ToString方法,另外還提供了一個(gè)Size屬性。除此之外,所有Tuple類(lèi)型都實(shí)現(xiàn)了IstruralEquatable,IstructuralComparable和IComparable接口,所以可以比較兩個(gè)Tuple對(duì)象。
二、有參屬性 編程語(yǔ)言還支持所謂的有參屬性,它的get訪問(wèn)器方法接收一個(gè)或多個(gè)屬性,set訪問(wèn)器方法接收兩個(gè)或多個(gè)參數(shù)。C#語(yǔ)言把它們稱(chēng)為索引器。VB稱(chēng)為默認(rèn)屬性?! #使用數(shù)組風(fēng)格的語(yǔ)法來(lái)公開(kāi)有參屬性(索引器)。換句話說(shuō),可將索引器看作C#開(kāi)發(fā)人員重載[]操作符的一種方式。下面是一個(gè)實(shí)例BitArray類(lèi),它允許用數(shù)組風(fēng)格的語(yǔ)法來(lái)索引由該類(lèi)的一個(gè)實(shí)例維護(hù)的一組二進(jìn)制位。internal sealed class BitArray { // 容納了二進(jìn)制位的私有字節(jié)數(shù)組 private Byte[] m_byteArray; private Int32 m_numBits; // 下面的構(gòu)造器用于分配字節(jié)數(shù)組,并將所有位設(shè)為 0 public BitArray(Int32 numBits) { // 先驗(yàn)證實(shí)參 if (numBits <= 0) throw new ArgumentOutOfRangeException("numBits must be > 0"); // 保留位的個(gè)數(shù) m_numBits = numBits; // 為位數(shù)組分配字節(jié) m_byteArray = new Byte[(m_numBits + 7) / 8]; } // 下面是索引器(有參屬性) public Boolean this[Int32 bitPos] { // 下面是索引器的get訪問(wèn)器方法 get { // 先驗(yàn)證實(shí)參 if ((bitPos < 0) || (bitPos >= m_numBits)) throw new ArgumentOutOfRangeException("bitPos", "bitPos must be between 0 and " + m_numBits); // 返回指定索引處的位的狀態(tài) return ((m_byteArray[bitPos / 8] & (1 << (bitPos % 8))) != 0); } // 下面是索引器的set訪問(wèn)器方法 set { if ((bitPos < 0) || (bitPos >= m_numBits)) throw new ArgumentOutOfRangeException("bitPos", "bitPos must be between 0 and " + m_numBits); if (value) { // 將指定索引處的位設(shè)為true m_byteArray[bitPos / 8] = (Byte) (m_byteArray[bitPos / 8] | (1 << (bitPos % 8))); } else { // 將指定索引處的位設(shè)為false m_byteArray[bitPos / 8] = (Byte) (m_byteArray[bitPos / 8] & ~(1 << (bitPos % 8))); } } }}BitArray類(lèi)的調(diào)用也非常簡(jiǎn)單:
private static void BitArrayTest() { // 分配含有14個(gè)位的bitArray數(shù)組 BitArray ba = new BitArray(14); // 調(diào)用set訪問(wèn)器方法,將編號(hào)為偶數(shù)的所有為設(shè)為true for (Int32 x = 0; x < 14; x++) { ba[x] = (x % 2 == 0); } // 調(diào)用get訪問(wèn)器方法顯示所有為的狀態(tài) for (Int32 x = 0; x < 14; x++) { Console.WriteLine("Bit " + x + " is " + (ba[x] ? "On" : "Off")); } } CLR本身并不區(qū)分無(wú)參屬性和有參屬性。對(duì)CLR來(lái)說(shuō),每個(gè)屬性都只是類(lèi)型中定義的一對(duì)方法和一些元數(shù)據(jù)。將this[...]作為表達(dá)一個(gè)索引器的語(yǔ)法,這純粹是C#團(tuán)隊(duì)自己的選擇,正因如此,C#只允許在對(duì)象的實(shí)例上定義索引器,C#沒(méi)有提供定義靜態(tài)索引器屬性的語(yǔ)法,雖然CLR是支持靜態(tài)有參屬性的。三、調(diào)用屬性訪問(wèn)器方法時(shí)的性能
對(duì)于簡(jiǎn)單的get和set訪問(wèn)器方法,JIT編譯器會(huì)將代碼內(nèi)聯(lián)。這樣一來(lái),使用屬性(而不使用字段)就沒(méi)有性能上的損失?! ?nèi)聯(lián)是指將一個(gè)方法的代碼直接編譯到它的方法中。這樣能避免在運(yùn)行時(shí)發(fā)出調(diào)用所產(chǎn)生的開(kāi)銷(xiāo),代價(jià)是編譯好的方法的額代碼會(huì)變得更大?! ∮捎趯傩栽L問(wèn)器方法通常只包含及少量代碼,所以對(duì)它們進(jìn)行內(nèi)聯(lián),反而會(huì)使最終生成的本地代碼更小,執(zhí)行更快。 JIT編譯器在調(diào)試代碼時(shí)不會(huì)內(nèi)聯(lián)屬性,因?yàn)檫@會(huì)變得難以調(diào)試。四、屬性訪問(wèn)器的可訪問(wèn)性
我們有時(shí)希望為get訪問(wèn)器方法指定一種可訪問(wèn)性,為set訪問(wèn)器方法指定另一種可訪問(wèn)性。如下:
public class SomeType { private String m_name; public String Name { get { return m_name;} protected set { m_name = value;} }}定義一個(gè)屬性時(shí),如果兩個(gè)訪問(wèn)器方法需要具有不同的可訪問(wèn)性,C#語(yǔ)法要求必須為屬性本身指定限制最不大的那一種可訪問(wèn)性。然后,在兩個(gè)訪問(wèn)器中,只能選擇一個(gè)來(lái)應(yīng)用限制較大的那一種可訪問(wèn)性。如前面例子中,屬性本身聲明為public,set訪問(wèn)器方法聲明為protected(限制比public大)。
五、泛型屬性訪問(wèn)器方法
既然屬性本質(zhì)是方法,而且C#和CLR允許方法是泛型的,但是C#不允許定義泛型屬性。這從概念上講不通。屬性本用來(lái)表示一項(xiàng)可供查詢(xún)的或設(shè)置的對(duì)象特征。從概念上講,屬性是不具有行為的。
|
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注