肥兔讀書筆記之Effective C#(第2版) 第一章

來源:互聯網
上載者:User

Effective C#(第2版)中文名稱為: C#高效編程 改進C#代碼的50個行之有效辦法(第2版)

這本書的中文名字起的很蛋疼,其它Effective系列的書名都是Effective  XXX,在網上商城輸入Effective就能全找到,唯獨這本死活找不到,後來偶然機會才知到原來中文名稱叫做C#高效編程 改進C#代碼的50個行之有效辦法,真是蛋疼至極。

第一章 C#語言習慣

條目1 使用屬性而不是可訪問的資料成員
條目2 用運行時常量(readonly)而不是編譯期常量(const)
條目3 推薦使用 is 或 as 操作符而不是強制類型轉換
條目4 使用 Conditional 特性而不是#if 條件編譯
條目5 為類型提供 ToString() 方法
條目6 理解幾個等同性判斷之間的關係
條目7 理解 GetHashCode() 的陷阱
條目8 推薦使用查詢文法而不是迴圈
條目9 避免在API中使用轉換操作符
條目10 使用選擇性參數減少方法重載的數量
條目11 理解短小方法的優勢
小結

條目1 使用屬性而不是可訪問的資料成員

  關於這個概念是程式員都知道,知道的原因更多是因為編程用屬性已經成為了一種習慣,你寫代碼若不用屬性會遭人鄙視,屬效能更好的進行封裝、更好的進行許可權存取控制等,然鮮為人知的是雖然屬性和資料成員在原始碼層次上完全相容,但在二進位層面上卻大相徑庭。這也就意味著,若將某個公有的資料成員改成了與之等同的公有屬性,那麼就必須重新編譯所有用到了該公有資料成員的代碼,否則運行時會拋出異常。

  如下所示,若Customer類被其它程式或類進行了引用,當把Name從變數改為屬性的時候必須重新編譯所有引用了Customer類的代碼,否則運行時會發生錯誤,雖然我們看上去沒任何區別,但將公有變數改為屬性破壞了二進位相容性,從而造成更新單一程式集變得很困難,認識這點沒啥太大的實際意義,但是可以讓我們更加堅定使用屬性的信念。

public class Customer{    /// <summary>    /// 這裡聲明為公有的變數    /// </summary>    public string Name;}public class Customer{    /// <summary>    /// 這裡聲明為公有的屬性    /// </summary>    public string Name { get; set; }}
條目2 用運行時常量(readonly)而不是編譯期常量(const)

  C#有兩種類型的常量:編譯期常量運行時常量。二者有著截然不同的行為,使用不當可能會帶來很嚴重的的後果,而使用者還茫然不知所措。

編譯期常量使用const關鍵字聲明,運行時常量使用readonly關鍵字聲明
編譯期常量可以聲明在可以聲明在類或者方法中,運行時常量只能聲明在類中不能聲明在方法中

/// <summary>/// 編譯時間常量/// </summary>public const int ConstAge = 23;/// <summary>/// 運行時常量/// </summary>public static readonly int ReadonlyAge = 23;

 編譯期常量與運行時常量的行為不同之處在於對他們的訪問方式不同。編譯期常量的值是在目標代碼中進行替換的。如

if (ReadonlyAge == ConstAge) 

將會與如下代碼編譯成同樣的IL

if (ReadonlyAge == 23)

這種差異性會導致的後果就是當引用了編譯時間常量const關鍵字定義的常量時,若定義常量的程式集發生了變化,而引用的程式集沒有重新編譯的,這樣就造成莫名其妙的錯誤。如:程式集 Utility中定義了一個const欄位和一個readonly欄位:

namespace Utility{    public class DemoClass    {        /// <summary>        /// 編譯時間常量        /// </summary>        public const int ConstNum = 3;        public static readonly int ReadonlyNum = 3;    }}

在程式集ConsoleApplication1引用了Utility程式集,並進行如下調用:

static void Main(string[] args){    Console.WriteLine("ConstNum:" + Utility.DemoClass.ConstNum);    Console.WriteLine("ReadonlyNum:" + Utility.DemoClass.ReadonlyNum);          }

輸出結果為:

ConstNum:3ReadonlyNum:3

修改Utility程式集如下:

namespace Utility{    public class DemoClass    {        /// <summary>        /// 編譯時間常量        /// </summary>        public const int ConstNum = 10;        public static readonly int ReadonlyNum = 10;    }}

更新ConsoleApplication1中對Utility程式集的引用,並沒有重新編譯ConsoleApplication1程式集,輸出如下:

ConstNum:3ReadonlyNum:10

沒有如我們預料的那樣輸出為

ConstNum:10ReadonlyNum:10

編譯時間常量只能定義為基本類型:整數、浮點數、枚舉和字串,其它類型即使為實值型別也無法定義為常量const如:

/// <summary>/// 這樣定義是不行滴,編譯都通不過/// </summary>public const DateTime Now = DateTime.Now;

  總結:除非資料絕對不可能發生改變,否則不要定義public類型的const常量,若需要的話都定義成private類型的,實在要定義常量時請使用static readonly代替,一來readonly支援的類型更多也更靈活,二來const除了在效能上有那麼丁點優勢外其他的和static readonly沒有任何區別

條目3 推薦使用 is 或 as 操作符而不是強制類型轉換

