分享一篇開發雜文

假如有一個設備,採用UDP組播協議,在正常通訊情況下通過網絡給你發送數據,注意的是,不管通道有沒有通都會發出數據但是對應的關鍵標識沒有,設備每100ms發送一次

在開始通訊前,有幾步要做的就是

1、先給設備發送打開通道的指令

2、返回狀態,然後想打電話一樣,發送呼叫號碼

3、呼叫成功,返回的狀態中會帶有成功表示,呼叫號碼爲呼叫時的號碼

4、歐克,此時證明鏈路已經打通了,此時發送和接收都可以正常產生了。

需求就是這麼個需求,如果是你,你會怎麼來實現這麼個例子呢?

emm~其實我最初最初的想法跟很多人一樣

就建個簡單的demo,建個winform案例,把組播通訊先實現了,然後建類,將要發送的數據格式,包括內容定義好。

創建個form,拖個timer控件,在timer2_Tick事件中,調用接收數據的方法。

1、定義接收的類,

2、定義發送的類

3、主界面中拖一些按鈕,文本框,什麼的。通過按鈕的點擊事件開啓通道,呼叫號碼,

4、調用組播,while(true){ }循環接收設備發送過來的數據。

大概的實現過程就是這樣,基本上測試也可以通過,(應該說正常的時候測試通過)

事實上呢???上面這種實現的方式有很大很大的弊端,沒有可以拓展的空間,性能很差,易出錯,甚至會導致程序直接崩潰掉。

一般情況下,處理數據的邏輯時不能放在主界面下面的,主線程下如果放了處理數據的,萬一出錯,界面直接卡死。

while(true){ }一般情況也是不能這麼寫的,容易引起死循環,要有一個出口,break

emm~說了這麼多,你是不是想問,說這麼多廢話幹啥子,直接告訴我應該怎麼做就好啦。

其實呢,我也不敢說以下的方式是最好的實現方式,但是一定是優於上文所述。

1、同樣需要一個界面,操作開啓通道,呼叫,輸入發送的內容,顯示接收的內容。

2、不同的時如何發送,如何接收

所有處理的過程全部在另外的類裏體現,採用單例模式,提供一個啓動的方法,調用此方法開啓接收和發送的任務

Task taskSend = Task.Factory.StartNew(new Action(發送的方法));
Task taskReceive = Task.Factory.StartNew(new Action(接收的方法));

Task task = Task.Factory.StartNew(new Action(處理接收數據的方法));

while(狀態標識){ }

emm~ 總之我想表達的是,實現一個東西,不要想着,怎麼樣實現它。而是應該想着如何更好的實現,

“好”的體現就是,

1、主程序下代碼儘量簡潔,處理邏輯儘量獨立

2、多采用異步線程,當然也不是讓你用特別多,一般十多個線程吧,多了也不好

3、考慮複用性、可移植性

C#相關性能優化小技巧:

1、顯式註冊的EvenHandler要顯式註銷以避免內存泄漏

將一個成員方法註冊到某個對象的事件會造成後者持有前者的引用。在事件註銷之前,前者不會被垃圾回收。

 

private void Form1_Load()
{
//註冊事件
CommandRemotingContext.CmdChanged += new ReciverCmdStateChangedEventHandler(this.CommandRemotingContext_CmdChanged);
}
 
private void Form1_FromClosed()
{
//關閉窗體時及時釋放事件
CommandRemotingContext.CmdChanged -= new ReciverCmdStateChangedEventHandler(this.CommandRemotingContext_CmdChanged);
}

由事件引起的內存泄漏問題:

  • 對象A訂閱了對象B中的事件
  • 對象A的生命週期遠遠大於對象B
  • 對象A沒有取消訂閱對象B的時間
  • 最終導致對象B無法釋放

2、控件綁定的數據源批量操作應避免自動刷新

  • 客戶端批量操作數據時,控件自帶的刷新操作,會造成不必要的時間消耗
  • 當數據源(如DataTable、Array、List、ObservableCollection或其他IListSource等)被綁定到控件時,批量操作數據時應該斷開綁定或掛起控件的刷新。
