在我們平常編程中,時(shí)間久了有時(shí)候會(huì)形成一種習(xí)慣性的思維方式,形成固有的編程風(fēng)格,但是有些地方是需要斟酌的,即使是一個(gè)很小的錯(cuò)誤也可能會(huì)導(dǎo)致昂貴的代價(jià),要學(xué)會(huì)善于總結(jié),從錯(cuò)誤中汲取教訓(xùn),盡量不再犯同樣錯(cuò)誤,注重編程之美,代碼的優(yōu)雅,總結(jié)幾個(gè)平常經(jīng)常犯的錯(cuò)誤。
1、在C#編程中,字符型類型是最容易處理出錯(cuò)的地方,代價(jià)是非常昂貴,在.Net Framwork中,字符串是一個(gè)相當(dāng)特別的引用類型,string本省就是一個(gè)不可繼承的密封類,但是它具有了值類型所應(yīng)用的特點(diǎn),但是它在CLR中內(nèi)存還是保存于托管堆之上,也就是說(shuō),當(dāng)我們每次定義一個(gè)字符串類型的時(shí)候,就在堆內(nèi)存中開辟一端內(nèi)存,而當(dāng)我們字符串被修改之后,它會(huì)創(chuàng)建一個(gè)新的內(nèi)存,注意這里的內(nèi)存是不連續(xù)的,而是通過修改棧內(nèi)地址引用而拼湊字符串,不會(huì)改變?cè)醋址趦?nèi)存中的地址,所以有些程序員總是喜歡使用這樣的方法格式化字符串:
string SelectText="select * from "+TableName+" where UserName='"+Name+"'";
上述代碼,使用了字符串拼湊的方法,因?yàn)槭褂昧硕嘀卮?lián),因此會(huì)在內(nèi)存中創(chuàng)建兩個(gè)不必要的字符串垃圾副本。
其實(shí)在C#中,已經(jīng)為我們提供了StringBuilder和String.Fromat來(lái)解決此問題,雖然他們可以實(shí)現(xiàn)同樣的功能,但是他們有質(zhì)的變化,StringBuilder在內(nèi)存中開辟的是一段連續(xù)內(nèi)存,當(dāng)增加新字符串時(shí)候,它會(huì)在棧中指向的同一個(gè)堆內(nèi)存中連續(xù)存放字符,這就形成了性能的提升。所以我們將上面代碼改成:
string SelectText=string.Format("select * from {0} where UserName={1}",TableName,Name);
2、大多數(shù)開發(fā)人員都不知道內(nèi)置的驗(yàn)證數(shù)據(jù)類型的方法,如System.Int32,因此很多人都是自己實(shí)現(xiàn)的,其實(shí)這是不妥的,因?yàn)檫@些基本類型中都存在自己固有的類型驗(yàn)證方法,下面這個(gè)就是自己實(shí)現(xiàn)驗(yàn)證的一個(gè)字符串是否是數(shù)值的代碼:
public bool CheckIfNumeric(string value)
{
bool IsNumeric=true;
try
{
int i=Convert.ToInt32(value);
}
catch(FormatException excepiton)
{
IsNumeric=false;
}
return IsNumeric;
}
雖然使用了try catch語(yǔ)句,這不是最佳的做法,更好的方法是下面使用Int.TryParse;
int output=0;
bool IsNumeric=int.TryParse(value,out output);
int.TryParse是更快、更簡(jiǎn)潔的方法。
3、自己利用IDisposable接口手動(dòng)釋放內(nèi)存
在.NET Framework中,對(duì)象的處理和使用一樣重要,理想的方法是在使用完對(duì)象的時(shí)候,在類中實(shí)現(xiàn)IDisposable接口中的dispose方法進(jìn)行內(nèi)存的釋放,當(dāng)然在.Net本身提供的垃圾回收機(jī)制(GC)中就提供了這樣的功能,在我們實(shí)例化類對(duì)象時(shí),在類本身的析構(gòu)函數(shù)中會(huì)調(diào)用dispose方法,GC在各級(jí)內(nèi)存堆滿的情況下,自動(dòng)檢查對(duì)象使用情況,去相應(yīng)的釋放內(nèi)存,但是運(yùn)行在非托管平臺(tái)上的方法,需要我們自己手動(dòng)釋放內(nèi)存,比如我們常見的SqlConnection對(duì)象,也就有了下面的創(chuàng)建、使用和處理方法:
public void DALOneMethod()
{
SqlConnection connection=null;
try
{
connection =new SqlConnection("。。。。。。。。。。。");
connection.Open();
//sqlcommand。。run
}
catch(Exception exception)
{
// manager exception
}
finally
{
connection.Close();
connection.Disopse();
}
}
上述代碼是大部分程序員會(huì)出現(xiàn)的代碼,乍看沒啥問題,連接處理在最后一個(gè)代碼中被明確調(diào)用,但是如果發(fā)生了一個(gè)異常,catch代碼塊就被執(zhí)行,然后再執(zhí)行最后一個(gè)代碼塊處理連接,因此在最后一個(gè)代碼塊執(zhí)行之前,連接將一直留在內(nèi)存中,大部分我們會(huì)在此處記錄錯(cuò)誤,一般涉及到IO操作,如果延時(shí)時(shí)間比較長(zhǎng)的話,這個(gè)連接將在內(nèi)存時(shí)間長(zhǎng)時(shí)間停留。我們一個(gè)原則就是當(dāng)對(duì)象不再使用的時(shí)候我們里面釋放資源。
我們采用程序邏輯域來(lái)處理這個(gè)問題會(huì)更好:
public void DALOneMethod()
{
using(SqlConnction connection=new SqlConnection("。。。。。。。"))
{
connction.Open();
// do SUAD
}
}
當(dāng)使用using代碼快時(shí),對(duì)象上的dispose()方法將在執(zhí)行推出邏輯域的時(shí)候調(diào)用,這樣就保證了SqlConnection的資源處理被盡早釋放,當(dāng)然這個(gè)方法也適用于實(shí)現(xiàn)IDisposable接口的類,當(dāng)時(shí)個(gè)人不推薦這樣做,在非常有把握的情況下可以手動(dòng)釋放,但是沒把握還是叫給.net系統(tǒng)釋放,因?yàn)楸旧眍惖奈鰳?gòu)函數(shù)就實(shí)現(xiàn)這個(gè)方法,當(dāng)我們自己重寫后,反而會(huì)導(dǎo)致系統(tǒng)誤以為你自己定義了方法,而推遲釋放資源,有興趣可以研究下GC運(yùn)行本質(zhì),假如能在第一代被釋放的內(nèi)存,如果我們重寫dispose方法反而推遲到第二代內(nèi)存堆中釋放,顯然是不可取的。
4、學(xué)會(huì)合理的管理公共變量,我們?cè)谙到y(tǒng)中經(jīng)常會(huì)濫用公共變量,沒有做到合適的封裝好。
static void Main(string[] args)
{
MyAccount account=new MyAccount();
//這地方不能隨便的調(diào)用account里面的字段進(jìn)行更改,但是缺改了
account.AccountNumber="ddddddddd";
Console.ReadKey();
}
public class MyAccount
{
public string AccountNumber;
public MyAcctount()
{
AccountNumber="ssssssssssssss";
}
}
在上面的MyAccount類中生命了一個(gè)AccountNumber公共變量,理想情況下,AccountNumber應(yīng)該是只讀的,不能讓外界修改,但是這里MyAccount類卻沒有對(duì)它做任何控制。
聲明公共做法應(yīng)該是使用屬性,如:
public class MyAccount
{
private stirng _accountNumber;
public string AccountNumber
{
get { return _accountNumber; }
}
public MyAccount()
{
_accountNumber="dddddddd";
}
}
這里我們封裝了AccountNumber公共變量,它變成了只讀,不能由調(diào)用者類進(jìn)行修改。
5、嵌套的異常處理,有的開發(fā)人員喜歡在方法末尾加上處理的嵌套方法,如
public class NestedExceptionHandling
{
public void MainMethod()
{
try
{
//some implementation
ChildMethod1();
}
catch (Exception exception)
{
//Handle exception
}
}
private void ChildMethod1()
{
try
{
//some implementation
ChildMethod2();
}
catch (Exception exception)
{
//Handle exception
throw;
}
}
private void ChildMethod2()
{
try
{
//some implementation
}
catch (Exception exception)
{
//Handle exception
throw;
}
}
}
如果相同的異常被處理多次,性能開銷將會(huì)增加。
我們的解決方法是讓異常處理方法獨(dú)立開來(lái),如:
public class NestedExceptionHandling
{
public void MainMethod()
{
try
{
//some implementation
ChildMethod1();
}
catch(Exception exception)
{
//Handle exception
}
}
private void ChildMethod1()
{
//some implementation
ChildMethod2();
}
private void ChildMethod2()
{
//some implementation
}
}
6、大數(shù)據(jù)量上使用Dataset和DataReader混用,當(dāng)單表數(shù)據(jù)量很大的情況,使用DataSet是一種很不明智的選擇,應(yīng)為DataSet是以DataTable內(nèi)存形式存放數(shù)據(jù)量,一次性將數(shù)據(jù)拖入內(nèi)存,當(dāng)數(shù)據(jù)很大的情況下,這種方式是很吃內(nèi)存的,相比DataSer,DataReader就顯得優(yōu)雅很多,它是每次讀取一條數(shù)據(jù),然后輪詢調(diào)用機(jī)制,但是也有它的弊端,就是相對(duì)長(zhǎng)連接,但是對(duì)內(nèi)存消耗而言這是有利的,當(dāng)然DataSet在大部分應(yīng)用場(chǎng)景下也是有自己的優(yōu)點(diǎn),充分解耦、一次性操作、領(lǐng)域模型操作等方面,兩者分情況分場(chǎng)景而用,這里只是稍微提提,根據(jù)場(chǎng)景分析區(qū)別。
內(nèi)容更正
原篇文章不動(dòng),感謝園友點(diǎn)評(píng),更正幾處內(nèi)容
1、第一條String類型內(nèi)存消耗問題,舉的例子不到位,在字符串?dāng)?shù)量少的時(shí)候性能沒有影響的,但就在.net Framwork平臺(tái)運(yùn)行,分析應(yīng)該就是此原理了。
現(xiàn)將老趙分析的結(jié)論歸結(jié)如下:
<1>對(duì)于字符串?dāng)?shù)量比較少的情況(從數(shù)據(jù)上來(lái)看大約是5-6個(gè)),StringBuilder的性能并不比普通連接操作來(lái)的快。因此,在任何地方都使用StringBuilder是不恰當(dāng)?shù)淖龇ā?/P>
參照:
另附性能比較源碼同樣出自老趙博文,有興趣的園友可自行比較測(cè)試:
2、類對(duì)象在使用完對(duì)象后并不是通過析構(gòu)函數(shù)調(diào)用Dispose方法實(shí)現(xiàn)垃圾回收,Dispose是.net類庫(kù)提供的一個(gè)釋放內(nèi)存的方法,供開發(fā)人員自行調(diào)用,它是通過Finalizer是供GC調(diào)用。
關(guān)于其他SQL注入、屬性公開知否妥當(dāng)、自行調(diào)用Dispose方法釋放內(nèi)存是否會(huì)推遲釋放等觀點(diǎn)都是分應(yīng)用場(chǎng)景而言,算作拋磚引玉吧,非絕對(duì)。最后謝園友們指點(diǎn)。
更多信息請(qǐng)查看IT技術(shù)專欄