周末空閑,選讀了一下一本很不錯的C#語言使用的書,特此記載下便于對項目代碼進行重構(gòu)和優(yōu)化時查看。 Standing On Shoulders of Giants,附上思維導(dǎo)圖,其中標記的顏色越深表示在實際中的實際意義越大。 名稱內(nèi)容和示例 提供API時盡量提供泛型接口Public interface IComparable{ int CompareTo(T other) } 泛型約束盡可能的嚴格并有效Public delegate T FactoryFunc(); Public static T Factory( FactoryFunc newT) where T:new() { T t = newt();} 通過運行時類型檢查具體化泛型算法比如根據(jù)不同的集合類型優(yōu)化相應(yīng)算法 使用泛型強制執(zhí)行編譯時類型推測Public static T ReadFromStream(XmlReader inputStream) { return (T)factory.Deserialize(inputStream) } 保證自定義泛型類支持可析構(gòu)的類型參數(shù)Public sealed class EngineDriver:IDisposable where T:Engine, new() { public void Dispose(){ var resource = driver as IDisposable; if(resource != null) resource.Dispose(); } } 通過委托在類型參數(shù)上定義方法約束Public static T Add(T left, T right, Func addFunc){ return addFunc(right, left); } 不要在基類和接口上創(chuàng)建具體化的泛型類型盡可能使的基類和接口的適用范圍更加的廣闊 推薦使用泛型方法,除非類型參數(shù)是實例字段Public static T Max(T left, T right) { return Comparer.Default.Compare(left, right) 推薦使用泛型的Tuple作為輸出和引用參數(shù)當設(shè)置方法的返回值,或者在需要使用ref參數(shù)的情形時,使用Tuple元組可以使代碼更清晰,當然如果參數(shù)比較復(fù)雜,還是選擇建立對應(yīng)的DTO類型為宜 在泛型接口上增加對應(yīng)的傳統(tǒng)接口這個在大家基礎(chǔ)架構(gòu)時非常重要,每個方法均提供泛型版本和object版本,使得代碼有很強的兼容性。 Public static bool CheckEquality(object left, object right) { return left.Equals(right); } Public static bool CheckEquality(T left, T right) where T:IEquatable { return left.Equals(right); } 名稱內(nèi)容和示例 使用線程池代替創(chuàng)建線程經(jīng)過微軟的官方測試,由自己調(diào)度線程和使用線程池,在每10萬個計算消耗的平均時長比較中,前者所消耗時長為后者三倍,因而選用線程池作為默認多線程處理機制是合理的選擇 Private static double ThreadPoolThreads(int numThreads) { var start = new Stopwatch(); Using(var e = new AutoResetEvent(false)){ int workerThreads = numThreads; start.Start();//watch.ElapsedMilliseconds, watch.Restart(), watch.Stop();for(var I = 0; I { // to do If(Interlocked.Decrement(ref workThreads) == 0) { e.Set(); } }); }} 使用后臺工作者組件對象用于處理多線程通信現(xiàn)在已經(jīng)不再使用后臺Worker,而推薦使用Task任務(wù)模型替代它,其邏輯為 將lock作為優(yōu)先級最高的同步原語使用lock相當于使用了Monitor.Enter和Exit,不過要方便很多,使用的是臨界區(qū)的概念。Public int TotalNum { get{ lock(syncObj) return total; } set{ lock(syncObj) total++;} } Lock中方法體盡可能精簡在使用lock時,一定不要使用lock(this)和lock(typeof(MyType))的形式,這會造成很多的問題,必須保證鎖的對象不是公開無法被外部使用的,常見的對方法加鎖的形式有: 1.使用特性,[MethodImpl(MethodImplOptions.Synchronized)] 2.使用私有變量作為鎖變量 private object syncHandler = new object(); 此外還有一種復(fù)雜點的形式如下。 Private object syncHandle; Private object GetSyncHandle(){ InterLocked.CompareExchange(ref syncHandle, new object(), null); } 避免在臨界區(qū)中調(diào)用未知代碼比如不要在臨界區(qū)中使用事件,因為事件的處理方法由調(diào)用方注冊,是未知的,會造成相關(guān)的問題,一定要保證臨界區(qū)中方法的確定性 理解在WinForm和WPF中的跨線程調(diào)用 做過WinForm編程的親,一定遇到過一個InvalidOperationException,內(nèi)容為跨線程操作非法,訪問Control的線程不是創(chuàng)建線程,這其實是Winform、WPF等框架對UI的保護,避免多個不同線程修改UI值的情況。這種情況主要有一下三種方式來處理,最推薦的解決方案為第二種。 在Form的構(gòu)造方法中加入 Control.CheckForIllegalCrossThreadCalls =false; (不推薦) Private void UpdateControl(string msg) { Action act = (x)=>{this.controlA.Text = x; } if(controlA.InvokeRequired) { this.controlA.Invoke(act, msg); } else{ act(); } } 使用BackgroundWorker組件(elide) 名稱內(nèi)容和示例 為序列創(chuàng)建可組合的API, yield return xxxPublic static IEnumerable Square(IEnumerable nums) { foreach(var num in nums) yield return num * num; } 通過Action,Predicate,F(xiàn)unctions解耦迭代器Public static IEnumerable Filter(IEnumerable sequence, Predicate filterFunc) { if(filterFunc(int)) yield return item; } 根據(jù)請求生成序列[IEnumerable].TakeWhile(num => num 通過Function參數(shù)解耦Public static T Sum(IEnumerable sequence, T total, Func accumulator) { foreach(T item in sequence){ total = accumulator(total, item); return total; } } 創(chuàng)建清晰,最小化,完整的方法組即在提供方法時,盡可能的保證完備性(支持主要的類型) 推薦定義方法重載操作符還記得在學(xué)習(xí)C++時,很推薦重載操作符,不過在面向?qū)ο笳Z言的今天,使用可讀性更強的方法更合理 理解事件是如何增加對象運行時的耦合性public event EventHandler OnProgress; public void DoLotsOfStuff() { for (var i = 0; i 只聲明非虛事件對象在.NET中,事件提供了類似屬性的簡易語法,通過add,remove方法添加相關(guān)事件處理程序,其實event就是delegate的包裝器,這個特殊的委托便于應(yīng)用事件處理模型,同時提供線程安全性。由于事件的運行時耦合性,如果使用虛事件容易造成未知的錯誤, private EventHandler progressEvent; public event EventHandler OnProgress { [MethodImpl(MethodImplOptions.Synchronized)] add { progressEvent += value; } [MethodImpl(MethodImplOptions.Synchronized)] remove { progressEvent -= value; } } 通過異常報告方法契約錯誤當出現(xiàn)業(yè)務(wù)異常流程時,推薦拋出異常而不是使用TryXXX組合的方式,因為這樣代碼更加簡單易懂。當然在與業(yè)務(wù)無關(guān)的,如簡單數(shù)據(jù)轉(zhuǎn)換的場景下,使用TryXXX是很好的選擇 確定屬性的行為和數(shù)據(jù)一樣讓屬性盡可能的簡單,不要將復(fù)雜邏輯放在屬性,如果需要可以通過提供相應(yīng)方法的方式,使得代碼更加通俗易懂,且使得調(diào)用人堅信屬性的調(diào)用不會造成任何的性能影響 區(qū)分繼承和組合在適當?shù)膱鼍跋?,用組合代替繼承是常見的代碼設(shè)計模式,這樣可以減少類的污染,在選用策略模式的場景下,組合使用的非常的多,常見的形式如下: public interface IContract{ void SampleImplMethod(); } public class MyInnerClass:IContract{ public void SampleImplMethod (){ //elided }} public class MyOuterClass:IContract{ private IContract impl = new MyInnerClass(); public void SampleImplMethod (){ impl.SampleImplMethod(); }} 名稱內(nèi)容和示例 通過擴展方法擴展接口Public static bool LessThan(this T left, T right) where T : IComparable { return left.CompareTo(right) 通過擴展方法增強已經(jīng)構(gòu)建的類型這部分很容易理解,比如你使用系統(tǒng)提供的相關(guān)類,無法修改源碼(雖然已開源),這時為了代碼的便捷性和可讀性,使用擴展方法增強該類變得非常有效 推薦隱式類型的本地變量簡單方便 通過匿名類限制類的可見范圍使得代碼的封裝性更好,更加健壯 為外部的組件創(chuàng)建可組合的API要求提供的API具有更好的健壯性,功能相對完整并獨立,復(fù)用性更強,例如盡量不要使用可空類型作為接口參數(shù)等 避免修改綁定的變量這部分內(nèi)容涉及閉包,通過以下的例子可以很容易的理解 public void Test() { int index = 0; Func sequence = () => Generate(30, () => index ++); index = 20; foreach (var item in sequence()) { Console.WriteLine(item); } } private IEnumerable Generate(int num, Func act) { for (; num > 0; num–) { yield return act(); } } 在匿名類型上定義本地函數(shù)public void Test01() { var randomNumbers = new Random(); var sequence = (from x in Generate(100, () => randomNumbers.NextDouble() * 100) let y = randomNumbers.NextDouble() * 100 select new { x, y }).TakeWhile(point => point.x 不要重載擴展方法由于個人創(chuàng)建擴展方法的普遍性和完備性不強,重載此類方法容易降低程序的健壯性 名稱內(nèi)容和示例 理解查詢表達式如何映射到方法調(diào)用簡單來說,我們所寫的LINQ語句都會先轉(zhuǎn)化為對應(yīng)的擴展方法,然后再解析相關(guān)的表達式樹最后生成對應(yīng)語句。 var people = from e in employees where e.Age > 30 orderby e.LastName, e.FirstName, e.Age select e; var people = employees.Where(e=>e.Age > 30).OrderBy(e=>e.LastName).ThenBy(e=>e.FirstName).ThenBy(e=>e.Age); 推薦Lazy延遲加載查詢延遲加載表示數(shù)據(jù)到真正使用時再去獲取,這個概念不太容易理解,簡單來說,我們的獲得集合函數(shù)調(diào)用實際上只是生成相應(yīng)的查詢語句,但并未實際執(zhí)行,獲得任何對象,只有在我們對其經(jīng)行迭代等操作時,才真正的加載數(shù)據(jù)。這些概念其實都和委托緊密相關(guān),從邏輯上講就是加了一個新的層次,函數(shù)本身(可以說是其指針、地址)是一個層次,函數(shù)的實際調(diào)用又是一個層次,在javascript也有相似的概念,就比如FunctionA和FunctionA()的區(qū)別。 Private static IEnumerable Generate(int number, Func generator) { for(var i = 0; i 這個格式?jīng)]有,和Task何其相似,一個是異步返回值,一個是延遲的返回值,僅僅是一個方便理解的小思路哈。 推薦使用lambda表達式代替方法這兒的實際意思是指在使用LINQ時,由于每個查詢的局限性,不推薦在查詢中調(diào)用外部方法,而因盡可能通過LINQ自身來完成相應(yīng)工作,減少各個查詢間的干擾 避免在Func和Action中拋出異常這個也很好理解,由于Action等委托常用于集合操作中,而任何一個一場都會中斷整個集合的操作,給集合操作帶來了很大的不確定性,并且在并行運算時更加難以控制,因而在Action中把異常捕獲并處理掉更加的合理。相信大家在job中常常會遇到循環(huán)調(diào)用的場景,這是通過返回值將相關(guān)的異常信息帶回是更合理的處理方式,之后無論是記log還是給相關(guān)人發(fā)郵件都顯得非常的合理 區(qū)分預(yù)先執(zhí)行和延遲執(zhí)行在實際應(yīng)用時,將正常加載和延遲加載組合使用非常的常見 var method1 = MethodA(); var answer = DoSomething(()=>method1, ()=>MethodB(), ()=>MethodC()); 此外,想說的是,在項目中,比如大部分數(shù)據(jù)是正常加載,少部分數(shù)據(jù)使用延遲加載,而一些特殊的場景通過(比如緩存服務(wù)器)則使用預(yù)熱(預(yù)先加載)的方式,弄清這里面的邏輯會讓這部分的應(yīng)用更加得心應(yīng)手 避免捕獲昂貴的資源之前介紹了C#編譯器如何生成委托和變量是如何在一個閉包的內(nèi)部被捕獲的,下面是一個簡單的構(gòu)建閉包的例子 int counter = 0; IEnumerable numbers = Generate(30, ()=>counter++); 其實際生成的代碼如下: private class Closure { public int generatedCounter; public int generatorFunc(){ return generatedCounter ++; } } var c = new Closure(); c.generatedCounter = 0; IEnumerable sequence = Generate(30, new Func(c.generatorFunc)); 通過閉包的形式,我們可以發(fā)現(xiàn)其擴展了捕獲對象的生命周期,如果這個捕獲對象是一個昂貴的資源,比如說是個很大的文件流,那么就可能發(fā)生內(nèi)存泄露的情況。因而在委托中使用本地的資源,一定要非常的當心,比較合理的方式是,將你所需要的內(nèi)容緩存后釋放原始對象。 區(qū)別IEnumerable和IQueryable的數(shù)據(jù)源由于IQueryable數(shù)據(jù)源其實是對IEnumerable數(shù)據(jù)源的封裝和增強,簡答來說,IQueryable對象的相關(guān)數(shù)據(jù)處理操作的性能要遠高于IEnumerable對象,因而如果實際的返回值為IQueryable對象,那么不要經(jīng)行相關(guān)的轉(zhuǎn)化,當然也可以通過typeA as IQueryable來嘗試轉(zhuǎn)化,如果本來就是IQueryable對象則直接返回,反之對其進行封裝后返回 通過Single()和First()方法強行控制查詢的語義這個就是讓我們的查詢語句通過語義來指導(dǎo)查詢,盡早的拋出異常 var stus = (from p in Students where p.Score > 60 orderby p.ID select p).Skip(2).First(); 推薦存儲Expression替代Func這部分很有意思,當然理解難度也不小,畢竟Expression完全可以實現(xiàn)一個簡單的編譯器了,真心強大。我們所使用的LINQ完全是建立在其上的,這兒只做個很粗略的學(xué)習(xí),作為未來加強學(xué)習(xí)的引子,可以看到,Expression表達式樹是Func的抽象Expression> IsOdd = val % 2 == 1;Expression> IsLargeNumber = val => val > 99; InvocationExpression callLeft = Expression.Invoke(IsOdd, Expression.Constant(5));InvocationExpressioncallRight = Expression.Invoke(IsLargeNumber, Expression.Constant(5));BinaryExpressionCombined = Expression.MakeBinary(ExpressionType.Add, callLeft, callRight); Expression> typeCombined = Expression.Lamda>( Combined); Func compiled = typeCombined.Compile(); Bool result = compiled(); 名稱內(nèi)容和示例 最小化可空類型的可見性簡單來說,就是減少在公共方法API的輸入?yún)?shù)和輸出返回值中使用可空類型,因而這樣會加大方法的調(diào)用難度。當然在內(nèi)部方法和實體類(包括代碼生成的實體類)中使用還是非常方便有效的 給部分類和部分方法建立構(gòu)造器,設(shè)值器和事件處理器這個主題常出現(xiàn)在有代碼生成器出現(xiàn)的場景,比如說使用代碼生成工具生成DAO層,其中只包含最基礎(chǔ)的CRUD操作,當擴展時,我們?nèi)绻苯有薷念愇募?,那么當下一次?shù)據(jù)庫修改,再次生成代碼時就可能出現(xiàn)代碼覆蓋等錯誤,因而在這種情況下我們會考慮使用分布類(說實話分布方法,我自己也沒怎么用過,記得在以前做C++時用過類似external關(guān)鍵字引用外部方法的情形,形式上有點像)。這是需要注意的是,工具生成類和擴展類(一般來說類名相同,但文件名加上Ext并放入對應(yīng)層次文件夾中)的設(shè)計,需要仔細考慮默認構(gòu)造方法、屬性值設(shè)置器、事件處理器等類成員的構(gòu)建。 將數(shù)組參數(shù)限制為參數(shù)數(shù)組由于數(shù)組的不確定性,因而不推薦將數(shù)組作為參數(shù)(指的是不同類型的數(shù)據(jù)放入一個object[]中,使得方法的使用非常容易出錯,當然泛型的數(shù)據(jù)集合等除外),而推薦params的形式來傳遞相應(yīng)數(shù)據(jù),這樣API參數(shù)在不存在或者提供null值時也不會報錯。 Private static void Write(params object[] params) { foreach(object o in params) Console.WriteLine(o); } 避免在構(gòu)造器中調(diào)用虛方法這其實是個很有用的建議,尤其是在構(gòu)建集成關(guān)系復(fù)雜的基類及其派生類時,由于子類、父類構(gòu)造方法調(diào)用順序原因,很容造成初始化和賦值的錯誤,用一個簡單的例子來說明這個問題,借用書中的一句原話,”一個對象在其所有構(gòu)造器執(zhí)行完成前并沒有完整的被構(gòu)建” class A { protected A() { MethodA(); } protected virtual void MethodA(){ Console.WriteLine(“MethodA in A”); } } class B : A{ private readonly string msg = “set by initializer”; public B(string msg){ this.msg = msg; } protected override void MethodA(){ Console.WriteLine(msg); } } class Program{ static void Main(string[] args){ B b = new B(“Constructed in main”); } } 這兒的結(jié)果是”set by initializer”,首先調(diào)用B的構(gòu)造方法,由于msg是readonly賦值木有成功,然后調(diào)用父類無參構(gòu)造方法,實際調(diào)用子類MethodA有以上結(jié)果。這部分在實際中我也曾犯過相似的錯誤,需要非常小心。 對大對象考慮使用弱引用弱引用的概念接觸的相對較少,實際就是將直接引用轉(zhuǎn)化為間接引用 Var weakR = new WeakReference(largeObj); largeObj = null; 咋一看,感覺確實不太好明白,這兒的意圖是首先將大對象的引用(指針)放入一個包裝類型,成為弱引用,之后將直接引用對象釋放,這樣就形成弱引用,利于垃圾回收,其使用場景主要針對沒有提供IDispose接口的大對象。說實話,在實際中,我也沒有這樣使用過,之后嘗試后再給大家分享。 推薦對易變量和不可序列化的數(shù)據(jù)使用隱式屬性簡單來說,就是在非Serializable對象中推薦使用priavte set,可以保護數(shù)據(jù)安全并便于提供驗證等方法。當然在支持序列化時,public的set方法和默認無參的構(gòu)造函數(shù)都是必須的 謝謝大家的閱讀,希望自己早日成為一名合格的程序員! 少年辛苦終身事,莫向光陰惰寸功 參考文獻: [美]Bill, Wagner. More Effective C#[M]. 北京:人民郵電出版社, 2009. 相關(guān)文章 StackOverflow 這么大,它的架構(gòu)是怎么樣的? C# 泛型的協(xié)變和逆變 .NET 基礎(chǔ)拾遺(3): 字符串、集合和流 ASP.NET MVC隨想錄(3):創(chuàng)建自定義的Middleware中間件 C#基礎(chǔ)系列:反射筆記 C#基礎(chǔ)系列:委托實現(xiàn)簡單設(shè)計模式(1) C#基礎(chǔ)系列:多線程的常見用法詳解 C#基礎(chǔ)系列:序列化效率比拼 編寫更好的C#代碼 MEF實現(xiàn)設(shè)計上的“松耦合”(2) 編寫高效率的C#代碼,首發(fā)于文章 - 伯樂在線。 |
|