關於C#程式優化的五十種方法

Endv發表於2015-01-19

關於C#程式優化的五十種方法

  
這篇文章主要介紹了C#程式優化的五十個需要注意的地方,使用c#開發的朋友可以看下
 

一、用屬性代替可訪問的欄位 

1、.NET資料繫結只支援資料繫結,使用屬性可以獲得資料繫結的好處; 
2、在屬性的get和set訪問器重可使用lock新增多執行緒的支援。   

二、readonly(執行時常量)和const(編譯時常量) 
1、const只可用於基元型別、列舉、字串,而readonly則可以是任何的型別; 
2、const在編譯時將替換成具體的常量,這樣如果在引用中同時使用了const和readonly兩種值,則對readonly的再次改變將會改變設計的初衷,這是需要重新編譯所更改的程式集,以重新引用新的常量值。 
3、const比readonly效率高,但失去了應用的靈活性。   

三、is與as 
1、兩者都是在執行時進行型別的轉換,as操作符只能使用在引用型別,而is可以使用值和引用型別; 
2、通常的做法是用is判斷型別,然後選擇使用as或強型別轉換操作符(用operater定義的轉換)有選擇地進行。   

四、ConditionalAttribute代替#if #endif條件編譯 
1、ConditionalAttribute只用於方法級,對其他的如型別、屬性等的新增都是無效的;而#if #endif則不受此限制; 
2、ConditionalAttribute可以新增多個編譯條件的或(OR)操作,而#if #endif則可以新增與(AND)[這裡可以完全定義為另一個單獨的符號]; 
3、ConditioanlAttribute定義可以放在一個單獨的方法中,使得程式更為靈活。   

五、提供ToString()方法 
1、可以更友好的方式提供使用者詳細的資訊; 
2、使用IFormatter.ToString()方法提供更靈活的定製,如果新增IFormatProvider 和ICustomFormatter介面則更有意義的定製訊息輸出。   

六、值和引用型別的區別 
1、值型別不支援多型,適合儲存應用程式操作的資料,而引用則支援多型,適用於定義應用程式的行為; 
2、對於陣列定義為值型別可以顯著提高程式的效能; 
3、值型別具有較少的堆記憶體碎片、記憶體垃圾和間接訪問時間,其在方法中的返回是以複製的方式進行,避免暴露內部結構到外界; 
4、值型別應用在如下的場景中:型別的職責主要是用於資料儲存;公共介面完全由一些資料成員存取屬性定義;永遠沒有子類;永遠沒有多型行為。   

七、值型別儘可能實現為常量性和原子性的型別 
1、使我們的程式碼更易於編寫和維護; 
2、初始化常量的三種策略:在構造中;工廠方法;構造一個可變的輔助類(如StringBuilder)。   

八、確保0為值得有效狀態 
1、值型別的預設狀態應為0; 
2、列舉型別的0不應為無效的狀態;在FlagsAttribute是應確保0值為有效地狀態;
3、在字串為為空時可以返回一個string.Empty的空字串。   

九、相等判斷的多種表示關係 
1、ReferenceEquals()判斷引用相等,需要兩個是引用同一個物件時方可返回true; 
2、靜態的Equals()方法先進行引用判斷,再進行值型別判斷的; 
3、對於引用型別的判斷可以在使用值語義時使用重寫Equals()方法; 
4、重寫Equals()方法時也應當重寫GetHashCode()方法,同時提供operater==()操作。   

十、理解GetHashCode()方法的缺陷 
1、GetHashCode()僅應用在基於雜湊的**定義鍵的雜湊值,如HashTable或Dictionary; 
2、GetHashCode()應當遵循相應的三條規則:兩個相等物件應當返回相同的雜湊碼;應當是一個例項不變式;雜湊函式應該在所有的整數中產生一個隨機的分佈。   

十一、優先使用foreach迴圈語句 
1、foreach可以消除編譯器對for迴圈對陣列邊界的檢查; 
2、foreach的迴圈變數是隻讀的,且存在一個顯式的轉換,在**物件的物件型別不正確時丟擲異常; 
3、foreach使用的**需要有:具備公有的GetEnumberator()方法;顯式實現了IEnumberable介面;實現了IEnumerator介面; 
4、foreach可以帶來資源管理的好處,因為如果編譯器可以確定IDisposable介面時,可以使用優化的try…finally塊;   

十二、預設欄位的初始化優於賦值語句 
1、欄位生命預設會將值型別初始化為0,引用型別初始化為null; 
2、對同一個物件進行多次初始化會降低程式碼的執行效率; 
3、將欄位的初始化放到構造器中有利於進行異常處理。   