this.gcBillList.DataSource = null;
DataRowCollection rows = this.ds.Tables[0].Rows;
foreach (DataRow row in rows)
{
// DataRow數據操作
}
this.gcBillList.DataSource = this.ds.Tables[0].DefaultView;

1. C#語言方面 

1.1 垃圾回收
 
垃圾回收解放了手工管理對象的工作,提高了程序的健壯性,但副作用就是程序代碼可能對於對象創建變得隨意。 
1.1.1 避免不必要的對象創建 
由於垃圾回收的代價較高,所以C#程序開發要遵循的一個基本原則就是避免不必要的對象創建。以下列舉一些常見的情形。 
1.1.1.1 避免循環創建對象 ★ 
如果對象並不會隨每次循環而改變狀態,那麼在循環中反覆創建對象將帶來性能損耗。高效的做法是將對象提到循環外面創建。 
1.1.1.2 在需要邏輯分支中創建對象 
如果對象只在某些邏輯分支中才被用到,那麼應只在該邏輯分支中創建對象。 
1.1.1.3 使用常量避免創建對象 
程序中不應出現如 new Decimal(0) 之類的代碼,這會導致小對象頻繁創建及回收,正確的做法是使用Decimal.Zero常量。我們有設計自己的類時,也可以學習這個設計手法,應用到類似的場景中。 
1.1.1.4 使用StringBuilder做字符串連接 

1.1.2 不要使用空析構函數 ★ 
如果類包含析構函數,由創建對象時會在 Finalize 隊列中添加對象的引用,以保證當對象無法可達時,仍然可以調用到 Finalize 方法。垃圾回收器在運行期間,會啓動一個低優先級的線程處理該隊列。相比之下,沒有析構函數的對象就沒有這些消耗。如果析構函數爲空,這個消耗就毫無意 義,只會導致性能降低!因此,不要使用空的析構函數。 
在實際情況中,許多曾在析構函數中包含處理代碼,但後來因爲種種原因被註釋掉或者刪除掉了,只留下一個空殼,此時應注意把析構函數本身註釋掉或刪除掉。 
1.1.3 實現 IDisposable 接口 
垃圾回收事實上只支持託管內在的回收,對於其他的非託管資源,例如 Window GDI 句柄或數據庫連接,在析構函數中釋放這些資源有很大問題。原因是垃圾回收依賴於內在緊張的情況,雖然數據庫連接可能已瀕臨耗盡,但如果內存還很充足的話, 垃圾回收是不會運行的。 
C#的 IDisposable 接口是一種顯式釋放資源的機制。通過提供 using 語句,還簡化了使用方式(編譯器自動生成 try ... finally 塊,並在 finally 塊中調用 Dispose 方法)。對於申請非託管資源對象,應爲其實現 IDisposable 接口,以保證資源一旦超出 using 語句範圍,即得到及時釋放。這對於構造健壯且性能優良的程序非常有意義! 
爲防止對象的 Dispose 方法不被調用的情況發生,一般還要提供析構函數,兩者調用一個處理資源釋放的公共方法。同時,Dispose 方法應調用 System.GC.SuppressFinalize(this),告訴垃圾回收器無需再處理 Finalize 方法了。 
1.2 String 操作 
1.2.1 使用 StringBuilder 做字符串連接
 
String 是不變類,使用 + 操作連接字符串將會導致創建一個新的字符串。如果字符串連接次數不是固定的,例如在一個循環中,則應該使用 StringBuilder 類來做字符串連接工作。因爲 StringBuilder 內部有一個 StringBuffer ,連接操作不會每次分配新的字符串空間。只有當連接後的字符串超出 Buffer 大小時,纔會申請新的 Buffer 空間。典型代碼如下:

