肥兔讀書筆記之Effective C#(第2版) 第一章
Effective C#(第2版)中文名稱為: C#高效編程 改進(jìn)C#代碼的50個(gè)行之有效的辦法(第2版)
這本書的中文名字起的很蛋疼,其它Effective系列的書名都是Effective XXX,在網(wǎng)上商城輸入Effective就能全找到,唯獨(dú)這本死活找不到,后來偶然機(jī)會(huì)才知到原來中文名稱叫做C#高效編程 改進(jìn)C#代碼的50個(gè)行之有效的辦法,真是蛋疼至極。
第一章 C#語言習(xí)慣
條目1 使用屬性而不是可訪問的數(shù)據(jù)成員
條目2 用運(yùn)行時(shí)常量(readonly)而不是編譯期常量(const)
條目3 推薦使用 is 或 as 操作符而不是強(qiáng)制類型轉(zhuǎn)換
條目4 使用 Conditional 特性而不是#if 條件編譯
條目5 為類型提供 ToString() 方法
條目6 理解幾個(gè)等同性判斷之間的關(guān)系
條目7 理解 GetHashCode() 的陷阱
條目8 推薦使用查詢語法而不是循環(huán)
條目9 避免在API中使用轉(zhuǎn)換操作符
條目10 使用可選參數(shù)減少方法重載的數(shù)量
條目11 理解短小方法的優(yōu)勢
小結(jié)
條目1 使用屬性而不是可訪問的數(shù)據(jù)成員
關(guān)于這個(gè)概念是程序員都知道,知道的原因更多是因?yàn)榫幊逃脤傩砸呀?jīng)成為了一種習(xí)慣,你寫代碼若不用屬性會(huì)遭人鄙視,屬性能更好的進(jìn)行封裝、更好的進(jìn)行權(quán)限訪問控制等,然鮮為人知的是雖然屬性和數(shù)據(jù)成員在源代碼層次上完全兼容,但在二進(jìn)制層面上卻大相徑庭。這也就意味著,若將某個(gè)公有的數(shù)據(jù)成員改成了與之等同的公有屬性,那么就必須重新編譯所有用到了該公有數(shù)據(jù)成員的代碼,否則運(yùn)行時(shí)會(huì)拋出異常。
如下所示,若Customer類被其它程序或類進(jìn)行了引用,當(dāng)把Name從變量改為屬性的時(shí)候必須重新編譯所有引用了Customer類的代碼,否則運(yùn)行時(shí)會(huì)發(fā)生錯(cuò)誤,雖然我們看上去沒任何區(qū)別,但將公有變量改為屬性破壞了二進(jìn)制兼容性,從而造成更新單一程序集變得很困難,認(rèn)識(shí)這點(diǎn)沒啥太大的實(shí)際意義,但是可以讓我們更加堅(jiān)定使用屬性的信念。
public class Customer { /// <summary> /// 這里聲明為公有的變量 /// </summary> public string Name; } public class Customer { /// <summary> /// 這里聲明為公有的屬性 /// </summary> public string Name { get; set; } }
條目2 用運(yùn)行時(shí)常量(readonly)而不是編譯期常量(const)
C#有兩種類型的常量:編譯期常量和運(yùn)行時(shí)常量。二者有著截然不同的行為,使用不當(dāng)可能會(huì)帶來很嚴(yán)重的的后果,而使用者還茫然不知所措。
編譯期常量使用const關(guān)鍵字聲明,運(yùn)行時(shí)常量使用readonly關(guān)鍵字聲明
編譯期常量可以聲明在可以聲明在類或者方法中,運(yùn)行時(shí)常量只能聲明在類中不能聲明在方法中
/// <summary> /// 編譯時(shí)常量 /// </summary> public const int ConstAge = 23; /// <summary> /// 運(yùn)行時(shí)常量 /// </summary> public static readonly int ReadonlyAge = 23;
編譯期常量與運(yùn)行時(shí)常量的行為不同之處在于對他們的訪問方式不同。編譯期常量的值是在目標(biāo)代碼中進(jìn)行替換的。如
if (ReadonlyAge == ConstAge)
將會(huì)與如下代碼編譯成同樣的IL
if (ReadonlyAge == 23)
這種差異性會(huì)導(dǎo)致的后果就是當(dāng)引用了編譯時(shí)常量const關(guān)鍵字定義的常量時(shí),若定義常量的程序集發(fā)生了變化,而引用的程序集沒有重新編譯的,這樣就造成莫名其妙的錯(cuò)誤。如:程序集 Utility中定義了一個(gè)const字段和一個(gè)readonly字段:
namespace Utility { public class DemoClass { /// <summary> /// 編譯時(shí)常量 /// </summary> public const int ConstNum = 3; public static readonly int ReadonlyNum = 3; } }
在程序集ConsoleApplication1引用了Utility程序集,并進(jìn)行如下調(diào)用:
static void Main(string[] args) { Console.WriteLine("ConstNum:" + Utility.DemoClass.ConstNum); Console.WriteLine("ReadonlyNum:" + Utility.DemoClass.ReadonlyNum); }
輸出結(jié)果為:
ConstNum:3
ReadonlyNum:3
修改Utility程序集如下:
namespace Utility { public class DemoClass { /// <summary> /// 編譯時(shí)常量 /// </summary> public const int ConstNum = 10; public static readonly int ReadonlyNum = 10; } }
更新ConsoleApplication1中對Utility程序集的引用,并沒有重新編譯ConsoleApplication1程序集,輸出如下:
ConstNum:3 ReadonlyNum:10
沒有如我們預(yù)料的那樣輸出為
ConstNum:10 ReadonlyNum:10
編譯時(shí)常量只能定義為基本類型:整數(shù)、浮點(diǎn)數(shù)、枚舉和字符串,其它類型即使為值類型也無法定義為常量const如:
/// <summary> /// 這樣定義是不行滴,編譯都通不過 /// </summary> public const DateTime Now = DateTime.Now;
總結(jié):除非數(shù)據(jù)絕對不可能發(fā)生改變,否則不要定義public類型的const常量,若需要的話都定義成private類型的,實(shí)在要定義常量時(shí)請使用static readonly代替,一來readonly支持的類型更多也更靈活,二來const除了在性能上有那么丁點(diǎn)優(yōu)勢外其他的和static readonly沒有任何區(qū)別
條目3 推薦使用 is 或 as 操作符而不是強(qiáng)制類型轉(zhuǎn)換
需要使用類型轉(zhuǎn)換的地方,盡量使用as操作符,強(qiáng)制類型轉(zhuǎn)換可能會(huì)帶來意向不到的負(fù)面影響,因?yàn)橄鄬τ趶?qiáng)制類型轉(zhuǎn)換來說,as更加安全,也更加高效,使用as進(jìn)行轉(zhuǎn)換時(shí)不會(huì)拋出異常,若轉(zhuǎn)換失敗后會(huì)返回NULL。
條目4 使用 Conditional 特性而不是#if 條件編譯
這點(diǎn)感覺實(shí)際用處不大,畢竟實(shí)際中用#if的時(shí)候都不多,沒必要花費(fèi)那個(gè)精力去研究啥Conditional ,略過
條目5 為類型提供 ToString() 方法
同上,這點(diǎn)感覺實(shí)際用途不大,為每個(gè)實(shí)體類實(shí)現(xiàn)ToString()方法,有些畫蛇添足的感覺,就為了觀察每個(gè)類的屬性信息,就來個(gè)ToString()而且還要關(guān)心到底有沒有把全部屬性都加進(jìn)去了,若修改了屬性啥的咋辦,而且代碼越多出錯(cuò)概率也越大哈,除非閑的蛋疼否則不去干這費(fèi)力不討好的事情
條目6 理解幾個(gè)等同性判斷之間的關(guān)系
實(shí)際用途不大,主要介紹了幾種相等判斷的方法,更多的篇幅用來介紹Equals方法并推薦去重寫所有值類型的Equals方法,本人實(shí)在不敢茍同可能是咱的理解層次太低吧,而且我們又有多少實(shí)際定義值類型的機(jī)會(huì)呢
條目7 理解 GetHashCode() 的陷阱
關(guān)于這點(diǎn)咋說呢,俺基本上還木有使用過GetHashCode()做過啥實(shí)際有意義的事情(無意識(shí)中用到的不算),所以陷阱之類的對我來說不存在哈哈,略過,以后用到的時(shí)候再來好好研究
條目8 推薦使用查詢語法而不是循環(huán)
這點(diǎn)主要是推薦大家在日常開發(fā)中使用linq語法代替繁雜的for、foreach、while循環(huán)(雖然有時(shí)感覺for循環(huán)也并不復(fù)雜,但顯然使用linq更簡潔),帶來的好處就是代碼更加簡潔,能夠更加清晰的表達(dá)你的意圖,可讀性、易用性大幅高,而且也更加容易使用并行計(jì)算充分利用多核CPU的優(yōu)勢,只需簡單加上.AsParallel,并行編程就是這么簡單
條目9 避免在API中使用轉(zhuǎn)換操作符
事實(shí)上就是在任何地方都鮮有使用轉(zhuǎn)換操符的應(yīng)用,更別提在API中使用了,至少我還木有遇到過,略過
條目10 使用可選參數(shù)減少方法重載的數(shù)量
.Net4.0開始支持參數(shù)默認(rèn)值了,應(yīng)用參數(shù)默認(rèn)值使參數(shù)變?yōu)榭蛇x參數(shù),我們終于可以從無盡的重載中解脫出來了,當(dāng)然可選參數(shù)也不是萬能的,同條目1一樣,修改了默認(rèn)的參數(shù)值后,相關(guān)引用的程序集、代碼都需要重新編譯才可以,不過相對于這個(gè)小小的不便,我還是要為可選參數(shù)喝彩。
以前我們要實(shí)現(xiàn)如下的調(diào)用
static void Main(string[] args) { SetUserName("LazyRabbit"); SetUserName("LazyRabbit", "cnblogs"); }
需要寫下面這樣的兩個(gè)重載才可以實(shí)現(xiàn)
public static void SetUserName(string last) { } public static void SetUserName(string last, string first) { }
來看看可選參數(shù)的魅力,我們只需要定義一個(gè)方法就ok了,實(shí)現(xiàn)一樣的效果
public static void SetUserName(string last, string first = "LazyRabbit") { }
條目11 理解短小方法的優(yōu)勢
我們平時(shí)編碼很多人都知道盡量不要讓一個(gè)方法代碼過多,代碼過多會(huì)造成可讀性變差同時(shí)意味著單個(gè)方法的職責(zé)變多,不利于擴(kuò)展、復(fù)用和修改,這里介紹的觀點(diǎn)不是討論這些,主要介紹了JIT在編譯階段的優(yōu)化,.Net運(yùn)行時(shí)將調(diào)用JIT編譯器來將C#編譯器生成的IL翻譯成機(jī)器碼,這個(gè)過程平攤在應(yīng)用程序的整個(gè)生命周期中,JIT剛開始不會(huì)完全編譯所有的IL,CLR會(huì)按照函數(shù)的粒度來逐一進(jìn)行JIT編譯,這樣就大幅降低了程序啟動(dòng)的代碼,沒有被調(diào)用的方法根本不會(huì)被JIT編譯,因此將那些不是很重要的邏輯分支分解成更多的小分支要比把所有邏輯放到一起形成大型復(fù)雜函數(shù)有著更好的性能。短小的方法讓JIT能更好的平攤編譯的代碼,提高程序運(yùn)行時(shí)的效率。如下代碼,第一次調(diào)用DemoMethod方法時(shí),if-else這兩個(gè)分支都會(huì)被JIT編譯,而實(shí)際上僅需要編譯其中一個(gè)分支。
public static void DemoMethod(bool condition) { if (condition) { //do Method1 thing } else { //do Method2 thing } }
而若改成如下形式的話,在第一次調(diào)用DemoMethod方法時(shí),僅會(huì)編譯Method1()或者M(jìn)ethod2()其中的一個(gè),這樣顯然更加高效
public static void DemoMethod(bool condition) { if (condition) { Method1(); } else { Method2(); } } public static void Method1() { //do Method1 thing } public static void Method2() { //do Method2 thing }
以上代碼有些牽強(qiáng),而實(shí)際上到底能帶來多大的提升呢,這個(gè)提升可能微乎其微,但可以肯定的是方法越復(fù)雜帶來的提升也就越大,同時(shí)方法越小也可以讓JIT更容易進(jìn)行寄存器選擇工作,可以讓局部變量存放在寄存器而不是棧上,更小的函數(shù)意味著更少的局部變量,更方便JIT對寄存器進(jìn)行優(yōu)化。
至此,第一章 C#語言習(xí)慣閱讀完畢,記錄于此以作備忘和回顧,有些觀點(diǎn)以前就已經(jīng)知道了通過閱讀加深了一些理解和認(rèn)識(shí),也有一些不知道的,感覺有用的就好好研讀一番,感覺實(shí)用價(jià)值不高的也一行行看完,權(quán)當(dāng)了解,沒準(zhǔn)哪天就用到了。
注:此文章屬懶惰的肥兔原創(chuàng),版權(quán)歸作者和博客園共有,歡迎轉(zhuǎn)載,但未經(jīng)作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接
若您覺得這篇文章還不錯(cuò)請點(diǎn)擊下右下角的推薦,有了您的支持才能激發(fā)作者更大的寫作熱情,非常感謝。
如有問題,可以通過lzrabbit@126.com聯(lián)系我。
浙公網(wǎng)安備 33010602011771號