天天看點

編寫高品質代碼改善C#程式的157個建議[正确操作字元串、使用預設轉型方法、卻别對待強制轉換與as和is]

前言

  本文主要來學習記錄前三個建議。

  建議1、正确操作字元串

  建議2、使用預設轉型方法

  建議3、差別對待強制轉換與as和is

其中有很多需要了解的東西,有些地方可能了解的不太到位,還望指正。

建議1、正确操作字元串

  字元串應該是所有程式設計語言中使用最頻繁的一種基礎資料類型。如果使用不慎,我們就會為一次字元串的操作所帶來的額外性能開銷而付出代價。本條建議将從兩個方面來探讨如何規避這類性能開銷:

  1、確定盡量少的裝箱

  2、避免配置設定額外的記憶體空間

先來介紹第一個方面,請看下面的兩行代碼:

從IL代碼可以得知,第一行代碼在運作時完成一次裝箱的行為,而第二行代碼中并沒有發生裝箱的行為,它實際調用的是整型的ToString()方法,效率要比裝箱高。是以,在使用其他值引用類型到字元串的轉換并完成拼接時,應當避免使用操作符“+”來完成,而應該使用值引用類型提供的ToString()方法。

第二方面,避免配置設定額外的記憶體空間。對CLR來說,string對象(字元串對象)是個很特殊的對象,它一旦被指派就不可改變。在運作時調用System.String類中的任何方法或進行任何運算(如“=”指派、“+”拼接等),都會在記憶體中建立一個新的字元串對象,這也意味着要為該新對象配置設定新的記憶體空間。像下面的代碼就會帶來運作時的額外開銷。

而以下代碼,字元串不會在運作時進行拼接,而是會在編譯時直接生成一個字元串。

由于使用System.String類會在某些場合帶來明顯的性能損耗,是以微軟另外提供了一個類型StringBuilder來彌補String的不足。

StringBuilder并不會重新建立一個string對象,它的效率源于預先以非托管的方式配置設定記憶體。如果StringBuilder沒有先定義長度,則預設配置設定的長度為16。當StringBuilder字元串長度小于等于16時,StringBuilder不會重新配置設定記憶體;當StringBuilder字元長度大于16小于32時,StringBuilder又會重新配置設定記憶體,使之成為16的倍數。在上面的代碼中,如果預先判斷字元串的長度将大于16,則可以為其設定一個更加合适的長度(如32)。StringBuilder重新配置設定記憶體時是按照上次容量加倍進行配置設定的。當然,我們需要注意,StringBuilder指定的長度要合适,太小了,需要頻繁配置設定記憶體,太大了,浪費空間。

檢視以下代碼,比較下面兩種字元串拼接方式,哪種效率更高:

  結果可以得知:兩者的效率都不高。不要以為前者比後者建立的字元串對象更少,事實上,兩者建立的字元串對象相等,且前者進行了3次string.Contact方法調用,比後者還多了兩次。

  要完成這樣的運作時字元串拼接(注意:是運作時),更佳的做法是使用StringBuilder類型,代碼如下所示:

微軟還提供了另外一個方法來簡化這種操作,即使用string.Format方法。string.Format方法在内部使用StringBuilder進行字元串的格式化,代碼如下所示:

建議2、使用預設轉型方法

1、使用類型的轉換運算符,其實就是使用類型内部的一方方法(即函數)。轉換運算符分為兩類:隐式轉換和顯式轉換(強制轉換)。基元類型普遍都提供了轉換運算符。

所謂“基元類型”,是指編譯器直接支援的資料類型。基元類型包括:sbyte、byte、short、ushort、int、uint、long、ulong、char、float、double、bool、decimal、object、string。

使用者自定義的類型也可以通過重載轉換運算符的方式提供這一類轉換:

提供的就是字元串到類型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()方法

編寫高品質代碼改善C#程式的157個建議[正确操作字元串、使用預設轉型方法、卻别對待強制轉換與as和is]

 繼承IConvertible接口必須同時實作其他轉型方法,如上文的ToBoolean、ToByte,如果不支援此類轉型,則應該抛出一個InvalidCastException,而不是一個NotImplementedException。

4、使用CLR支援的轉型

CLR支援的轉型,即上溯轉型和下溯轉型。這個概念首先是在Java中提出來的,實際上就是基類和子類之間的互相轉換。

就比如: 動作Animal類、Dog類繼承Animal類、Cat類也繼承自Amimal類。在進行子類向基類轉型的時候支援隐式轉換,如Dog顯然就是一個Animal;而當Animal轉型為Dog的時候,必須是顯式轉換,因為Animal還可能是一個Cat。

建議3、差別對待強制轉換與as和is

首先來看一個簡單的執行個體

從上面的三行代碼可以看出,類似上面的應該就是強制轉換。

首先需要明确強制轉換可能意味這兩件不同的事情:

1、FirstType和SecondType彼此依靠轉換操作來完成兩個類型之間的轉換。

2、FirstType是SecondType的基類。

類型之間如果存在強制轉換,那麼它們之間的關系要麼是第一種,要麼是第二種。不可能同時是繼承的關系,又提供了轉型符。

針對第一種情況:

這裡上面也有添加注釋,通過強制轉換是可以轉換成功的,但是使用as運算符是不成功的編譯就不通過。

編寫高品質代碼改善C#程式的157個建議[正确操作字元串、使用預設轉型方法、卻别對待強制轉換與as和is]
編寫高品質代碼改善C#程式的157個建議[正确操作字元串、使用預設轉型方法、卻别對待強制轉換與as和is]

這裡就是通過轉換符進行處理的結果。

接下來我們再在Program類中添加一個方法

如注釋所說的,編譯通過執行報錯的問題。

如果類型之間都上溯到了某個共同的基類,那麼根據此基類進行的轉換(即基類轉型為子類本身),應該使用as。子類與子類之間的轉換,則應該提供轉換操作符,以便進行強制轉換。

現在可以如上方法改寫為

保證編譯執行都不會報錯。as操作符永遠不會抛出異常,如果類型不比對(被轉換對象的運作時類型既不是所轉換的目标類型,也不是其派生類型),或者轉型的源對象為null,那麼轉型之後的值也為null。改造前的DoWithSomeType方法會因為引發異常帶來效率問題,而使用as後,就可以完美的避免這種問題。

現在來看第二種情況,即FirstType是SecondType的基類。這種情況下,既可以使用強制轉型又可以使用as操作符。

但是,即使可以使用強制轉型,從效率的角度來看,也建議大家使用as進行轉型。

下面再來看一下is操作符。

這個版本的效率顯然沒有上一個版本的效率高。因為目前這個版本進行了兩次類型檢測。但是,as操作符有個問題,就是它不能操作基元類型。如果涉及到基元類型的算法,那麼就要使用is進行判斷之後再進行轉型的操作,以避免轉型失敗。

編寫高品質代碼改善C#程式的157個建議[正确操作字元串、使用預設轉型方法、卻别對待強制轉換與as和is]

繼續閱讀