clip_image001 StringBuilder sb = new StringBuilder( 256 ); 
clip_image001[1] for ( int i = 0 ; i < Results.Count; i ++ ) 
clip_image002 { 
clip_image003 sb.Append (Results[i]); 
clip_image004
clip_image001[2]

如果連接次數是固定的並且只有幾次,此時應該直接用 + 號連接,保持程序簡潔易讀。實際上,編譯器已經做了優化,會依據加號次數調用不同參數個數的 String.Concat 方法。例如: 
String str = str1 + str2 + str3 + str4; 
會被編譯爲 String.Concat(str1, str2, str3, str4)。該方法內部會計算總的 String 長度,僅分配一次,並不會如通常想象的那樣分配三次。作爲一個經驗值,當字符串連接操作達到 10 次以上時,則應該使用 StringBuilder。 
這裏有一個細節應注意:StringBuilder 內部 Buffer 的缺省值爲 16 ,這個值實在太小。按 StringBuilder 的使用場景,Buffer 肯定得重新分配。經驗值一般用 256 作爲 Buffer 的初值。當然,如果能計算出最終生成字符串長度的話,則應該按這個值來設定 Buffer 的初值。使用 new StringBuilder(256) 就將 Buffer 的初始長度設爲了256。 
1.2.2 避免不必要的調用 ToUpper 或 ToLower 方法 
String是不變類,調用ToUpper或ToLower方法都會導致創建一個新的字符串。如果被頻繁調用,將導致頻繁創建字符串對象。這違背了前面講到的“避免頻繁創建對象”這一基本原則。 
例如,bool.Parse方法本身已經是忽略大小寫的,調用時不要調用ToLower方法。 
另一個非常普遍的場景是字符串比較。高效的做法是使用 Compare 方法,這個方法可以做大小寫忽略的比較,並且不會創建新字符串。 
還有一種情況是使用 HashTable 的時候,有時候無法保證傳遞 key 的大小寫是否符合預期,往往會把 key 強制轉換到大寫或小寫方法。實際上 HashTable 有不同的構造形式,完全支持採用忽略大小寫的 key: new HashTable(StringComparer.OrdinalIgnoreCase)。 
1.2.3 最快的空串比較方法 
將String對象的Length屬性與0比較是最快的方法:if (str.Length == 0) 
其次是與String.Empty常量或空串比較:if (str == String.Empty)或if (str == "") 
注:C#在編譯時會將程序集中聲明的所有字符串常量放到保留池中(intern pool),相同常量不會重複分配。 
1.3 多線程 
1.3.1 線程同步
 
線 程同步是編寫多線程程序需要首先考慮問題。C#爲同步提供了 Monitor、Mutex、AutoResetEvent 和 ManualResetEvent 對象來分別包裝 Win32 的臨界區、互斥對象和事件對象這幾種基礎的同步機制。C#還提供了一個lock語句,方便使用,編譯器會自動生成適當的 Monitor.Enter 和 Monitor.Exit 調用。 
1.3.1.1 同步粒度 
同步粒度可以是整個方法,也可以是方法中某一段代碼。爲方法指定 MethodImplOptions.Synchronized 屬性將標記對整個方法同步。例如:

clip_image001[3] [MethodImpl(MethodImplOptions.Synchronized)] 
clip_image001[4] public static SerialManager GetInstance() 
clip_image002[1] { 
clip_image003[1] if (instance == null ) 
clip_image005 { 
clip_image003[2] instance = new SerialManager(); 
clip_image006 } 
clip_image003[3] return instance; 
clip_image004[1]}

通常情況下,應減小同步的範圍,使系統獲得更好的性能。簡單將整個方法標記爲同步不是一個好主意,除非能確定方法中的每個代碼都需要受同步保護。 
1.3.1.2 同步策略 
使用 lock 進行同步,同步對象可以選擇 Type、this 或爲同步目的專門構造的成員變量。 
避免鎖定Type★ 
鎖定Type對象會影響同一進程中所有AppDomain該類型的所有實例,這不僅可能導致嚴重的性能問題,還可能導致一些無法預期的行爲。這是一個很不 好的習慣。即便對於一個只包含static方法的類型,也應額外構造一個static的成員變量,讓此成員變量作爲鎖定對象。 
避免鎖定 this 
鎖定 this 會影響該實例的所有方法。假設對象 obj 有 A 和 B 兩個方法,其中 A 方法使用 lock(this) 對方法中的某段代碼設置同步保護。現在,因爲某種原因,B 方法也開始使用 lock(this) 來設置同步保護了,並且可能爲了完全不同的目的。這樣,A 方法就被幹擾了,其行爲可能無法預知。所以,作爲一種良好的習慣,建議避免使用 lock(this) 這種方式。 
使用爲同步目的專門構造的成員變量 
這是推薦的做法。方式就是 new 一個 object 對象, 該對象僅僅用於同步目的。 
如果有多個方法都需要同步,並且有不同的目的,那麼就可以爲些分別建立幾個同步成員變量。 
1.3.1.4 集合同步 
C#爲各種集合類型提供了兩種方便的同步機制:Synchronized 包裝器和 SyncRoot 屬性。

clip_image001[5] // Creates and initializes a new ArrayList 
clip_image001[6] ArrayList myAL = new ArrayList(); 
clip_image001[7]myAL.Add( " The " ); 
clip_image001[8]myAL.Add( " quick " ); 
clip_image001[9]myAL.Add( " brown " ); 
clip_image001[10]myAL.Add( " fox " ); 
clip_image001[11] 
clip_image001[12] // Creates a synchronized wrapper around the ArrayList 
clip_image001[13] ArrayList mySyncdAL = ArrayList.Synchronized(myAL);

調用 Synchronized 方法會返回一個可保證所有操作都是線程安全的相同集合對象。考慮 mySyncdAL[0] = mySyncdAL[0] + "test" 這一語句,讀和寫一共要用到兩個鎖。一般講,效率不高。推薦使用 SyncRoot 屬性,可以做比較精細的控制。 
1.3.2 使用 ThreadStatic 替代 NameDataSlot ★ 
存 取 NameDataSlot 的 Thread.GetData 和 Thread.SetData 方法需要線程同步,涉及兩個鎖:一個是 LocalDataStore.SetData 方法需要在 AppDomain 一級加鎖,另一個是 ThreadNative.GetDomainLocalStore 方法需要在 Process 一級加鎖。如果一些底層的基礎服務使用了 NameDataSlot,將導致系統出現嚴重的伸縮性問題。 
規避這個問題的方法是使用 ThreadStatic 變量。示例如下:

clip_image001[14] public sealed class InvokeContext 
clip_image002[2] { 
clip_image003[4] [ThreadStatic] 
clip_image003[5] private static InvokeContext current; 
clip_image003[6] private Hashtable maps = new Hashtable(); 
clip_image004[2]}

1.3.3 多線程編程技巧 
1.3.3.1 使用 Double Check 技術創建對象

clip_image001[15] internal IDictionary KeyTable 
clip_image002[3] { 
clip_image003[7] get 
clip_image005[1] { 
clip_image003[8] if ( this ._keyTable == null ) 
clip_image005[2] { 
clip_image003[9] lock ( base ._lock) 
clip_image005[3] { 
clip_image003[10] if ( this ._keyTable == null ) 
clip_image005[4] { 
clip_image003[11] this ._keyTable = new Hashtable(); 
clip_image006[1] } 
clip_image006[2] } 
clip_image006[3] } 
clip_image003[12] return this ._keyTable; 
clip_image006[4] } 
clip_image004[3]}

創建單例對象是很常見的一種編程情況。一般在 lock 語句後就會直接創建對象了,但這不夠安全。因爲在 lock 鎖定對象之前,可能已經有多個線程進入到了第一個 if 語句中。如果不加第二個 if 語句,則單例對象會被重複創建,新的實例替代掉舊的實例。如果單例對象中已有數據不允許被破壞或者別的什麼原因,則應考慮使用 Double Check 技術。 
1.4 類型系統 
1.4.1 避免無意義的變量初始化動作
 
CLR保證所有對象在訪問前已初始化,其做法是將分配的內存清零。因此,不需要將變量重新初始化爲0、false或null。 
需要注意的是:方法中的局部變量不是從堆而是從棧上分配,所以C#不會做清零工作。如果使用了未賦值的局部變量,編譯期間即會報警。不要因爲有這個印象而對所有類的成員變量也做賦值動作,兩者的機理完全不同! 
1.4.2 ValueType 和 ReferenceType 
1.4.2.1 以引用方式傳遞值類型參數
 
值類型從調用棧分配,引用類型從託管堆分配。當值類型用作方法參數時,默認會進行參數值複製,這抵消了值類型分配效率上的優勢。作爲一項基本技巧,以引用方式傳遞值類型參數可以提高性能。 
1.4.2.2 爲 ValueType 提供 Equals 方法 
.net 默認實現的 ValueType.Equals 方法使用了反射技術,依靠反射來獲得所有成員變量值做比較,這個效率極低。如果我們編寫的值對象其 Equals 方法要被用到(例如將值對象放到 HashTable 中),那麼就應該重載 Equals 方法。

clip_image001[16] public struct Rectangle 
clip_image002[4] { 
clip_image003[13] public double Length; 
clip_image003[14] public double Breadth; 
clip_image003[15] public override bool Equals ( object ob) 
clip_image005[5] { 
clip_image003[16] if (ob is Rectangle) 
clip_image003[17] return Equels ((Rectangle)ob)) 
clip_image003[18] else 
clip_image003[19] return false ; 
clip_image006[5] } 
clip_image003[20] private bool Equals (Rectangle rect) 
clip_image005[6] { 
clip_image003[21] return this .Length == rect.Length && this .Breadth == rect.Breach; 
clip_image006[6] } 
clip_image004[4]}

