改善C#程式的50種方法 條款3:操作符is或as優於強制轉型

來源:互聯網
上載者:User

C#是一門強型別語言。一般情況下,我們最好避免 將一個類型強制轉換為其他類型。但是,有時候運行時類型檢查是無法避免的。相信大家都寫過很多以System.Object類型為參數的函數,因為. NET架構預先為我們定義了這些函數的簽名。在這些函數內部,我們經常要把那些參數向下轉型為其他類型,或者是類,或者是介面。對於這種轉型,我們通常有 兩種選擇:使用as操作符,或者使用傳統C風格的強制轉型。另外還有一種比較保險的做法:先使用is來做一個轉換測試,然後再使用as操作符或者強制轉 型。

正確的選擇應該是儘可能地使用as操作符,因為它比強制轉型要安全,而且在運行時層面也有比較好的效率。需要注意的是,as和is操作符都不執行任何使用者自訂的轉換。只有當運行時類型與目標轉換類型匹配時,它們才會轉換成功。它們永遠不會在轉換過程中構造新的對象。

我們來看一個例子。假如需要將一個任意的對象轉換為一個MyType的執行個體。我們可能會像下面這樣來做:

object o = Factory.GetObject( );

// 第一個版本:

MyType t = o as MyType;

if ( t != null )

{

  // 處理t, t現在的類型為MyType。

} else

{

  // 報告轉型失敗。

}

或者,也可以像下面這樣來做:

object o = Factory.GetObject( );

// 第二個版本:

try {

  MyType t;

  t = ( MyType ) o;

  if ( t != null )

  {

    // 處理t, t現在的類型為MyType。

  } else

  {

    // 報告Null 參考失敗。

  }

} catch

{

  // 報告轉型失敗。

}

相信大家都同意第一個版本的轉型代碼更簡單,也更 容易閱讀。其中沒有添加額外的try/catch語句,因此也就避免了其帶來的負擔。注意,第二個版本中除了要捕捉異常外,還要對null的情況進行檢 查,因為如果o本來就是null,那麼強制轉型可以將它轉換成任何參考型別。但如果是as操作符,且被轉換對象為null,那麼執行結果將返回null。 因此,如果使用強制轉型,我們既要檢查其是否為null,還要捕捉異常。如果使用as操作符,我們只需要檢查返回的引用是否為null就可以了。

cast 和as操作符之間最大的區別就在於如何處理使用者自訂的轉換。操作符as和is都只檢查被轉換對象的運行時類型,並不執行其他的操作。如果被轉換對象的運 行時類型既不是所轉換的目標類型,也不是其衍生類別型,那麼轉型將告失敗。但是,強制轉型則會使用轉換操作符來執行轉型操作,這包括任何內建的數值轉換。例 如,將一個long類型強制轉換為一個short類型將會導致部分資訊丟失。

條款3:操作符is或as優於強制轉型  21

 

在我們使用使用者自訂的轉換時,也會有同樣的問題,來看下面的代碼:

public class SecondType

{

  private MyType _value;

  // 忽略其他細節。

  // 轉換操作符。

  // 將SecondType 轉換為MyType,參見條款29。[4]

  public static implicit operator

    MyType( SecondType t )

  {

    return t._value;

  }

}

假設下面第一行代碼中的Factory.GetObject()返回的是一個SecondType對象:

object o = Factory.GetObject( );

// o 為一個SecondType:

MyType t = o as MyType; // 轉型失敗,o的類型不是MyType。

if ( t != null )

{

  // 處理t, t現在的類型為MyType。

} else

{

  // 報告轉型失敗。

}

// 第二個版本:

try {

  MyType t1;

  t1 = ( MyType ) o; // 轉型失敗,o的類型不是MyType。

  if ( t1 != null )

  {

    // 處理t1, t1現在的類型為MyType。

  } else

  {

    // 報告Null 參考失敗。

  }

} catch

