天天看點

C#幾個經常犯錯誤彙總

原文位址:http://www.cnblogs.com/zhijianliutang/archive/2012/03/20/2407688.html

在我們平常程式設計中,時間久了有時候會形成一種習慣性的思維方式,形成固有的程式設計風格,但是有些地方是需要斟酌的,即使是一個很小的錯誤也可能會導緻昂貴的代價,要學會善于總結,從錯誤中汲取教訓,盡量不再犯同樣錯誤,注重程式設計之美,代碼的優雅,總結幾個平常經常犯的錯誤。

1、在c#程式設計中,字元型類型是最容易處理出錯的地方,代價是非常昂貴,在.net framwork中,字元串是一個相當特别的引用類型,string本省就是一個不可繼承的密封類,但是它具有了值類型所應用的特點,但是它在clr中記憶體還是儲存于托管堆之上,也就是說,當我們每次定義一個字元串類型的時候,就在堆記憶體中開辟一端記憶體,而當我們字元串被修改之後,它會建立一個新的記憶體,注意這裡的記憶體是不連續的,而是通過修改棧内位址引用而拼湊字元串,不會改變源字元串在記憶體中的位址,是以有些程式員總是喜歡使用這樣的方法格式化字元串:

上述代碼,使用了字元串拼湊的方法,因為使用了多重串聯,是以會在記憶體中建立兩個不必要的字元串垃圾副本。

其實在c#中,已經為我們提供了stringbuilder和string.fromat來解決此問題,雖然他們可以實作同樣的功能,但是他們有質的變化,stringbuilder在記憶體中開辟的是一段連續記憶體,當增加新字元串時候,它會在棧中指向的同一個堆記憶體中連續存放字元,這就形成了性能的提升。是以我們将上面代碼改成:

2、大多數開發人員都不知道内置的驗證資料類型的方法,如system.int32,是以很多人都是自己實作的,其實這是不妥的,因為這些基本類型中都存在自己固有的類型驗證方法,下面這個就是自己實作驗證的一個字元串是否是數值的代碼:

雖然使用了try catch語句,這不是最佳的做法,更好的方法是下面使用int.tryparse;

int.tryparse是更快、更簡潔的方法。

3、自己利用idisposable接口手動釋放記憶體

在.netframework中,對象的處理和使用一樣重要,理想的方法是在使用完對象的時候,在類中實作idisposable接口中的dispose方法進行記憶體的釋放,當然在.net本身提供的垃圾回收機制(gc)中就提供了這樣的功能,在我們執行個體化類對象時,在類本身的析構函數中會調用dispose方法,gc在各級記憶體堆滿的情況下,自動檢查對象使用情況,去相應的釋放記憶體,但是運作在非托管平台上的方法,需要我們自己手動釋放記憶體,比如我們常見的sqlconnection對象,也就有了下面的建立、使用和處理方法:

上述代碼是大部分程式員會出現的代碼,乍看沒啥問題,連接配接處理在最後一個代碼中被明确調用,但是如果發生了一個異常,catch代碼塊就被執行,然後再執行最後一個代碼塊處理連接配接,是以在最後一個代碼塊執行之前,連接配接将一直留在記憶體中,大部分我們會在此處記錄錯誤,一般涉及到io操作,如果延時時間比較長的話,這個連接配接将在記憶體時間長時間停留。我們一個原則就是當對象不再使用的時候我們裡面釋放資源。

我們采用程式邏輯域來處理這個問題會更好:

當使用using代碼快時,對象上的dispose()方法将在執行推出邏輯域的時候調用,這樣就保證了sqlconnection的資源處理被盡早釋放,當然這個方法也适用于實作idisposable接口的類,當時個人不推薦這樣做,在非常有把握的情況下可以手動釋放,但是沒把握還是叫給.net系統釋放,因為本身類的析構函數就實作這個方法,當我們自己重寫後,反而會導緻系統誤以為你自己定義了方法,而推遲釋放資源,有興趣可以研究下gc運作本質,假如能在第一代被釋放的記憶體,如果我們重寫dispose方法反而推遲到第二代記憶體堆中釋放,顯然是不可取的。

4、學會合理的管理公共變量,我們在系統中經常會濫用公共變量,沒有做到合适的封裝好。

在上面的myaccount類中生命了一個accountnumber公共變量,理想情況下,accountnumber應該是隻讀的,不能讓外界修改,但是這裡myaccount類卻沒有對它做任何控制。

聲明公共做法應該是使用屬性,如:

這裡我們封裝了accountnumber公共變量,它變成了隻讀,不能由調用者類進行修改。

5、嵌套的異常處理,有的開發人員喜歡在方法末尾加上處理的嵌套方法,如

如果相同的異常被處理多次,性能開銷将會增加。

我們的解決方法是讓異常處理方法獨立開來,如:

6、大資料量上使用dataset和datareader混用,當單表資料量很大的情況,使用dataset是一種很不明智的選擇,應為dataset是以datatable記憶體形式存放資料量,一次性将資料拖入記憶體,當資料很大的情況下,這種方式是很吃記憶體的,相比dataser,datareader就顯得優雅很多,它是每次讀取一條資料,然後輪詢調用機制,但是也有它的弊端,就是相對長連接配接,但是對記憶體消耗而言這是有利的,當然dataset在大部分應用場景下也是有自己的優點,充分解耦、一次性操作、領域模型操作等方面,兩者分情況分場景而用,這裡隻是稍微提提,根據場景分析差別。

 内容更正

原篇文章不動,感謝園友的點評,更正幾處内容

1、第一條string類型記憶體消耗問題,舉的例子不到位,在字元串數量少的時候性能沒有影響的,但就在.net framwork平台運作,分析應該就是此原理了。

現将老趙分析的結論歸結如下:

     <1>對于字元串數量比較少的情況(從資料上來看大約是5-6個),stringbuilder的性能并不比普通連接配接操作來的快。是以,在任何地方都使用stringbuilder是不恰當的做法。    

參照:http://blog.zhaojie.me/2009/11/string-concat-perf-1-benchmark.html

另附性能比較源碼同樣出自老趙博文,有興趣的園友可自行比較測試:http://www.cnblogs.com/zhijianliutang/archive/2011/12/17/2291323.html

2、類對象在使用完對象後并不是通過析構函數調用dispose方法實作垃圾回收,dispose是.net類庫提供的一個釋放記憶體的方法,供開發人員自行調用,它是通過finalizer是供gc調用。

關于其他sql注入、屬性公開知否妥當、自行調用dispose方法釋放記憶體是否會推遲釋放等觀點都是分應用場景而言,算作抛磚引玉吧,非絕對。最後謝園友們指點。