  需要使用類型轉換的地方,盡量使用as操作符,強制類型轉換可能會帶來意向不到的負面影響,因為相對於強制類型轉換來說,as更加安全,也更加高效,使用as進行轉換時不會拋出異常,若轉換失敗後會返回NULL。

條目4 使用 Conditional 特性而不是#if 條件編譯

  這點感覺實際用處不大,畢竟實際中用#if的時候都不多,沒必要花費那個精力去研究啥Conditional ,略過

條目5 為類型提供 ToString() 方法

  同上,這點感覺實際用途不大,為每個實體類實現ToString()方法,有些畫蛇添足的感覺,就為了觀察每個類的屬性資訊,就來個ToString()而且還要關心到底有沒有把全部屬性都加進去了,若修改了屬性啥的咋辦,而且代碼越多出錯機率也越大哈,除非閑的蛋疼否則不去幹這費力不討好的事情

條目6 理解幾個等同性判斷之間的關係

  實際用途不大,主要介紹了幾種相等判斷的方法,更多的篇幅用來介紹Equals方法並推薦去重寫所有實值型別的Equals方法,本人實在不敢苟同可能是咱的理解層次太低吧,而且我們又有多少實際定義實值型別的機會呢

條目7 理解 GetHashCode() 的陷阱

  關於這點咋說呢,俺基本上還木有使用過GetHashCode()做過啥實際有意義的事情(無意識中用到的不算),所以陷阱之類的對我來說不存在哈哈,略過,以後用到的時候再來好好研究

條目8 推薦使用查詢文法而不是迴圈

  這點主要是推薦大家在日常開發中使用linq文法代替繁雜的for、foreach、while迴圈(雖然有時感覺for迴圈也並不複雜,但顯然使用linq更簡潔),帶來的好處就是代碼更加簡潔,能夠更加清晰的表達你的意圖,可讀性、易用性大幅高,而且也更加容易使用並行計算充分利用多核CPU的優勢,只需簡單加上.AsParallel,並行編程就是這麼簡單

條目9 避免在API中使用轉換操作符

  事實上就是在任何地方都鮮有使用轉換操符的應用,更別提在API中使用了,至少我還木有遇到過,略過

條目10 使用選擇性參數減少方法重載的數量

  .Net4.0開始支援參數預設值了,應用參數預設值使參數變為選擇性參數,我們終於可以從無盡的重載中解脫出來了,當然選擇性參數也不是萬能的,同條目1一樣,修改了預設的參數值後,相關引用的程式集、代碼都需要重新編譯才可以,不過相對於這個小小的不便,我還是要為選擇性參數喝彩。
以前我們要實現如下的調用

static void Main(string[] args){    SetUserName("LazyRabbit");    SetUserName("LazyRabbit", "cnblogs");}

需要寫下面這樣的兩個重載才可以實現

public static void SetUserName(string last){}public static void SetUserName(string last, string first){}

來看看選擇性參數的魅力,我們只需要定義一個方法就ok了,實現一樣的效果

public static void SetUserName(string last, string first = "LazyRabbit"){}
條目11 理解短小方法的優勢

  我們平時編碼很多人都知道盡量不要讓一個方法代碼過多,代碼過多會造成可讀性變差同時意味著單個方法的職責變多,不利於擴充、複用和修改,這裡介紹的觀點不是討論這些,主要介紹了JIT在編譯階段的最佳化,.Net運行時將調用JIT編譯器來將C#編譯器產生的IL翻譯成機器碼,這個過程平攤在應用程式的整個生命週期中,JIT剛開始不會完全編譯所有的IL,CLR會按照函數的粒度來逐一進行JIT編譯,這樣就大幅降低了程式啟動的代碼,沒有被調用的方法根本不會被JIT編譯,因此將那些不是很重要的邏輯分支分解成更多的小分支要比把所有邏輯放到一起形成大型複雜函數有著更好的效能。短小的方法讓JIT能更好的平攤編譯的代碼,提高程式運行時的效率。如下代碼,第一次調用DemoMethod方法時,if-else這兩個分支都會被JIT編譯,而實際上僅需要編譯其中一個分支。

public static void DemoMethod(bool condition){    if (condition)    {    //do Method1 thing    }    else    {    //do Method2 thing    }}

而若改成如下形式的話,在第一次調用DemoMethod方法時,僅會編譯Method1()或者Method2()其中的一個,這樣顯然更加高效

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}

以上代碼有些牽強,而實際上到底能帶來多大的提升呢,這個提升可能微乎其微,但可以肯定的是方法越複雜帶來的提升也就越大,同時方法越小也可以讓JIT更容易進行寄存器選擇工作,可以讓局部變數存放在寄存器而不是棧上,更小的函數意味著更少的局部變數,更方便JIT對寄存器進行最佳化。

至此,第一章 C#語言習慣閱讀完畢,記錄於此以作備忘和回顧,有些觀點以前就已經知道了通過閱讀加深了一些理解和認識,也有一些不知道的,感覺有用的就好好研讀一番,感覺實用價值不高的也一行行看完,權當瞭解,沒準哪天就用到了。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.