{

  // 報告轉型失敗。

}

兩個版本的轉型操作都失敗了。大家應該還記得我前 面說過強制轉型會執行使用者自訂的轉換,有讀者據此認為強制轉型的那個版本會成功。這麼想本身沒有錯誤,只是編譯器在產生代碼時依據的是對象o的編譯時間類 型。編譯器對於o的運行時類型一無所知——編譯器只知道o的類型是System.Object。因此編譯器只會檢查是否存在將System.Object 轉換為MyType的使用者自訂轉換。它會到System.Object類型和MyType類型的定義中去做這樣的檢查。由於沒有找到任何使用者自訂轉 換,編譯器將產生代碼來檢查o的運行時類型,並將其和MyType進行比對。由於o的運行時類型為SecondType,因此轉型將告失敗。編譯器不會檢 查在o的運行時類型SecondType和MyType之間是否存在使用者自訂的轉換。

當然,如果將上述代碼做如下修改,轉換就會成功執行:

object o = Factory.GetObject( );

// 第三個版本:

SecondType st = o as SecondType;

try {

  MyType t;

  t = ( MyType ) st;

  if ( t != null )

  {

    // 處理t, t現在的類型為MyType。

  } else

  {

    // 報告Null 參考失敗。

  }

} catch

{

條款3:操作符is或as優於強制轉型  24

 

  // 報告轉型失敗。

}

在正式的開發中,我們絕不能寫如此醜陋的代碼,但它卻向我們揭示了問題的所在。雖然大家永遠都不可能像上面那樣寫代碼,但可以使用一個以System.Object類型為參數的函數,讓該函數在內部執行正確的轉換。

object o = Factory.GetObject( );

DoStuffWithObject( o );

private void DoStuffWithObject( object o2 )

{

  try {

    MyType t;

    t = ( MyType ) o2; // 轉型失敗,o的類型不是MyType

    if ( t != null )

    {

      // 處理t, t現在的類型為MyType。

    } else

    {

      // 報告Null 參考失敗。

    }

  } catch

  {

    // 報告轉型失敗。

  }

}

記住,使用者自訂的轉換操作符只作用於對象的編譯時間類型,而非運行時類型上。至於o2的運行時類型和MyType之間是否存在轉換,並不重要。事實上,編譯器對此並不瞭解,也不關心。對於下面的語句,如果st的宣告類型不同,會有不同的行為:

t = ( MyType ) st;

但對於下面的語句,不管st的宣告類型是什麼,都會產生同樣的結果[5]。因此,我們說as操作符要優於強制轉型——它的轉型結果相對比較一致。

但如果as操作符兩邊的類型沒有繼承關係,即使存在使用者自訂轉換操作符,也會產生編譯時間錯誤。例如,下面的語句:

t = st as MyType;

我們已經知道在轉型的時候應該儘可能地使用as操作符。下面我們來談談一些不能使用as操作符的情況。首先,as操作符不能應用於實值型別。例如,下面的代碼編譯的時候就會報錯:

object o = Factory.GetValue( );

int i = o as int; // 不能通過編譯。

這是因為int是一個實值型別,所以不可以為null。如果o不是一個整數,那這個i裡面還能存放什麼呢?存入的任何值都必須是有效整數,所以as不能和實值型別一起使用。那就只能使用強制轉型了:

object o = Factory.GetValue( );

int i = 0;

try {

  i = ( int ) o;

} catch

{

  i = 0;

}

但是,我們也並非只能這樣。我們還可以使用is語句來避免其中對異常的檢查或者強制轉型:

object o = Factory.GetValue( );

int i = 0;

if ( o is int )

  i = ( int ) o;

如果o是某個其他可以轉換為int的類型,例如double,那麼is操作符將返回false。如果o的值為null,is操作符也將返回false。

只有當我們不能使用as操作符來進行類型轉換時,才應該使用is操作符。否則,使用is將會帶來代碼的冗餘:

