Effective C# Chapter1-Language Elements

來源:互聯網
上載者:User

標籤:style   blog   使用   strong   資料   2014   

《EffectiveC#》這本書講了一些關於C#語言的提示和經驗. 該系列文章是備忘錄和自己的一些見解.程式猿們最喜歡這類問題了,歡迎討論~


菜單

Item 1 使用屬性取代公用成員變數

Item 2 優先考慮readonly而不是const

Item 3 使用is/as取代轉換操作符來進行物件類型轉換

Item 4 使用ConditionalAttribute 取代 #if

Item 5 總是提供ToString()方法

Item 6 理解Value Types 和 Referance Types的差別

Item 7 Perfer Immutable Atomic Value Type

Item 8 確保0(對象預設值,default(T)) 是一個有效狀態(已定義狀態)

Item 9 理解比較關係的方法:ReferanceEquals(), static Equals(), instance Equals() 和 operator ==

Item 10 理解GetHashCode()裡的陷阱

Item 11 優先使用foreach

Item 1 使用屬性取代公用成員變數

這個是C++轉C#程式猿最開始糾結的地方。


1、假設直接是public DataMember ,這肯定不正確,不管是C++還是C#,都須要進行封裝。

特別的,在C#中,假設使用DataMember,會導致在跨Assembly使用時,假設該變數有所變化(比方預設值),全部用到該變數的Assembly都得重編譯.
這在多DLL的項目裡會變成一種災難.

2、Porperty 和 Indexer 是C#裡的概念和語言上的屬性類別型

那麼C++的Access Methods 為什麼也不要用了呢?由於Access Methods是C++裡的東東; 對成員的訪問控制,使用Property會得到一些C#提供的編程便利,比方能夠直接對一個Property應用某個[Attribute],而使用Access Methods則須要分別處理Get 和 Set, 實現起來更加繁瑣;
很多其它的語言特性也是基於"Property"的,這個語言屬性在反射相關的編碼活動中能得到類型上的差別,而Access Methods是編碼風格,C#不正確其在語言上直接給予支援。

3、效能沒區別

JIT為Property實現的是inline property accessor,所以效能和DataMember等價.

總結:

總是使用屬性來向外提供資料訪問的能力(ValueType可能會有特例,比方Vector3, Matrix44之類);
總是使用Indexer來向外提供資料的隊列或定位的能力
資料成員所有是private的
(或protected,個人看法);
Property和Indexer都不得有異常拋出;


Item 2 優先考慮readonly而不是const

1、C#語言裡有兩種版本號碼的常量:編譯期常量(const)和執行期常量(readonly)

const在編譯成IL的時候,會直接將常量解釋成字面值,而readonly則解釋成某種引用

2、const效率最高,可是有潛在風險

兩者都有價值,const無效能開銷,是最高效的,由於直接使用字面值常量來產生IL代碼。
關於潛在風險,考慮例如以下一種情況:
程式集的A版本號碼公布,裡面有個const 值為4,因為是DataMember(參考Item1 的理由1),使用該程式集的client全部用到的代碼都被編譯成4了.
數周后,程式集更新到B版本號碼,const的值改為5了. 不論什麼沒又一次編譯的相關程式集裡該const使用處的地方的值還是4.(被坑過的同學請舉手)

3、readonly有一定程度的靈活性,當然也有少量的效能開銷

readonly在編譯期和const在文法特性上一致,在執行期能夠避免const帶來的潛在風險--全部使用處都是記著指定程式集的指定變數,而不是直接寫成字面值.
自然,這樣的引用會帶來效能開銷,只是也就是個inline的開銷罷了.
個人覺得:readonly於const, 就像 property 於 data member
總結: 僅僅有特彆強調效能的場合才使用const,其它不論什麼場合使用readonly 使用const的常量必須確保在程式不斷更新版本號碼時也不會發生改變,否則使用readonly

Item3 使用is/as取代轉換操作符來進行物件類型轉換這個這個...就和C++裡使用 xxxcast取代強制轉換的條款類似。只是C#裡強制轉換失敗時會拋出異常,而不像C++那樣悄無聲息。我們知道物件導向的語言就不該直接轉,所以該條款沒啥可商議的。
總結: 總是使用as/is進行類型轉換