1.4.2.3 避免裝箱和拆箱 
C#可以在值類型和引用類型之間自動轉換,方法是裝箱和拆箱。裝箱需要從堆上分配對象並拷貝值,有一定性能消耗。如果這一過程發生在循環中或是作爲底層方法被頻繁調用,則應該警惕累計的效應。 
一種經常的情形出現在使用集合類型時。例如:

clip_image001[17] ArrayList al = new ArrayList(); 
clip_image001[18] for ( int i = 0 ; i < 1000 ; i ++ ) 
clip_image002[5] { 
clip_image003[22] al.Add(i); // Implicitly boxed because Add() takes an object 
clip_image004[5] } 
clip_image001[19] int f = ( int )al[ 0 ]; // The element is unboxed

1.5 異常處理 
異常也是現代語言的典型特徵。與傳統檢查錯誤碼的方式相比,異常是強制性的(不依賴於是否忘記了編寫檢查錯誤碼的代碼)、強類型的、並帶有豐富的異常信息(例如調用棧)。 
1.5.1 不要吃掉異常★ 
關於異常處理的最重要原則就是:不要吃掉異常。這個問題與性能無關,但對於編寫健壯和易於排錯的程序非常重要。這個原則換一種說法,就是不要捕獲那些你不能處理的異常。 
吃掉異常是極不好的習慣,因爲你消除了解決問題的線索。一旦出現錯誤,定位問題將非常困難。除了這種完全吃掉異常的方式外,只將異常信息寫入日誌文件但並不做更多處理的做法也同樣不妥。 
1.5.2 不要吃掉異常信息★ 
有些代碼雖然拋出了異常,但卻把異常信息吃掉了。 
爲異常披露詳盡的信息是程序員的職責所在。如果不能在保留原始異常信息含義的前提下附加更豐富和更人性化的內容,那麼讓原始的異常信息直接展示也要強得多。千萬不要吃掉異常。 
1.5.3 避免不必要的拋出異常 
拋出異常和捕獲異常屬於消耗比較大的操作,在可能的情況下,應通過完善程序邏輯避免拋出不必要不必要的異常。與此相關的一個傾向是利用異常來控制處理邏輯。儘管對於極少數的情況,這可能獲得更爲優雅的解決方案,但通常而言應該避免。 
1.5.4 避免不必要的重新拋出異常 
如果是爲了包裝異常的目的(即加入更多信息後包裝成新異常),那麼是合理的。但是有不少代碼,捕獲異常沒有做任何處理就再次拋出,這將無謂地增加一次捕獲異常和拋出異常的消耗,對性能有傷害。 
1.6 反射 
反射是一項很基礎的技術,它將編譯期間的靜態綁定轉換爲延遲到運行期間的動態綁定。在很多場景下(特別是類框架的設計),可以獲得靈活易於擴展的架構。但帶來的問題是與靜態綁定相比,動態綁定會對性能造成較大的傷害。 
1.6.1 反射分類 
type comparison :類型判斷,主要包括 is 和 typeof 兩個操作符及對象實例上的 GetType 調用。這是最輕型的消耗,可以無需考慮優化問題。注意 typeof 運算符比對象實例上的 GetType 方法要快,只要可能則優先使用 typeof 運算符。 
member enumeration : 成員枚舉,用於訪問反射相關的元數據信息,例如Assembly.GetModule、Module.GetType、Type對象上的 IsInterface、IsPublic、GetMethod、GetMethods、GetProperty、GetProperties、 GetConstructor調用等。儘管元數據都會被CLR緩存,但部分方法的調用消耗仍非常大,不過這類方法調用頻度不會很高,所以總體看性能損失程 度中等。 
member invocation:成員調用,包括動態創建對象及動態調用對象方法,主要有Activator.CreateInstance、Type.InvokeMember等。 
1.6.2 動態創建對象 
C#主要支持 5 種動態創建對象的方式: 
1. Type.InvokeMember 
2. ContructorInfo.Invoke 
3. Activator.CreateInstance(Type) 
4. Activator.CreateInstance(assemblyName, typeName) 
5. Assembly.CreateInstance(typeName) 
最快的是方式 3 ,與 Direct Create 的差異在一個數量級之內,約慢 7 倍的水平。其他方式,至少在 40 倍以上,最慢的是方式 4 ,要慢三個數量級。 
1.6.3 動態方法調用 
方法調用分爲編譯期的早期綁定和運行期的動態綁定兩種,稱爲Early-Bound Invocation和Late-Bound Invocation。Early-Bound Invocation可細分爲Direct-call、Interface-call和Delegate-call。Late-Bound Invocation主要有Type.InvokeMember和MethodBase.Invoke,還可以通過使用LCG(Lightweight Code Generation)技術生成IL代碼來實現動態調用。 
從測試結果看,相比Direct Call,Type.InvokeMember要接近慢三個數量級;MethodBase.Invoke雖然比Type.InvokeMember要快三 倍,但比Direct Call仍慢270倍左右。可見動態方法調用的性能是非常低下的。我們的建議是:除非要滿足特定的需求,否則不要使用! 
1.6.4 推薦的使用原則 
模式 
1. 如果可能,則避免使用反射和動態綁定 
2. 使用接口調用方式將動態綁定改造爲早期綁定 
3. 使用Activator.CreateInstance(Type)方式動態創建對象 
4. 使用typeof操作符代替GetType調用 
反模式 
1. 在已獲得Type的情況下,卻使用Assembly.CreateInstance(type.FullName) 
1.7 基本代碼技巧 
這裏描述一些應用場景下,可以提高性能的基本代碼技巧。對處於關鍵路徑的代碼,進行這類的優化還是很有意義的。普通代碼可以不做要求,但養成一種好的習慣也是有意義的。 
1.7.1 循環寫法 
可以把循環的判斷條件用局部變量記錄下來。局部變量往往被編譯器優化爲直接使用寄存器,相對於普通從堆或棧中分配的變量速度快。如果訪問的是複雜計算屬性 的話,提升效果將更明顯。for (int i = 0, j = collection.GetIndexOf(item); i < j; i++) 
需要說明的是:這種寫法對於CLR集合類的Count屬性沒有意義,原因是編譯器已經按這種方式做了特別的優化。 
1.7.2 拼裝字符串 
拼裝好之後再刪除是很低效的寫法。有些方法其循環長度在大部分情況下爲1,這種寫法的低效就更爲明顯了:

