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()方法,垃圾回收器總是會調用終結器方法的,這樣可以避免出現非托管的內存資源不被釋放引起內存泄漏的問題;l
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、如果需要創建派生類則需要提供一個掛鉤方法供派生類使用。
由于本文過長的原因,分為兩篇為大家介紹,請看下一篇>>
【編輯推薦】