十三、使用靜態構造器初始化靜態成員 
1、靜態構造器會在一個類的任何方法、變數或者屬性訪問之前執行; 
2、靜態欄位同樣會在靜態構造器之前執行,同時靜態構造器有利於異常處理。   

十四、利用構造器鏈(在.NET 4.0已經用可選引數解決了這個問題) 
1、用this將初始化工作交給另一個構造器,用base呼叫基類的構造器; 
2、型別例項的操作順序是:將所有的靜態欄位都設定為0;執行靜態欄位初始化器;執行基類的靜態構造器;執行當前型別的靜態構造器; 
將所有的例項欄位設定為0;執行例項欄位初始化器;執行合適的基類例項構造器;執行當前型別的例項構造器。   

十五、利用using和try/finally語句來清理資源 
在IDisposable介面的Dispose()方法中用GC.SuppressFinalize()可通知垃圾收集器不再執行終結操作。   

十六、儘量減少記憶體垃圾 
1、分配和銷燬一個堆上的物件都要花費額外的處理器時間; 
2、減少分配物件數量的技巧:經常使用的區域性變數提升為欄位;提供一個類,用於儲存Singleton物件來表達特定型別的常用例項。 
3、用StringBuilder進行復雜的字串操作。   

十七、儘量減少裝箱和拆箱 
1、關注一個型別到System.Object的隱式轉換,同時值型別不應該被替換為System.Object型別; 
2、使用介面而不是使用型別可以避免裝箱,即將值型別從介面實現,然後通過介面呼叫成員。   

十八、實現標準Dispose模式 
1、使用非記憶體資源,它必須有一個終結器,垃圾收集器在完成沒有終結其的記憶體物件後,會將實現了終結器物件的新增到終結佇列中,然後垃圾收集器會啟動一個新的執行緒來執行這些物件上的終結器,這種防禦性的變成方式是因為如果使用者忘記了呼叫Dispose()方法,垃圾回收器總是會呼叫終結器方法的,這樣可以避免出現非託管的記憶體資源不被釋放引起記憶體洩漏的問題; 
2、使用IDisposable.Dispose()方法需要做四個方面的工作:釋放所有的非託管資源;釋放所有的託管資源;設定一個狀態標記來表示是否已經執行了Dispose();呼叫GC.SuppressFinalize(this)取消物件的終結操作; 
3、為需要多型的型別新增一個受保護的虛方法Dispose(),派生類通過重寫這個方法來釋放自己的任務; 
4、在需要IDisoposable介面的型別中,即使我們不需要一個終結器也應該實現一個終結器。   

十九、定義並實現介面優於繼承型別 
1、不相關的型別可以共同實現一個共同的介面,而且實現介面比繼承更容易; 
2、介面比較穩定,他將一組功能封裝在一個介面中,作為其他型別的實現合同,而基類則可以隨著時間的推移進行擴充套件。   

二十、明辨介面實現和虛方法重寫 
1、在基類中實現一個介面時,派生類需要使用new來隱藏對基類方法的使用; 
2、可以將基類介面的方法申明為虛方法,然後再派生類中實現。   

二十一、使用委託表達回撥 
1、委託物件本身不提供任何異常捕獲,所以任何的多播委託呼叫都會結束整個呼叫鏈; 
2、通過顯示呼叫委託鏈上的每個委託目標可以避免多播委託僅返回最後一個委託的輸出。   

二十二、使用事件定義外部介面 
1、應當宣告為共有的事件,讓編譯器為我們建立add和renmove方法; 
2、使用System.ComponentModel.EventHandlerList容器來儲存各個事件處理器,在型別中包含大量事件時可以使用他來隱藏所有事件的複雜性。   

二十三、避免返回內部類物件的引用 
1、由於值型別物件的訪問會建立一個該物件的副本,所以定義一個值型別的的屬性完全不會改變型別物件內部的狀態; 
2、常量型別可以避免改變物件的狀態; 
3、定義介面將訪問限制在一個子集中從而最小化對物件內部狀態的破壞; 
4、定義一個包裝器物件來限制另一個物件的訪問; 
5、希望客戶程式碼更改內部資料元素時可以實現Observer模式,以使物件可以對更改進行校驗或相應。   

二十四、宣告式程式設計優於指令式程式設計 
可以避免在多個類似的手工編寫的演算法中犯錯誤的可能性,並提供清晰和可讀的程式碼。   

