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