Effective C#: Item 3: Prefer the is or as Operators to Casts
?
Item 3: Prefer the is or as Operators to Casts
C#是強(qiáng)類型語言.我們要盡量避免類型轉(zhuǎn)換.
有時(shí)我們必須要在runtime檢查一個(gè)變量的類型.比如有時(shí)你要用到一些.Net framework提供的方法,這些方法需要用到System.Object類型的參數(shù).你需要把這些object (方法的參數(shù))向下cast成其他的類型(類或者interface),這時(shí)你有兩個(gè)基本方式可以選擇,一是使用as操作符,二是使用C語言風(fēng)格的cast.兩者也可以結(jié)合成一個(gè)更加保險(xiǎn)的方法,就是先用is 操作符來測試類型的轉(zhuǎn)換,然后再用cast或者as 操作符進(jìn)行轉(zhuǎn)換.
正確的選擇應(yīng)該是使用as操作符來進(jìn)行類型轉(zhuǎn)換. as操作符比碰運(yùn)氣型的cast更加的安全,而且在runtime更加的高效. as和is操作符并不能進(jìn)行所有的用戶定義的類型轉(zhuǎn)換, 只有當(dāng)runtime類型和目標(biāo)類型一致時(shí)轉(zhuǎn)換操作才會成功.它們永遠(yuǎn)不會為了滿足程序調(diào)用請求而創(chuàng)建一個(gè)新的object.
在下例中,你需要把一個(gè)object轉(zhuǎn)換成一個(gè)MyType的實(shí)例,你可以這樣實(shí)現(xiàn):
????????????????? object o = Factory.GetObject( );<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
?
????????????????? // Version one:
????????????????? MyType t = o as MyType;
?
????????????????? if ( t != null )
????????????????? {
??????????????????????? // work with t, it's a MyType.
????????????????? }
????????????????? else
????????????????? {
??????????????????????? // report the failure.
????????????????? }
也可以這樣寫:
????????????????? object o = Factory.GetObject( );
?
????????????????? // Version two:
????????????????? try
????????????????? {
??????????????????????? MyType t;
??????????????????????? t = ( MyType ) o;
??????????????????????? if ( t != null )
??????????????????????? {
????????????????????????????? // work with T, it's a MyType.
??????????????????????? }
??????????????????????? else
??????????????????????? {
????????????????????????????? // Report a null reference failure.
??????????????????????? }
????????????????? }
????????????????? catch
????????????????? {
??????????????????????? // report the conversion failure.
????????????????? }
第一個(gè)方法明顯更加的簡單易讀,而且也沒有try/catch的overhead.代碼更加的高效.我們注意到cast版本不僅要檢查轉(zhuǎn)換后的object是否為null,還要catch異常.但as操作符版本卻不用. 這是因?yàn)槭褂?/span>cast時(shí), null可以被轉(zhuǎn)換成任何一種reference類型,但是當(dāng)應(yīng)用as操作符在一個(gè)null reference上時(shí)會返回null. 所以as操作符只需檢查一下返回的reference是否為null, 而不用catch異常.
as操作符和cast操作符最大的不同是如何對待用戶定義的轉(zhuǎn)換. as和is操作符只會檢查被轉(zhuǎn)換的object的runtime類型,而不做任何其他的工作. 如果這個(gè)object不是目標(biāo)類型,或者不是目標(biāo)類型的子類型, 操作失敗并終止. 但cast操作符卻不同,它會把object轉(zhuǎn)換成目標(biāo)類型, 這包括所有的numeric轉(zhuǎn)換, 比如從long轉(zhuǎn)換成short, object的一些信息就在這種轉(zhuǎn)換中丟失了.
而且當(dāng)cast用戶自定義類型時(shí)也會有同樣的問題.比如:
????? public class SecondType
????? {
??????????? private MyType _value;
?
??????????? // other details elided
?
??????????? // Conversion operator.
??????????? // This converts a SecondType to
??????????? // a MyType, see item 29.
??????????? public static implicit operator
????????????????? MyType( SecondType t )
??????????? {
????????????????? return t._value;
??????????? }
????? }
假設(shè)我們用Factory.GetObject()生成了一個(gè)SecondType的object,并把它轉(zhuǎn)換成MyType類型.
????????????????? object o = Factory.GetObject( );
?
????????????????? // o is a SecondType:
????????????????? MyType t = o as MyType; // Fails. o is not MyType
?
????????????????? if ( t != null )
????????????????? {
??????????????????????? // work with t, it's a MyType.
????????????????? }
????????????????? else
????????????????? {
??????????????????????? // report the failure.
????????????????? }
?
????????????????? // Version two:
????????????????? try
????????????????? {
??????????????????????? MyType t1;
??????????????????????? t = ( MyType ) o; // Fails. o is not MyType
??????????????????????? if ( t1 != null )
??????????????????????? {
????????????????????????????? // work with t1, it's a MyType.
??????????????????????? }
??????????????????????? else
??????????????????????? {
????????????????????????????? // Report a null reference failure.
??????????????????????? }
????????????????? }
????????????????? catch
????????????????? {
??????????????????????? // report the conversion failure.
????????????????? }
兩個(gè)版本都會失敗.但是cast卻執(zhí)行了用戶定義的轉(zhuǎn)換.這種假象使你認(rèn)為cast成功了.但實(shí)際上它是失敗的,因?yàn)榫幾g器會根據(jù)編譯時(shí)object的類型來生成代碼.編譯器對運(yùn)行時(shí)object的類型一無所知.它只是把o當(dāng)作System.Object的一個(gè)實(shí)例.編譯器沒有發(fā)現(xiàn)從System.Object到MyType可行的轉(zhuǎn)換.它檢查System.Object和MyType的定義,因?yàn)槿鄙儆脩舳x的轉(zhuǎn)換信息,編譯器生成代碼來檢查o的運(yùn)行時(shí)的類型,然后檢查它是不是MyType類型.因?yàn)?/span>o是SecondType類型,所以轉(zhuǎn)換失敗.編譯器并不檢查o在運(yùn)行時(shí)的類型是否可以轉(zhuǎn)換成MyType類型.
如果你想讓轉(zhuǎn)換成功,可以這樣寫代碼:
????????????????? object o = Factory.GetObject( );
?
????????????????? // Version three:
????????????????? SecondType st = o as SecondType;
????????????????? try
????????????????? {
??????????????????????? MyType t;
??????????????????????? t = ( MyType ) st;
??????????????????????? if ( t != null )
??????????????????????? {
????????????????????????????? // work with T, it's a MyType.
??????????????????????? }
??????????????????????? else
??????????????????????? {
????????????????????????????? // Report a null reference failure.
??????????????????????? }
????????????????? }
????????????????? catch
????????????????? {
??????????????????????? // report the failure.
????????????????? }
你永遠(yuǎn)也不應(yīng)該寫這樣丑陋的代碼,但這也是一個(gè)常見的問題.盡管你永遠(yuǎn)不應(yīng)該寫這樣的代碼,但你可以用System.Object來當(dāng)作一個(gè)進(jìn)行轉(zhuǎn)換操作的function的參數(shù),比如:
??????????? object o = Factory.GetObject( );
?
??????????? DoStuffWithObject( o );
?
??????????? private void DoStuffWithObject( object o2 )
??????????? {
????????????????? try
????????????????? {
??????????????????????? MyType t;
??????????????????????? t = ( MyType ) o2; // Fails. o is not MyType
??????????????????????? if ( t != null )
??????????????????????? {
????????????????????????????? // work with T, it's a MyType.
??????????????????????? }
??????????????????????? else
??????????????????????? {
????????????????????????????? // Report a null reference failure.
??????????????????????? }
????????????????? }
????????????????? catch
????????????????? {
??????????????????????? // report the conversion failure.
????????????????? }
??????????? }
用戶自定義的轉(zhuǎn)換只作用于編譯時(shí)object的類型,而不時(shí)運(yùn)行時(shí)的類型. 至于運(yùn)行時(shí)是否存在o2和MyType類型的轉(zhuǎn)換, 編譯器不知道也根本不關(guān)心. 但當(dāng)st類型不同時(shí),這個(gè)語句有著不同的表現(xiàn):
t = ( MyType ) st;上面的語句會調(diào)用用戶自定義的轉(zhuǎn)換,從而造成轉(zhuǎn)換成功的假象. 但使用as操作符的語句卻有著一致的表現(xiàn).所以應(yīng)該盡量的使用as操作符.如下面的語句:
t = st as MyType;?
事實(shí)上,如果st和MyType之間不存在繼承關(guān)系的話,而是通過一個(gè)用戶自定義的轉(zhuǎn)換來進(jìn)行類型轉(zhuǎn)換,那么編譯器會報(bào)告一個(gè)錯(cuò)誤.
現(xiàn)在你知道了應(yīng)該盡可能的使用as操作符.但也有一些情況不能使用它.as操作符不能作用于value type上.下面的這個(gè)語句不會通過編譯:
object o = Factory.GetValue( );
????????????????? int i = o as int; // Does not compile.
因?yàn)?/span>int是值類型,永遠(yuǎn)不能為null.那么如果o不是整數(shù)類型的話, i里面應(yīng)該存什么值呢? 所以你不能使用as操作符.你可以用下面這種變通的方式:
????????????????? object o = Factory.GetValue( );
????????????????? int i = 0;
????????????????? try
????????????????? {
??????????????????????? i = ( int ) o;
????????????????? }
????????????????? catch
????????????????? {
??????????????????????? i = 0;
????????????????? }
但你不必一定這樣一來做,不要忘了is操作符,你可以在轉(zhuǎn)換之前先判斷o的類型:
????????????????? object o = Factory.GetValue( );
????????????????? int i = 0;
????????????????? if ( o is int )
??????????????????????? i = ( int ) o;
如果o不是整數(shù)類型,那么is操作符返回false. is操作符作用于null arguments上時(shí),永遠(yuǎn)返回false;
但你應(yīng)該只在你不能使用as操作符轉(zhuǎn)換類型時(shí)使用is,否則就是重復(fù)的, 比如:
????????????????? // correct, but redundant:
????????????????? object o = Factory.GetObject( );
?
????????????????? MyType t = null;
????????????????? if ( o is MyType )
??????????????????????? t = o as MyType;
上面的代碼和下面的代碼是等效的:
????????????????? // correct, but redundant:
????????????????? object o = Factory.GetObject( );
?
????????????????? MyType t = null;
????????????????? if ( ( o as MyType ) != null )
??????????????????????? t = o as MyType;
可以看出,進(jìn)行了兩次轉(zhuǎn)換,低效而且重復(fù).如果你已經(jīng)決定了要使用as操作符來轉(zhuǎn)換類型,那么只需檢查返回值是否為null就可以了.
現(xiàn)在你已經(jīng)明白了as, is和cast,那么foreach循環(huán)用的是什么操作符呢?
??????????? public void UseCollection( IEnumerable theCollection )
??????????? {
????????????????? foreach ( MyType t in theCollection )
??????????????????????? t.DoStuff( );
??????????? }
foreach用的實(shí)際上是cast操作符.上面的代碼可以重寫成下面的代碼:
??????????? public void UseCollection( IEnumerable theCollection )
??????????? {
????????????????? IEnumerator it = theCollection.GetEnumerator( );
????????????????? while ( it.MoveNext( ) )
????????????????? {
??????????????????????? MyType t = ( MyType ) it.Current;
??????????????????????? t.DoStuff( );
????????????????? }
??????????? }
這是因?yàn)?/span>foreach要用cast來支持value type和reference type. 如果使用as操作符的話,foreach語句仍表現(xiàn)相同的行為,但會拋出BadCastException,因?yàn)?/span>as不能作用于值類型上.
因?yàn)?/span>IEnumerator.Current返回一個(gè)System.Object類型的object,而這個(gè)object不具備轉(zhuǎn)換操作,所以并不能用于這個(gè)測試. SecondType類型的collection也不能用于UseCollection()因?yàn)檗D(zhuǎn)換會失敗. Foreach語句并不檢查collection中object的運(yùn)行時(shí)類型是否支持這種轉(zhuǎn)換,它只檢查IEnumerator.Current所返回的System.Object類型是否支持到目標(biāo)類型(本例中的MyType)之間的轉(zhuǎn)換.
最后,有時(shí)你想知道一個(gè)object確切的類型,而不只是關(guān)心這個(gè)object是否可以轉(zhuǎn)換成目標(biāo)類型. 因?yàn)?/span>as操作符對于所有從目標(biāo)類型繼承而來的類型的轉(zhuǎn)換都返回true. 但GetType()方法返回object運(yùn)行時(shí)的類型,它比as或者is操作符提供的測試都要更嚴(yán)格. 它返回的是object的確切類型.
再看一下UseCollection():
??????????? public void UseCollection( IEnumerable theCollection )
??????????? {
????????????????? foreach ( MyType t in theCollection )
??????????????????????? t.DoStuff( );
??????????? }
如果你創(chuàng)建一個(gè)叫NewType的類,這個(gè)類繼承MyType,那么NewType objects的collection也可以在UseCollection()中很好的工作.
????? public class NewType : MyType
????? {
??????????? // contents elided.
????? }
如果你的意圖是寫出一個(gè)可以使用所有MyType類型(自身或繼承而來)的function時(shí),這沒有什么.但如果你的意圖是寫一個(gè)只接受MyType類型自身的function的話,你就要用確切的類型來進(jìn)行比較. 在本例中,你可以在foreach循環(huán)里做. 知道運(yùn)行時(shí)確切的類型只有在做equality測試時(shí)是非常重要的.在大多數(shù)其他的情況下,as和is操作符提供的isinst比較是語法上正確的.
好的OO經(jīng)驗(yàn)告訴我們要盡量避免類型的轉(zhuǎn)換,但有時(shí)類型轉(zhuǎn)換是必需的.在這種情況下,盡量的使用as和is操作符來表達(dá)你的意圖. 不同的類型強(qiáng)制轉(zhuǎn)換有不同的規(guī)則,但as和is卻在絕大多數(shù)情況下都是正確的,而且它們只有在object是正確的類型時(shí)才轉(zhuǎn)換成功. Cast操作符會帶來一些副作用,而且轉(zhuǎn)換的成功與失敗往往出乎意料.
??
?
?
本系列文章只是作者讀書筆記,版權(quán)完全屬于原作者 (Bill Wagner),任何人及組織不得以任何理由以商業(yè)用途使用本文,任何對本文的引用和轉(zhuǎn)載必須通知作者:zphillm@hotmail.com
轉(zhuǎn)載于:https://www.cnblogs.com/ZphillM/archive/2005/08/06/208713.html
總結(jié)
以上是生活随笔為你收集整理的Effective C#: Item 3: Prefer the is or as Operators to Casts的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 根据IP地址获取主机名称
- 下一篇: .net性能测试和优化1 基本概念