前言
本文主要來(lái)學(xué)習(xí)記錄前三個(gè)建議。
建議1、正確操作字符串
建議2、使用默認(rèn)轉(zhuǎn)型方法
建議3、區(qū)別對(duì)待強(qiáng)制轉(zhuǎn)換與as和is
其中有很多需要理解的東西,有些地方可能理解的不太到位,還望指正。
建議1、正確操作字符串
字符串應(yīng)該是所有編程語(yǔ)言中使用最頻繁的一種基礎(chǔ)數(shù)據(jù)類(lèi)型。如果使用不慎,我們就會(huì)為一次字符串的操作所帶來(lái)的額外性能開(kāi)銷(xiāo)而付出代價(jià)。本條建議將從兩個(gè)方面來(lái)探討如何規(guī)避這類(lèi)性能開(kāi)銷(xiāo):
1、確保盡量少的裝箱
2、避免分配額外的內(nèi)存空間
先來(lái)介紹第一個(gè)方面,請(qǐng)看下面的兩行代碼:
String str1="str1"+9;String str2="str2"+9.ToString();
從IL代碼可以得知,第一行代碼在運(yùn)行時(shí)完成一次裝箱的行為,而第二行代碼中并沒(méi)有發(fā)生裝箱的行為,它實(shí)際調(diào)用的是整型的ToString()方法,效率要比裝箱高。所以,在使用其他值引用類(lèi)型到字符串的轉(zhuǎn)換并完成拼接時(shí),應(yīng)當(dāng)避免使用操作符“+”來(lái)完成,而應(yīng)該使用值引用類(lèi)型提供的ToString()方法。
第二方面,避免分配額外的內(nèi)存空間。對(duì)CLR來(lái)說(shuō),string對(duì)象(字符串對(duì)象)是個(gè)很特殊的對(duì)象,它一旦被賦值就不可改變。在運(yùn)行時(shí)調(diào)用System.String類(lèi)中的任何方法或進(jìn)行任何運(yùn)算(如“=”賦值、“+”拼接等),都會(huì)在內(nèi)存中創(chuàng)建一個(gè)新的字符串對(duì)象,這也意味著要為該新對(duì)象分配新的內(nèi)存空間。像下面的代碼就會(huì)帶來(lái)運(yùn)行時(shí)的額外開(kāi)銷(xiāo)。
PRivate static void NewMethod1(){ string s1="abc"; s1="123"+s1+"456"; ////以上兩行代碼創(chuàng)建了3個(gè)字符串對(duì)象對(duì)象,并執(zhí)行了一次string.Contact方法 }private static void NewMethod2(){ string re=9+"456"; ////該方法發(fā)生了一次裝箱,并調(diào)用一次string.Contact方法 }
關(guān)于裝箱拆箱的問(wèn)題大家可以查看我之前的文章http://m.survivalescaperooms.com/aehyok/p/3504449.html
而以下代碼,字符串不會(huì)在運(yùn)行時(shí)進(jìn)行拼接,而是會(huì)在編譯時(shí)直接生成一個(gè)字符串。
private static void NewMethod3(){ string re2="123"+"abc"+"456"; ///該代碼等效于///string re2="123abc456"; }private static void NewMethod4(){ const string a="t"; string re="abc"+a; ///因?yàn)閍是一個(gè)常量,所以該代碼等效于string=re="abc"+"t"; 最終等效于string re="abct"; } 由于使用System.String類(lèi)會(huì)在某些場(chǎng)合帶來(lái)明顯的性能損耗,所以微軟另外提供了一個(gè)類(lèi)型StringBuilder來(lái)彌補(bǔ)String的不足。
StringBuilder并不會(huì)重新創(chuàng)建一個(gè)string對(duì)象,它的效率源于預(yù)先以非托管的方式分配內(nèi)存。如果StringBuilder沒(méi)有先定義長(zhǎng)度,則默認(rèn)分配的長(zhǎng)度為16。當(dāng)StringBuilder字符串長(zhǎng)度小于等于16時(shí),StringBuilder不會(huì)重新分配內(nèi)存;當(dāng)StringBuilder字符長(zhǎng)度大于16小于32時(shí),StringBuilder又會(huì)重新分配內(nèi)存,使之成為16的倍數(shù)。在上面的代碼中,如果預(yù)先判斷字符串的長(zhǎng)度將大于16,則可以為其設(shè)定一個(gè)更加合適的長(zhǎng)度(如32)。StringBuilder重新分配內(nèi)存時(shí)是按照上次容量加倍進(jìn)行分配的。當(dāng)然,我們需要注意,StringBuilder指定的長(zhǎng)度要合適,太小了,需要頻繁分配內(nèi)存,太大了,浪費(fèi)空間。
查看以下代碼,比較下面兩種字符串拼接方式,哪種效率更高:
private static void NewMethod1() { string a = "t"; a += "e"; a += "s"; a += "t"; } private static void NewMethod2() { string a = "t"; string b = "e"; string c = "s"; string d = "t"; string result = a + b + c + d; }結(jié)果可以得知:兩者的效率都不高。不要以為前者比后者創(chuàng)建的字符串對(duì)象更少,事實(shí)上,兩者創(chuàng)建的字符串對(duì)象相等,且前者進(jìn)行了3次string.Contact方法調(diào)用,比后者還多了兩次。
要完成這樣的運(yùn)行時(shí)字符串拼接(注意:是運(yùn)行時(shí)),更佳的做法是使用StringBuilder類(lèi)型,代碼如下所示:
public static void NewMethod() { ////定義了四個(gè)變量 string a = "t"; string b = "e"; string c = "s"; string d = "t"; StringBuilder sb = new StringBuilder(a); sb.Append(b); sb.Append(c); sb.Append(d); ///提示是運(yùn)行時(shí),所以沒(méi)有使用以下代碼 //StringBuilder sb = new StringBuilder("t"); //sb.Append("e"); //sb.Append("s"); //sb.Append("t"); //string result = sb.ToString(); }微軟還提供了另外一個(gè)方法來(lái)簡(jiǎn)化這種操作,即使用string.Format方法。string.Format方法在內(nèi)部使用StringBuilder進(jìn)行字符串的格式化,代碼如下所示:
public static void NewMethod4() { string a = "t"; string b = "e"; string c = "s"; string d = "t"; string result = string.Format("{0}{1}{2}{3}", a, b, c, d); }對(duì)于String和StringBuilder的簡(jiǎn)單介紹也可以參考我之前的一篇文章http://m.survivalescaperooms.com/aehyok/p/3505000.html
建議2、使用默認(rèn)轉(zhuǎn)型方法
1、使用類(lèi)型的轉(zhuǎn)換運(yùn)算符,其實(shí)就是使用類(lèi)型內(nèi)部的一方方法(即函數(shù))。轉(zhuǎn)換運(yùn)算符分為兩類(lèi):隱式轉(zhuǎn)換和顯式轉(zhuǎn)換(強(qiáng)制轉(zhuǎn)換)?;?lèi)型普遍都提供了轉(zhuǎn)換運(yùn)算符。
所謂“基元類(lèi)型”,是指編譯器直接支持的數(shù)據(jù)類(lèi)型。基元類(lèi)型包括:sbyte、byte、short、ushort、int、uint、long、ulong、char、float、double、bool、decimal、object、string。
int i = 0; float j = 0; j = i; ///int 到float存在一個(gè)隱式轉(zhuǎn)換 i = (int)j; ///float到int必須存在一個(gè)顯式轉(zhuǎn)換
用戶(hù)自定義的類(lèi)型也可以通過(guò)重載轉(zhuǎn)換運(yùn)算符的方式提供這一類(lèi)轉(zhuǎn)換:
public class ip { IPAddress value; public Ip(string ip) { value = IPAddress.Parse(ip); } //重載轉(zhuǎn)換運(yùn)算符,implicit 關(guān)鍵字用于聲明隱式的用戶(hù)定義類(lèi)型轉(zhuǎn)換運(yùn)算符。 public static implicit Operator Ip(string ip) { Ip iptemp = new Ip(ip); return iptemp; } //重寫(xiě)ToString方法 public override string ToString() { return value.ToString(); } } class Program { public static void Main(string[] args) { Ip ip = "192.168.1.1"; //通過(guò)Ip類(lèi)的重載轉(zhuǎn)換運(yùn)算符,實(shí)現(xiàn)字符串到Ip類(lèi)型的隱式轉(zhuǎn)換 Console.WriteLine(ip.ToString()); Console.ReadLine(); } }
提供的就是字符串到類(lèi)型Ip之間的隱式轉(zhuǎn)換。
2、使用類(lèi)型內(nèi)置的Parse、TryParse,或者如ToString、ToDouble、ToDateTime等方法
比如從string轉(zhuǎn)換為int,因?yàn)槠浣?jīng)常發(fā)生,所以int本身就提供了Parse和TryParse方法。一般情況下,如果要對(duì)某類(lèi)型進(jìn)行轉(zhuǎn)換操作,建議先查閱該類(lèi)型的API文檔。
3、使用幫助類(lèi)提供的方法
可以使用System.Convert類(lèi)、System.BitConverter類(lèi)來(lái)進(jìn)行類(lèi)型的轉(zhuǎn)換。
System.Convert提供了將一個(gè)基元類(lèi)型轉(zhuǎn)換為其他基元類(lèi)型的方法,如ToChar、ToBoolean方法等。值得注意的是,System.Convert還支持將任何自定義類(lèi)型轉(zhuǎn)換為任何基元類(lèi)型,只要自定義類(lèi)型繼承了IConvertible接口就可以。如上文中的IP類(lèi),如果將Ip轉(zhuǎn)換為string,除了重寫(xiě)Object的ToString方法外,還可以實(shí)現(xiàn)IConvertible的ToString()方法

繼承IConvertible接口必須同時(shí)實(shí)現(xiàn)其他轉(zhuǎn)型方法,如上文的ToBoolean、ToByte,如果不支持此類(lèi)轉(zhuǎn)型,則應(yīng)該拋出一個(gè)InvalidCastException,而不是一個(gè)NotImplementedException。
4、使用CLR支持的轉(zhuǎn)型
CLR支持的轉(zhuǎn)型,即上溯轉(zhuǎn)型和下溯轉(zhuǎn)型。這個(gè)概念首先是在java中提出來(lái)的,實(shí)際上就是基類(lèi)和子類(lèi)之間的相互轉(zhuǎn)換。
就比如: 動(dòng)作Animal類(lèi)、Dog類(lèi)繼承Animal類(lèi)、Cat類(lèi)也繼承自Amimal類(lèi)。在進(jìn)行子類(lèi)向基類(lèi)轉(zhuǎn)型的時(shí)候支持隱式轉(zhuǎn)換,如Dog顯然就是一個(gè)Animal;而當(dāng)Animal轉(zhuǎn)型為Dog的時(shí)候,必須是顯式轉(zhuǎn)換,因?yàn)锳nimal還可能是一個(gè)Cat。
Animal animal = new Animal(); Dog dog = new Dog(); animal = dog; /////隱式轉(zhuǎn)換,因?yàn)镈og就是Animal ///dog=animal; ////編譯不通過(guò) dog = (dog)animal; /////必須存在一個(gè)顯式轉(zhuǎn)換
建議3、區(qū)別對(duì)待強(qiáng)制轉(zhuǎn)換與as和is
首先來(lái)看一個(gè)簡(jiǎn)單的實(shí)例
FirstType firstType = new FirstType(); SecondType secondType = new SecondType(); secondType = (SecondType)firstType;
從上面的三行代碼可以看出,類(lèi)似上面的應(yīng)該就是強(qiáng)制轉(zhuǎn)換。
首先需要明確強(qiáng)制轉(zhuǎn)換可能意味這兩件不同的事情:
1、FirstType和SecondType彼此依靠轉(zhuǎn)換操作來(lái)完成兩個(gè)類(lèi)型之間的轉(zhuǎn)換。
2、FirstType是SecondType的基類(lèi)。
類(lèi)型之間如果存在強(qiáng)制轉(zhuǎn)換,那么它們之間的關(guān)系要么是第一種,要么是第二種。不可能同時(shí)是繼承的關(guān)系,又提供了轉(zhuǎn)型符。
針對(duì)第一種情況:
public class FirstType { public string Name { get; set; } } public class SecondType { public string Name { get; set; } public static explicit operator SecondType(FirstType firstType) { SecondType secondType = new SecondType() { Name = "轉(zhuǎn)型自:" + firstType.Name }; return secondType; } } class Program { static void Main(string[] args) { FirstType firstType = new FirstType() { Name="First Type"}; SecondType secondType = (SecondType)firstType; ///此轉(zhuǎn)換是成功的 secondType = firstType as SecondType; ///編譯不通過(guò) Console.ReadLine(); } }這里上面也有添加注釋?zhuān)ㄟ^(guò)強(qiáng)制轉(zhuǎn)換是可以轉(zhuǎn)換成功的,但是使用as運(yùn)算符是不成功的編譯就不通過(guò)。


這里就是通過(guò)轉(zhuǎn)換符進(jìn)行處理的結(jié)果。
接下來(lái)我們?cè)僭赑rogram類(lèi)中添加一個(gè)方法
static void DoWithSomeType(object obj) { ///編譯器首先判斷的是,SEOndType和ojbect之間有沒(méi)有繼承關(guān)系。 ///因?yàn)樵贑#中,所有的類(lèi)型都是繼承自object的,所以這里編譯沒(méi)有什么問(wèn)題。 ///但編譯器會(huì)自動(dòng)產(chǎn)生代碼來(lái)檢查obj在運(yùn)行時(shí)是不是SecondType,這樣就繞過(guò)了操作轉(zhuǎn)換符,導(dǎo)致轉(zhuǎn)換失敗。 SecondType secondType = (SecondType)obj; }如注釋所說(shuō)的,編譯通過(guò)執(zhí)行報(bào)錯(cuò)的問(wèn)題。
如果類(lèi)型之間都上溯到了某個(gè)共同的基類(lèi),那么根據(jù)此基類(lèi)進(jìn)行的轉(zhuǎn)換(即基類(lèi)轉(zhuǎn)型為子類(lèi)本身),應(yīng)該使用as。子類(lèi)與子類(lèi)之間的轉(zhuǎn)換,則應(yīng)該提供轉(zhuǎn)換操作符,以便進(jìn)行強(qiáng)制轉(zhuǎn)換。
現(xiàn)在可以如上方法改寫(xiě)為
static void DoWithSomeType(object obj) { SecondType secondType = obj as SecondType; }保證編譯執(zhí)行都不會(huì)報(bào)錯(cuò)。as操作符永遠(yuǎn)不會(huì)拋出異常,如果類(lèi)型不匹配(被轉(zhuǎn)換對(duì)象的運(yùn)行時(shí)類(lèi)型既不是所轉(zhuǎn)換的目標(biāo)類(lèi)型,也不是其派生類(lèi)型),或者轉(zhuǎn)型的源對(duì)象為null,那么轉(zhuǎn)型之后的值也為null。改造前的Do
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注