Item 4 使用ConditionalAttribute 取代 #if嗯,這是個C++程式猿沒見過的新奇玩意
[Conditional("DEBUG")]void fun(){        // do something in debug model}

1、這個函數能夠在不論什麼地方調用,在編譯Release的時候,這個函數和其調用代碼就像根本不存在一樣。這個文法特性從某種程度上降低了我們編寫須要條件編譯的代碼的工作量。 2、除了DEBUG,TRACE這樣的已經內建到IDE裡的條件編譯變數,它支援隨意條件編譯變數 3、能夠 [Conditional(A),Conditional(B)] ,等於 #if A || B, 假設要表達A and B, 須要預先定義#if A && B#define C#endif 4、僅僅支援函數
總結:看著用吧,事實上無法全然取代#if 。

Item 5 總是提供ToString()方法 總結:不用列理由了,親們,你們dump對象內容的代碼寫的還少嘛?在C#的世界裡,每一個類都應該有ToString,讓我們一起努力讓這樣的美好持續下去吧!一些須要支援特定format的類,須要實現IFormattable介面。

Item 6 理解Value Types 和 Referance Types的差別這是一個能夠長篇大論的條款,有太多的文章解說兩者的差別。在這裡我列一些相關的知識點吧 1、值傳遞和引用傳遞 2、C#沒有常引用,常量函數
3、裝箱(boxing) 和 拆箱(unboxing) 4、ValueTypes不支援實現繼承,但支援介面繼承,通過介面使用ValueType會導致boxing操作. 5、非常多C#預設實現都是基於object的,這些實現會導致ValueType的boxing操作.
Item 7 Perfer Immutable Atomic Value Type 1、Immutable特性不可變特性的原子級ValueType。嗯,老實說我不覺得ValueType須要是Immutable的,可是Immutable的特性還是非常實用的。strcut A{ public int x{get;set;}}這個ValueType就不是 immutable 的, 由於當一個對象持有該類執行個體時,比方 G.a = new A();我們能夠通過 G.a.x = 3 來改變 a 的內部狀態。 為了使 A 不可變,須要去掉全部的 set方法. 這個類就是Immutable類了--可是一個無法改變內部狀態的類有啥用?稍後會說明。
2、Atomic特性再考慮這樣一種情況:
strcut Address{public int ZipCode{get;set;}public string CityName{get;set;}}


這是一個Address類,可是它有個問題,就是我們從開放的介面層面同意使用者用錯:僅僅更改ZipCode而不更改CityName,這會導致郵編和城市對不上! 這個類就不是Atomic的。
為了維護對象內部資料的統一性,須要給出一些特定的原子級訪問介面,比方 Address 能夠提供一個特殊的改動函數來維持其Atomic特性。
struct Address{private int _zipCode;private string _cityName;public int ZipCode{get { return _zipCode; }}public string CityName{get { return _cityName; }}public Address(int zipCode, string cityName){_zipCode = zipCode;_cityName = cityName;}public void Modify(int zipCode, string cityName){_zipCode = zipCode;_cityName = cityName;}}


儘管我們不會在使用Address時出錯了,但這個新的Atomic Address還不是immutable的,由於我們能夠改動其內部狀態。那麼我們如今將它改成Immutable的:
public Address Modify(int zipCode, string cityName){return new Address(zipCode, cityName);}

能夠看到,我們僅僅改了一個函數,Modify不再更改對象內部的屬性,而是建立了一個新的Address執行個體返回出去了。那麼這個類就是具有 Immutable和Atomic屬性的ValueType。
在理解了Immutable 和 Atomic 的概念之後,我們能夠做一些擴充討論了: 1、Immutable Or Atomic 不一定非要是Value Typestring 是一個 Immutable Referance Type 所以用起來和built-in的ValueType 一樣,就像一個int。而Atomic就更無所謂了。 2、類假設持有ReferanceType 的DataMember,要維護其Immutable屬性,將很困難(還是能夠做到),主要是由於其引用傳遞的特性所導致。這也是為什麼推薦使用ValueType來實現Immutable特性的資料結構:Immutable對象不會改變資料內部狀態,而ValueType在賦值時是值傳遞,不會導致owner的內部狀態發生變化。 3、Immutable屬性用來做HashCode之類的事情很合適.其實GetHashCode預設使用Object的第一個DataMember的GetHashCode來作為整個對象的hash值。
Item 8 確保0(對象預設值,default(T)) 是一個有效狀態(已定義狀態)C#和C++在對象初始化的時候有些不同:不管是Debug還是Release,C#總是將各種變數初始化為0---valueType就是0, refType就是null. 總結: 1、代碼裡應該處理好這樣的事情 2、enum一定要包括0作為一個有效值--即使它是無意義的值,也代表了我們考慮過一個enum對象被預設的初始化為0時的情況。
Item 9 理解比較關係的方法:ReferanceEquals(), static Equals(), instance Equals() 和 operator ==
public static bool ReferanceEquals(object left,object right)public static bool Equals(object left,object right)public virtual bool Equals(object right)public static bool operator==(T left, T right)

so many 比較函數...剛從C++過來的人預計一下子就暈了吧。以下的4條分別解釋上述4種比較函數的特點和差別。 1、ReferanceQuals 用來比較兩個引用是否相等,程式猿從來不會自己改動這個函數在C#裡,有 具名的handler 和 不具名的 object 這樣的概念,就等同於變數 和 變數的值一樣。 這個函數就是檢查 handler 指向的引用是否一致。這個函數無法作用於ValueType或EnumType,由於僅僅是檢查 引用,對於值類型,調用此函數會導致boxing,所以封裝出來的是2個object了,肯定不同,enum也是一樣。 2、static bool Equals 是預設實現,預設調用left.Equals(right) ,程式猿從來不會自己改動這個函數這是一種預設機制,在比較兩者是否相等時,能夠通過 多態+System.Object是全部類型的基類來達到檢查隨意兩個object是否相等的目的。參考事實上現
public static bool Equals(object left,object right){if(left == right)return true;if(left==null || right == null)return false;return left.Equals(right);}

能夠看到,這個函數先調用==,再調用instance.Equals ,很的完備,是吧?這裡是有陷阱的。 這個函數我們無法重載,而它是使用object作為參數類型的,這就意味著,使用Enum或者ValueType時,會有boxing操作,這會帶來效能開銷 3、對於ValueType,這個函數總是要重載,對於RefType,除非要更改其預設行為,否則不重載,RefType的預設行為是僅僅比較引用。假設不ValueType的Equals,工作的也對,可是因為其boxing操作,為了讓隨意的valueType的隨意DataMember能正確的得到比較,C#使用了反射,這比boxing要慢1000000倍!重載的意義在於排除不必要的反射操作:能夠直接推斷下right的類型是不是我們要比較的。而對於RefType,除非我們要定製其比較規則,否則都能夠依賴C#自己的預設實現,這裡沒有boxing,也沒有reflection. RefType僅僅推斷是否引用相等,不推斷內容是否相等4、operator==(T,T) 這個是為消除boxing和cast準備的,絕大多數情況下,僅僅有ValueType才會明白重載該操作符。RefType應該總是使用Equals來進行比較。RefType的內容比較,最後都須要到ValueType上進行比較。我們能夠調用ValueType的Instance.Equals(有效能開銷,最少也有boxing),也能夠調用其 operator==() , 這個是能夠直接給出特定類型的比較的,直接排除了各種boxing 和 reflection的可能。 5、RefType是否重載3和4,須要看詳細場合是否關心ref的值相等問題,並且大部分情況下,僅僅須要實現instance.Equals,4僅僅在有文法糖需求的場合實現。
Item 10 理解GetHashCode()裡的陷阱這個條目在理解Immutable的概念後,就非常easy理解了:假設作為hash的dataMember都能夠變來變去,那麼就得不到一個穩定的結果了。再一個就是,對於一個對象,假設使用非readonly的資料成員作為其hash的計算基礎,在計算完畢、對象被儲存到某個基於hash的容器內,他的hash成員變數又發生變化了的話,他就再也無法通過hash值在容器中被檢索出來了。C#不禁止你這麼做,所以風險也須要自己去承擔。
總結: 1、ValueType 的 GetHashCode()假設要工作正常,必須讓自己的第一個資料成員是readonly的immutable類型對象,否則結果【可能】不對--假設你能冒這個險。 2、假設須要自己寫HashCode的返回值,大部分情況下,使用全部immutable成員的GetHashCode()的值進行xor,該結果值作為本類型的hash值。
Item 11 優先使用foreach 1、編譯器會對foreach自己主動做出最優的代碼翻譯比方 Array類型的資料結構,foreach 等價於 for(int i = 0 ....)而對於關聯容器,則使用 using( var e = container.GetEnumartor()){ ...} 這樣的結構。使用者無需關心實現細節。 2、用中間變數記著count的結構也無法和foreach比效能由於foreach在IL層面做過最佳化了,比包括中間計算的手工代碼要更快。
總結:用foreach來實現迴圈吧! (個人經驗,因為Unity3D使用的Mono.dll的BUG,必須手工實現 while(e.MoveNext() ){...} 的結構,避免無意義的GC Alloc)
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.