二十五、儘可能將型別實現為可序列化的型別 
1、型別表示的不是UI控制元件、視窗或者表單,都應使型別支援序列化; 
2、在新增了NonSerializedAttribute的反序列化的屬性時可以通過實現IDeserializationCallback的OnDeserialization()方法裝入預設值; 
3、在版本控制中可以使用ISerializable介面來進行靈活的控制,同時提供一個序列化的構造器來根據流中的資料初始化物件,在實現時還要求SerializationFormatter異常的許可; 
4、如果需要建立派生類則需要提供一個掛鉤方法供派生類使用。   

二十六、使用IComparable和IComparer介面實現排序關係 
1、IComparable介面用於為型別實現最自然的排序關係,過載四個比較操作符,可以提供一個過載版的CompareTo()方法,讓其接受具體型別作為引數; 
2、IComparer用於提供有別於IComparable的排序關係,或者為我們提供型別本身說沒有實現的排序關係。   

二十七、避免ICloneable介面 
1、對於值型別永遠不需要支援ICloneable介面,使用預設的賦值操作即可; 
2、對於可能需要支援ICloneable介面的基類,應該為其創造一個受保護的複製構造器,並應當避免支援IConeable介面。   

二十八、避免強制轉換操作符 
通過使用構造器來代替轉換操作符可以使轉換工作變得更清晰,由於在轉換後使用的臨時物件,容易導致一些詭異的BUG。   

二十九、只有當新版積累導致問題時才考慮使用new修飾符   

三十、儘可能實現CLS相容的程式集 
1、建立一個相容的程式集需要遵循兩條規則:程式集中所有公有和受保護成員所使用的引數和返回值型別都必須與CLS相容;任何與CLS不相容的公有和受保護成員都必須有一個與CLS相容的替代品; 
2、可以通過顯式實現介面來避開CLS相容型別檢查,及CLSCompliantAttribute不會檢查私有的成員的CLS相容性。   

三十一、儘可能實現短小簡潔的方法 
1、JIT編譯器以方法為單位進行編譯,沒有被呼叫的方法不會被JIT編譯; 
2、如果將較長的Switch中的Case語句的程式碼替換成一個一個的方法,則JIT編譯器所節省的時間將成倍增加;
3、短小精悍的方法並選擇較少的區域性變數可以獲得優化的暫存器使用; 
4、方法內的控制分支越少,JIT編譯器越容易將變數放入暫存器。   

三十二、儘可能實現小尺寸、高內聚的程式集 
1、將所有的公有類以及共用的基類放到一些程式集中,把為公有類提供功能的工具類也放入同樣的程式集中,把相關的公有介面打包到他們自己的程式集中,最後處理遍佈應用程式中水平位置的類; 
2、原則上建立兩種元件:一種為小而聚合、具有某項特定功能的程式集,另一種為大而寬、包含共用功能的程式集。   

三十三、限制型別的可見性 
1、使用介面來暴露型別的功能,可以使我們更方便地建立內部類,同時又不會限制他們在程式集外的可用性; 
2、向外暴露的公有型別越少,未來擴充套件和更改實現所擁有的選擇就越多。   

三十四、建立大粒度的Web API 
這是在機器之間的交易的頻率和載荷都降到最低,將大的操作和細粒度的執行放到伺服器執行。   

三十五、重寫優於事件處理器 
1、一個事件處理器丟擲異常,則事件鏈上的其他處理器將不會被呼叫,而重寫的虛方法則不會出現這種情況; 
2、重寫要比關聯事件處理器高效得多,事件處理器需要迭代整個請求列表,這樣佔用了更多的CPU時間; 
3、事件能在執行時響應,具有更多的靈活性,可以對同一個事件關聯多個響應; 
4、通行的規則是處理一個派生類的事件是,重寫方式較好。   

三十六、合理使用.NET執行時診斷 
1、System.Diagnostics.Debug\Trace\EventLog為執行時提供了程式新增診斷資訊所需要的所有工具,EventLog提供入口時的應用程式能寫到系統事件日誌中; 
2、最後不要寫自己的診斷庫,.NET FCL 已經擁有了我們需要的核心庫。   

三十七、使用標準配置機制 
1、.NET框架的System.Windows.Application類為我們定義了建立通用配置路徑的屬性; 
2、Application.LocalAppDataPath 和 Application.userDataPath 會生成本地資料目錄和使用者資料的路徑名; 
3、不要在ProgramFiles和Windows系統目錄中寫入資料,這些位置需要更高的安全許可權,不要指望使用者擁有寫入的許可權。   