clip_image001[20] public static string ToString(MetadataKey entityKey) 
clip_image002[6] { 
clip_image003[23] string str = "" ; 
clip_image003[24] object [] vals = entityKey.values; 
clip_image003[25] for ( int i = 0 ; i < vals.Length; i ++ ) 
clip_image005[7] { 
clip_image003[26] str += " , " + vals[i].ToString(); 
clip_image006[7] } 
clip_image003[27] return str == "" ? "" : str.Remove( 0 , 1 ); 
clip_image004[6]}

推薦下面的寫法:

clip_image001[21] if (str.Length == 0 ) 
clip_image001[22] str = vals[i].ToString(); 
clip_image001[23] else 
clip_image001[24] str += " , " + vals[i].ToString();

其實這種寫法非常自然,而且效率很高,完全不需要用個Remove方法繞來繞去。 
1.7.3 避免兩次檢索集合元素 
獲取集合元素時,有時需要檢查元素是否存在。通常的做法是先調用ContainsKey(或Contains)方法,然後再獲取集合元素。這種寫法非常符合邏輯。 
但如果考慮效率,可以先直接獲取對象,然後判斷對象是否爲null來確定元素是否存在。對於Hashtable,這可以節省一次GetHashCode調用和n次Equals比較。

如下面的示例:

