本文主要來學習記錄前三個建議。
建議1、正確操作字符串
建議2、使用默認轉型方法
建議3、區別對待強制轉換與as和is
其中有很多需要理解的東西,有些地方可能理解的不太到位,還望指正。
字符串應該是所有編程語言中使用最頻繁的一種基礎數據類型。如果使用不慎,我們就會為一次字符串的操作所帶來的額外性能開銷而付出代價。本條建議將從兩個方面來探討如何規避這類性能開銷:
1、確保盡量少的裝箱
2、避免分配額外的內存空間
先來介紹第一個方面,請看下面的兩行代碼:
String str1=+=+.ToString();
從IL代碼可以得知,第一行代碼在運行時完成一次裝箱的行為,而第二行代碼中並沒有發生裝箱的行為,它實際調用的是整型的ToString()方法,效率要比裝箱高。所以,在使用其他值引用類型到字符串的轉換並完成拼接時,應當避免使用操作符“+”來完成,而應該使用值引用類型提供的ToString()方法。
第二方面,避免分配額外的內存空間。對CLR來說,string對象(字符串對象)是個很特殊的對象,它一旦被賦值就不可改變。在運行時調用System.String類中的任何方法或進行任何運算(如“=”賦值、“+”拼接等),都會在內存中創建一個新的字符串對象,這也意味著要為該新對象分配新的內存空間。像下面的代碼就會帶來運行時的額外開銷。
s1==+s1+; re=+; }
關於裝箱拆箱的問題大家可以查看我之前的文章http://www.cnblogs.com/aehyok/p/3504449.html
而以下代碼,字符串不會在運行時進行拼接,而是會在編譯時直接生成一個字符串。
re2=++; a= re=+a; }
由於使用System.String類會在某些場合帶來明顯的性能損耗,所以微軟另外提供了一個類型StringBuilder來彌補String的不足。
StringBuilder並不會重新創建一個string對象,它的效率源於預先以非托管的方式分配內存。如果StringBuilder沒有先定義長度,則默認分配的長度為16。當StringBuilder字符串長度小於等於16時,StringBuilder不會重新分配內存;當StringBuilder字符長度大於16小於32時,StringBuilder又會重新分配內存,使之成為16的倍數。在上面的代碼中,如果預先判斷字符串的長度將大於16,則可以為其設定一個更加合適的長度(如32)。StringBuilder重新分配內存時是按照上次容量加倍進行分配的。當然,我們需要注意,StringBuilder指定的長度要合適,太小了,需要頻繁分配內存,太大了,浪費空間。
查看以下代碼,比較下面兩種字符串拼接方式,哪種效率更高:
a = += += += a = b = c = d = result = a + b + c +
結果可以得知:兩者的效率都不高。不要以為前者比後者創建的字符串對象更少,事實上,兩者創建的字符串對象相等,且前者進行了3次string.Contact方法調用,比後者還多了兩次。
要完成這樣的運行時字符串拼接(注意:是運行時),更佳的做法是使用StringBuilder類型,代碼如下所示:
a = b = c = d = = }
微軟還提供了另外一個方法來簡化這種操作,即使用string.Format方法。string.Format方法在內部使用StringBuilder進行字符串的格式化,代碼如下所示:
a = b = c = d = result = .Format(
對於String和StringBuilder的簡單介紹也可以參考我之前的一篇文章http://www.cnblogs.com/aehyok/p/3505000.html
1、使用類型的轉換運算符,其實就是使用類型內部的一方方法(即函數)。轉換運算符分為兩類:隱式轉換和顯式轉換(強制轉換)。基元類型普遍都提供了轉換運算符。
所謂“基元類型”,是指編譯器直接支持的數據類型。基元類型包括:sbyte、byte、short、ushort、int、uint、long、ulong、char、float、double、bool、decimal、object、string。
i = j = = i; i = ()j;
用戶自定義的類型也可以通過重載轉換運算符的方式提供這一類轉換:
Ip(= Ip(= Main(= ;
提供的就是字符串到類型Ip之間的隱式轉換。
2、使用類型內置的Parse、TryParse,或者如ToString、ToDouble、ToDateTime等方法
比如從string轉換為int,因為其經常發生,所以int本身就提供了Parse和TryParse方法。一般情況下,如果要對某類型進行轉換操作,建議先查閱該類型的API文檔。
3、使用幫助類提供的方法
可以使用System.Convert類、System.BitConverter類來進行類型的轉換。
System.Convert提供了將一個基元類型轉換為其他基元類型的方法,如ToChar、ToBoolean方法等。值得注意的是,System.Convert還支持將任何自定義類型轉換為任何基元類型,只要自定義類型繼承了IConvertible接口就可以。如上文中的IP類,如果將Ip轉換為string,除了重寫Object的ToString方法外,還可以實現IConvertible的ToString()方法
繼承IConvertible接口必須同時實現其他轉型方法,如上文的ToBoolean、ToByte,如果不支持此類轉型,則應該拋出一個InvalidCastException,而不是一個NotImplementedException。
4、使用CLR支持的轉型
CLR支持的轉型,即上溯轉型和下溯轉型。這個概念首先是在Java中提出來的,實際上就是基類和子類之間的相互轉換。
就比如: 動作Animal類、Dog類繼承Animal類、Cat類也繼承自Amimal類。在進行子類向基類轉型的時候支持隱式轉換,如Dog顯然就是一個Animal;而當Animal轉型為Dog的時候,必須是顯式轉換,因為Animal還可能是一個Cat。
Animal animal = = = dog; dog = (dog)animal;
首先來看一個簡單的實例
FirstType firstType = = = (SecondType)firstType;
從上面的三行代碼可以看出,類似上面的應該就是強制轉換。
首先需要明確強制轉換可能意味這兩件不同的事情:
1、FirstType和SecondType彼此依靠轉換操作來完成兩個類型之間的轉換。
2、FirstType是SecondType的基類。
類型之間如果存在強制轉換,那麼它們之間的關系要麼是第一種,要麼是第二種。不可能同時是繼承的關系,又提供了轉型符。
針對第一種情況:
Name { ; Name { ; = SecondType() { Name = + Main(= FirstType() { Name== (SecondType)firstType; secondType = firstType SecondType;
這裡上面也有添加注釋,通過強制轉換是可以轉換成功的,但是使用as運算符是不成功的編譯就不通過。
這裡就是通過轉換符進行處理的結果。
接下來我們再在Program類中添加一個方法
DoWithSomeType( SecondType secondType =
如注釋所說的,編譯通過執行報錯的問題。
如果類型之間都上溯到了某個共同的基類,那麼根據此基類進行的轉換(即基類轉型為子類本身),應該使用as。子類與子類之間的轉換,則應該提供轉換操作符,以便進行強制轉換。
現在可以如上方法改寫為
DoWithSomeType(= obj
保證編譯執行都不會報錯。as操作符永遠不會拋出異常,如果類型不匹配(被轉換對象的運行時類型既不是所轉換的目標類型,也不是其派生類型),或者轉型的源對象為null,那麼轉型之後的值也為null。改造前的DoWithSomeType方法會因為引發異常帶來效率問題,而使用as後,就可以完美的避免這種問題。
現在來看第二種情況,即FirstType是SecondType的基類。這種情況下,既可以使用強制轉型又可以使用as操作符。
Name { ; Main(= SecondType() { Name=== secondType
但是,即使可以使用強制轉型,從效率的角度來看,也建議大家使用as進行轉型。
下面再來看一下is操作符。
DoWithSomeType( (obj = obj
這個版本的效率顯然沒有上一個版本的效率高。因為當前這個版本進行了兩次類型檢測。但是,as操作符有個問題,就是它不能操作基元類型。如果涉及到基元類型的算法,那麼就要使用is進行判斷之後再進行轉型的操作,以避免轉型失敗。