三十八、定製和支援資料繫結 
1、BindingMananger和CurrencyManager這兩個物件實現了控制元件和資料來源之間的資料傳輸; 
2、資料繫結的優勢:使用資料繫結要比編寫自己的程式碼簡單得多;應該將它用於文字資料項之外的範圍 —— 其他顯示屬性也可以被繫結;對於 Windowos Forms 資料繫結能夠處理多個控制元件同步的檢查相關資料來源; 
3、在物件不支援所需的屬性時,可以通過遮蔽當前的物件,然後新增一個想要的物件來支援資料繫結。   

三十九、使用.NET驗證 
1、ASP.NET中有五種控制元件來驗證有效性,可以用CustomValidator派生一個新類來增加自己的認證器; 
2、Windows驗證需要子System.Windows.Forms.Control.Validating寫一個事件處理器。   

四十、根據需要選用恰當的** 
1、陣列有兩個比較明顯的缺陷:不能動態的調整大小;調整大小非常耗時; 
2、ArrayList混合了一維陣列和連結串列的特徵,Queue和Stack是建立在Array基礎上的特殊陣列; 
3、當程式更加靈活的新增和刪除項時,可以使更加健壯的**型別,當建立一個模擬**的類時,應當為其實現索引器和IEnumberable介面。   

四十一、DataSet優於自定義結構 
1、DataSet有兩個缺點個:使用XML序列化機制的DataSet與非.NET 程式碼之間的互動不是很好;DataSet是一個非常通用的容器; 
2、強型別的DataSet打破了更多的設計規則,其獲得的開發效率要遠遠高於自己編寫的看上去更為優雅的設計。   

四十二、利用特性簡化反射 
通過設計和實現特性類,強制開發人員用他們來宣告可被動態使用的型別、方法和屬性,可以減少應用程式的執行時錯誤,提高軟體的使用者滿意度。   

四十三、避免過度使用反射 
1、Invoke成員使用的引數和返回值都是System.Object,在執行時進行型別的轉換,但出現問題的可能性也變得更多了; 
2、介面使我們可以得到一個更為清晰、也更具可維護性的系統,反射是一個很強大的晚期繫結機制,.NET框架使用它來實現Windows控制元件和Web控制元件的資料繫結。   

四十四、為應用程式建立特定的異常類 
1、需要不同的異常類的唯一原因是讓使用者在編寫catch處理器時能夠方便地對不同的錯誤採取不同的做法; 
2、可能有不同的修復行為時,我們才應該建立多種不同的異常類,通過提供異常基類所支援的所有構造器,可以為應用程式建立功能完整的異常類,使用InnerException屬性可以儲存更低階別錯誤條件所產生的所有錯誤資訊。   

四十五、優先選擇異常安全保證 
1、強異常保證在從異常中恢復和簡化異常處理之間提供了最好的平衡,在操作因為異常而中斷,程式的狀態保留不變; 
2、對將要修改的資料做防禦性的複製,對這些資料的防禦性複製進行修改,這中間的操作可能會引發異常,將臨時的副本和原物件進行交換; 
3、終結器、Dispose()方法和委託物件所繫結的目標方法在任何情況下都應當確保他們不會丟擲異常。   

四十六、最小化互操作 
1、互操作有三個方面的代價:資料在託管堆和非託管堆之間的列舉成本,託管程式碼和非託管程式碼之間切換的成本,對開發人員來說與混合環境打交道的開發工作; 
2、在interop中使用blittable型別可以有效地在託管和非託管環境中來回複製,而不受物件內部結構的影響; 
3、使用In/Out特性來確保最貼切的不必要的多次複製,通過宣告資料如何被列舉來提高效能; 
4、使用COM Interop用最簡單的方式實現和COM元件的互操作,使用P/Invoke呼叫Win32 API,或者使用C++編譯器的/CLR開關來混合託管和非託管的程式碼;   

四十七、優先選擇安全程式碼
1、儘可能的避免訪問非託管記憶體,隔離儲存不能防止來自託管程式碼和受信使用者的訪問; 
2、程式集在Web上執行時可以考慮使用隔離儲存,當某些演算法確實需要更高的安全許可時,應該將那些程式碼隔離在一個單獨的程式集中。   

四十八、掌握相關工具與資源 
1、使用NUnit建立自動單元測試(整合在VS2010 中了); 
2、FXCop工具會獲取程式集中的IL程式碼,並將其與異族編碼規則和最佳實踐對照分析,最後報告違例情況; 
3、ILDasm是一個IL反彙編工具,可以幫助我們洞察細節; 
4、Shared Source CLI是一個包含.NET框架核心和C#編譯器的實現原始碼。

四十九、為C#2.0做準備(這個規則現在已經沒什麼意義了,畢竟現在已經到了4.0 )
五十、瞭解ECMA標準

相關文章