clip_image001[25] public IData GetItemByID(Guid id) 
clip_image002[7] { 
clip_image003[28] IData data1 = null ; 
clip_image003[29] if ( this .idTable.ContainsKey(id.ToString()) 
clip_image005[8] { 
clip_image003[30] data1 = this .idTable[id.ToString()] as IData; 
clip_image006[8] } 
clip_image003[31] return data1; 
clip_image004[7]}

其實完全可用一行代碼完成:return this.idTable[id] as IData; 
1.7.4 避免兩次類型轉換 
考慮如下示例,其中包含了兩處類型轉換:

clip_image001[26] if (obj is SomeType) 
clip_image002[8] { 
clip_image003[32] SomeType st = (SomeType)obj; 
clip_image003[33] st.SomeTypeMethod(); 
clip_image004[8]}

效率更高的做法如下:

clip_image001[27] SomeType st = obj as SomeType; 
clip_image001[28] if (st != null ) 
clip_image002[9] { 
clip_image003[34] st.SomeTypeMethod(); 
clip_image004[9]}

1.8 Hashtable 
Hashtable是一種使用非常頻繁的基礎集合類型。需要理解影響Hashtable的效率有兩個因素:一是散列碼(GetHashCode方法),二 是等值比較(Equals方法)。Hashtable首先使用鍵的散列碼將對象分佈到不同的存儲桶中,隨後在該特定的存儲桶中使用鍵的Equals方法進 行查找。 
良好的散列碼是第一位的因素,最理想的情況是每個不同的鍵都有不同的散列碼。Equals方法也很重要,因爲散列只需要做一次,而存儲桶中查找鍵可能需要做多次。從實際經驗看,使用Hashtable時,Equals方法的消耗一般會佔到一半以上。

System.Object類提供了默認的GetHashCode實現,使用對象在內存中的地址作爲散列碼。我們遇到過一個用Hashtable來緩存對 象的例子,每次根據傳遞的OQL表達式構造出一個ExpressionList對象,再調用QueryCompiler的方法編譯得到 CompiledQuery對象。以ExpressionList對象和CompiledQuery對象作爲鍵值對存儲到Hashtable中。 ExpressionList對象沒有重載GetHashCode實現,其超類ArrayList也沒有,這樣最後用的就是System.Object類 的GetHashCode實現。由於ExpressionList對象會每次構造,因此它的HashCode每次都不同,所以這個 CompiledQueryCache根本就沒有起到預想的作用。這個小小的疏漏帶來了重大的性能問題,由於解析OQL表達式頻繁發生,導致 CompiledQueryCache不斷增長,造成服務器內存泄漏!解決這個問題的最簡單方法就是提供一個常量實現,例如讓散列碼爲常量0。雖然這會導 致所有對象匯聚到同一個存儲桶中,效率不高,但至少可以解決掉內存泄漏問題。當然,最終還是會實現一個高效的GetHashCode方法的。 
以上介紹這些Hashtable機理,主要是希望大家理解:如果使用Hashtable,你應該檢查一下對象是否提供了適當的GetHashCode和Equals方法實現。否則,有可能出現效率不高或者與預期行爲不符的情況。

參考:https://www.cnblogs.com/liangqihui/p/9502669.html

ok ,今天的分享就到這裏了,有問題的歡迎提出

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章