《Effective C#》快速筆記(一)- C# 語言習慣

目錄
- 一、使用屬性而不是可訪問的數據成員
- 二、使用運行時常量(readonly)而不是編譯時常量(const)
- 三、推薦使用 is 或 as 操作符而不是強制類型轉換
- 四、使用 Conditional 特性而不是 #if 條件編譯
- 五、為類型提供 ToString() 方法
- 六、理解幾個等同性判斷之間的關系
- 七、理解 GetHashCode() 的陷阱
- 八、推薦使用查詢語法而不是循環
- 九、避免在 API 中使用轉換操作符
- 十、使用可選參數減少方法重載的數量
- 十一、理解短小方法的優勢
一、使用屬性而不是可訪問的數據成員
二、使用運行時常量(readonly)而不是編譯時常量(const)
1. C# 有兩種類型的常量:編譯時常量和運行時常量。
2.盡量使用運行時常量,而不是編譯時常量。
/// <summary> /// 編譯時常量 /// </summary> public const int Num = 100; /// <summary> /// 運行時常量 /// </summary> public static readonly int Year = 2017;
3.編譯時常量只能用於數字和字符串,運行時常量也是一種常量,因為在構造函數執行后它不能被再次修改。
4.const 比 readonly 效率高,但靈活性低。
三、推薦使用 is 或 as 操作符而不是強制類型轉換
1.as 比強轉更加高效、安全。
2.as 操作符不能配合值類型使用,因為值類型永遠不可能為 null。
四、使用 Conditional 特性而不是 #if 條件編譯
public static void Test() { string msg = null; #if DEBUG msg = "Hi"; #endif Console.WriteLine(msg); }

假如你是將這塊代碼在 Release 版本中執行的話,就會輸出空行。出現 Bug 的原因是我們把程序中的主要邏輯代碼和條件編譯代碼混在一塊了。這會讓我們很難察覺不同版本間的差異,導致錯誤的行為發生。
五、為類型提供 ToString() 方法
1.應該為類型提供一個合適的 ToString() 版本,否則使用者會根據類的一些屬性來自行構造並用於顯示。
2.object 默認提供的 ToString() 方法會返回類型的完整名稱,意義不大。如:System.Drawing.Rect。
3.重寫所有類型的 ToString(),可以簡單明了的顯示對象的摘要信息。
六、理解幾個等同性判斷之間的關系
1.系統提供 4 種函數判斷兩個對象是否“相等”。

2.對於前兩種方法,我們永遠不要重新定義,我們通常要重寫 Equals 方法。
3.重寫 Equals 的類型也要實現 IEquatable<T>,如果是結構體的話需要實現 IStructuralEquatable。
4.引用同一個 DataRow,會認為相等,如果想比較內容的話,而不是引用地址,那么就應該重寫 Equals() 實例方法。
5.Equals() 實例方法的重寫原則:對於所有的值類型,都應該重寫 Equals() 方法,對於引用類型,如果不能滿足需要時才去重寫該方法。重寫該方法的同時也需要重寫 GetHashCode() 方法。
6.operator == ():只要創建的是值類型,都必須重新定義 operator == (),因為系統默認是通過反射來比較兩個值是否相等,效率過低。
七、理解 GetHashCode() 的陷阱
1.對於我們實現的大多數類型來說,避免實現 GetHashCode()。
2.GetHashCode() 的重載版本必須遵循以下三條原則:
(1)如果兩個對象相等(由 operator == 定義),那么它們必須生成相同的散列碼。
(2)對於任何一個對象 A,A.GetHashCode() 必須保持不變。
(3)對於所有的輸入,散列函數應該在所有整數中按照隨機分布生成散列碼。
八、推薦使用查詢語法而不是循環
示例:
//1.使用循環 var foo = new int[100]; for (int i = 0; i < 100; i++) { foo[i] = i * i; } //使用查詢語法 var foo2 = (from n in Enumerable.Range(0, 100) select n * n).ToArray();
1.有些方法語法沒有對應的查詢語法,如 Take、TaskWhile、Skip、SkipWhile、Min、Max 等,就需要使用方法語法。
九、避免在 API 中使用轉換操作符
十、使用可選參數減少方法重載的數量
1.對於程序集的第一次發布,可以隨意使用可選參數和命名參數。而在進行后續發布時,必須為額外的參數創建重載。這樣才能保證現在的程序仍能正常運行。此外,在任何的后續發布中,都要避免修改參數的名稱,因為參數名稱已經成為公有接口的一部分。
十一、理解短小方法的優勢
1.我們最好盡可能地編寫出最清晰的代碼,將優化工作交給 JIT 完成。一個常見的錯誤優化是,我們將大量的邏輯放在一個函數中,以為這樣可以減少額外的方法調用開銷。
public string Test(bool isTrue) { var sb = new StringBuilder(); if (isTrue) { sb.AppendLine("A"); sb.AppendLine("B"); sb.AppendLine("C"); } else { sb.AppendLine("E"); sb.AppendLine("F"); sb.AppendLine("G"); } return sb.ToString(); }
在第一次調用 Test 方法時, if-else 的兩個分支都被 JIT 編譯,而實際上只需要編譯其中一個,修改后:
public string Test2(bool isTrue) { var sb = new StringBuilder(); if (isTrue) { return Method1(); } else { return Method2(); } }
現在進行了方法拆分,這兩個方法就可以根據需要進行 JIT 編譯,而不必第一次進行全部編譯。
2.可以將 if-else 分支中有超過幾十條的語句,或者某個分支專門用來處理程序發生的錯誤,或者 switch 語句中的每個 case 中的代碼進行選擇性的提取。
3.短小精悍的方法(一般包含較少的局部變量)會讓 JIT 更容易地進行寄存器選擇工作,即選擇哪些局部變量放在寄存器中,而不是棧上。
4.盡量編寫短小精悍的方法。
本系列
《Effective C#》快速筆記(一)- C# 語言習慣
《Effective C#》快速筆記(二)- .NET 資源托管
《Effective C#》快速筆記(三)- 使用 C# 表達設計
《Effective C#》快速筆記(五) - C# 中的動態編程
《Effective C#》快速筆記(六) - C# 高效編程要點補充
【博主】反骨仔
【原文】http://www.cnblogs.com/liqingwen/p/6754401.html
【參考】《Effective C#》