// 正確, 但是冗餘:

object o = Factory.GetObject( );

MyType t = null;

條款3:操作符is或as優於強制轉型  26

 

if ( o is MyType )

  t = o as MyType;

上面的代碼和下面的代碼事實上是一樣的:

// 正確, 但是冗餘:

object o = Factory.GetObject( );

MyType t = null;

if ( ( o as MyType ) != null )

  t = o as MyType;

這種做法顯然既不高效,也顯得冗餘。如果我們打算使用as來做轉型,那麼再使用is檢查就沒有必要了。直接將as操作符的運算結果和null進行比對就可以了,這樣比較簡單。

既然我們已經明白了is操作符、as操作符和強制轉型之間的差別,那麼大家猜猜看foreach迴圈語句中使用的是哪個操作符來執行類型轉換呢?

public void UseCollection( IEnumerable theCollection )

{

  foreach ( MyType t in theCollection )

    t.DoStuff( );

}

答案是強制轉型。事實上,下面的代碼和上面foreach語句編譯後的結果是一樣的:

public void UseCollection( IEnumerable theCollection )

{

  IEnumerator it = theCollection.GetEnumerator( );

  while ( it.MoveNext( ) )

  {

    MyType t = ( MyType ) it.Current;

    t.DoStuff( );

  }

}

之所以使用強制轉型,是因為foreach語句需要同時支援實值型別和參考型別。無論轉換的目標類型是什麼,foreach語句都可以展現相同的行為。但是,由於使用的是強制轉型,foreach語句可能產生BadCastException異常[6]。

由於IEnumerator.Current返回 的是System.Object,而Object中又沒有定義任何的轉換操作符,因此轉換操作符就不必考慮了。如果集合中是一組SecondType對 象,那麼運用在UseCollection()函數中將會出現轉型失敗,因為foreach語句使用的是強制轉型,而強制轉型並不關心集合元素的運行時類 型。它只檢查在System.Object類(由IEnumerator.Current返回的類型)和迴圈變數的宣告類型MyType之間是否存在轉 換。

最後,有時候我們可能想知道一個對象的確切類型, 而並不關心它是否可以轉換為另一種類型。如果一個類型繼承自另一個類型,那麼is操作符將返回true。使用System.Object的GetType ()方法,可以得到一個對象的運行時類型。利用該方法可以對類型進行比is或as更為嚴格的測試,因為我們可以拿它所返回的對象的類型和一個具體的類型做 對比。

再來看下面的函數:

public void UseCollection( IEnumerable theCollection )

{

  foreach ( MyType t in theCollection )

    t.DoStuff( );

}

如果建立了一個繼承自MyType的類NewType,那便可以將一組NewType對象集合應用在UseCollection函數中。

public class NewType : MyType

{

  // 忽略實現細節。

}

如果我們打算編寫一個函數來處理所有與 MyType類型相容的執行個體對象,那麼UseCollection函數所展示的做法就挺好。但如果打算編寫的函數只處理運行時類型為MyType的對象, 那就應該使用GetType()方法來對類型做精確的測試。我們可以將這種測試放在foreach迴圈中。運行時類型測試最常用的地方就是相等判斷(參見 條款9)。對於絕大多數其他的情況,as和is操作符提供的.isinst比較[7]在語義上都是正確的。

 

條款4:使用Conditional特性代替#if條件編譯  27

 

好的物件導向實踐一般都告誡我們要避免轉型,但有時候我們別無選擇。不能避免轉型時,我們應該儘可能地使用C#語言中提供的as和is操作符來更清晰地表 達意圖。不同的轉型方式有不同的規則,is和as操作符絕大多數情況下都能滿足我們的要求,只有當被測試的對象是正確的類型時,它們才會成功。一般情況下 不要使用強制轉型,因為它可能會帶來意想不到的負面效應,而且成功或者失敗往往在我們的預料之外。 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.