Item 3: Prefer the is or as Operators to Casts

C#是强类型语言.我们要尽量避免类型转换.

有时我们必须要在runtime检查一个变量的类型.比如有时你要用到一些.Net framework提供的方法,这些方法需要用到System.Object类型的参数.你需要把这些object (方法的参数)向下cast成其他的类型(类或者interface),这时你有两个基本方式可以选择,一是使用as操作符,二是使用C语言风格的cast.两者也可以结合成一个更加保险的方法,就是先用is 操作符来测试类型的转换,然后再用cast或者as 操作符进行转换.

正确的选择应该是使用as操作符来进行类型转换. as操作符比碰运气型的cast更加的安全,而且在runtime更加的高效. as和is操作符并不能进行所有的用户定义的类型转换, 只有当runtime类型和目标类型一致时转换操作才会成功.它们永远不会为了满足程序调用请求而创建一个新的object.

在下例中,你需要把一个object转换成一个MyType的实例,你可以这样实现:

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.

}

第一个方法明显更加的简单易读,而且也没有try/catch的overhead.代码更加的高效.我们注意到cast版本不仅要检查转换后的object是否为null,还要catch异常.但as操作符版本却不用. 这是因为使用cast时, null可以被转换成任何一种reference类型,但是当应用as操作符在一个null reference上时会返回null. 所以as操作符只需检查一下返回的reference是否为null, 而不用catch异常.

as操作符和cast操作符最大的不同是如何对待用户定义的转换. as和is操作符只会检查被转换的object的runtime类型,而不做任何其他的工作. 如果这个object不是目标类型,或者不是目标类型的子类型, 操作失败并终止. 但cast操作符却不同,它会把object转换成目标类型, 这包括所有的numeric转换, 比如从long转换成short, object的一些信息就在这种转换中丢失了.

而且当cast用户自定义类型时也会有同样的问题.比如:

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;

}

}

假设我们用Factory.GetObject()生成了一个SecondType的object,并把它转换成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.

}

两个版本都会失败.但是cast却执行了用户定义的转换.这种假象使你认为cast成功了.但实际上它是失败的,因为编译器会根据编译时object的类型来生成代码.编译器对运行时object的类型一无所知.它只是把o当作System.Object的一个实例.编译器没有发现从System.Object到MyType可行的转换.它检查System.Object和MyType的定义,因为缺少用户定义的转换信息,编译器生成代码来检查o的运行时的类型,然后检查它是不是MyType类型.因为o是SecondType类型,所以转换失败.编译器并不检查o在运行时的类型是否可以转换成MyType类型.

如果你想让转换成功,可以这样写代码:

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.

}

你永远也不应该写这样丑陋的代码,但这也是一个常见的问题.尽管你永远不应该写这样的代码,但你可以用System.Object来当作一个进行转换操作的function的参数,比如:

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.

}

}

用户自定义的转换只作用于编译时object的类型,而不时运行时的类型. 至于运行时是否存在o2和MyType类型的转换, 编译器不知道也根本不关心. 但当st类型不同时,这个语句有着不同的表现:

t = ( MyType ) st;

上面的语句会调用用户自定义的转换,从而造成转换成功的假象. 但使用as操作符的语句却有着一致的表现.所以应该尽量的使用as操作符.如下面的语句:

t = st as MyType;

事实上,如果st和MyType之间不存在继承关系的话,而是通过一个用户自定义的转换来进行类型转换,那么编译器会报告一个错误.

现在你知道了应该尽可能的使用as操作符.但也有一些情况不能使用它.as操作符不能作用于value type上.下面的这个语句不会通过编译:

object o = Factory.GetValue( );

int i = o as int; // Does not compile.

因为int是值类型,永远不能为null.那么如果o不是整数类型的话, i里面应该存什么值呢? 所以你不能使用as操作符.你可以用下面这种变通的方式:

object o = Factory.GetValue( );

int i = 0;

try

{

i = ( int ) o;

}

catch

{

i = 0;

}

但你不必一定这样一来做,不要忘了is操作符,你可以在转换之前先判断o的类型:

object o = Factory.GetValue( );

int i = 0;

if ( o is int )

i = ( int ) o;

如果o不是整数类型,那么is操作符返回false. is操作符作用于null arguments上时,永远返回false;

但你应该只在你不能使用as操作符转换类型时使用is,否则就是重复的, 比如:

// 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;

可以看出,进行了两次转换,低效而且重复.如果你已经决定了要使用as操作符来转换类型,那么只需检查返回值是否为null就可以了.

现在你已经明白了as, is和cast,那么foreach循环用的是什么操作符呢?

public void UseCollection( IEnumerable theCollection )

{

foreach ( MyType t in theCollection )

t.DoStuff( );

}

foreach用的实际上是cast操作符.上面的代码可以重写成下面的代码:

public void UseCollection( IEnumerable theCollection )

{

IEnumerator it = theCollection.GetEnumerator( );

while ( it.MoveNext( ) )

{

MyType t = ( MyType ) it.Current;

t.DoStuff( );

}

}

这是因为foreach要用cast来支持value type和reference type. 如果使用as操作符的话,foreach语句仍表现相同的行为,但会抛出BadCastException,因为as不能作用于值类型上.

因为IEnumerator.Current返回一个System.Object类型的object,而这个object不具备转换操作,所以并不能用于这个测试. SecondType类型的collection也不能用于UseCollection()因为转换会失败. Foreach语句并不检查collection中object的运行时类型是否支持这种转换,它只检查IEnumerator.Current所返回的System.Object类型是否支持到目标类型(本例中的MyType)之间的转换.

最后,有时你想知道一个object确切的类型,而不只是关心这个object是否可以转换成目标类型. 因为as操作符对于所有从目标类型继承而来的类型的转换都返回true. 但GetType()方法返回object运行时的类型,它比as或者is操作符提供的测试都要更严格. 它返回的是object的确切类型.

再看一下UseCollection():

public void UseCollection( IEnumerable theCollection )

{

foreach ( MyType t in theCollection )

t.DoStuff( );

}

如果你创建一个叫NewType的类,这个类继承MyType,那么NewType objects的collection也可以在UseCollection()中很好的工作.

public class NewType : MyType

{

// contents elided.

}

如果你的意图是写出一个可以使用所有MyType类型(自身或继承而来)的function时,这没有什么.但如果你的意图是写一个只接受MyType类型自身的function的话,你就要用确切的类型来进行比较. 在本例中,你可以在foreach循环里做. 知道运行时确切的类型只有在做equality测试时是非常重要的.在大多数其他的情况下,as和is操作符提供的isinst比较是语法上正确的.

好的OO经验告诉我们要尽量避免类型的转换,但有时类型转换是必需的.在这种情况下,尽量的使用as和is操作符来表达你的意图. 不同的类型强制转换有不同的规则,但as和is却在绝大多数情况下都是正确的,而且它们只有在object是正确的类型时才转换成功. Cast操作符会带来一些副作用,而且转换的成功与失败往往出乎意料.

本系列文章只是作者读书笔记,版权完全属于原作者 (Bill Wagner),任何人及组织不得以任何理由以商业用途使用本文,任何对本文的引用和转载必须通知作者:zphillm@hotmail.com

转载于:https://www.cnblogs.com/ZphillM/archive/2005/08/06/208713.html

Effective C#: Item 3: Prefer the is or as Operators to Casts相关推荐

  1. Item 13: Prefer const_iterators to iterators.

    Item 13: Prefer const_iterators to iterators. Effective Modern C++ Item 13 的学习和解读. STL 中 const_itera ...

  2. Effective C#: Item 1 Always use properties instead of accessible data members

    Effective C#: Item 1 Always use properties instead of accessible data members Item 1: 当设计类时,永远用Prope ...

  3. Item 02: Prefer consts, enums, and inlines to #defines

    Item 02: Prefer consts, enums, and inlines to #defines 尽量以 const,enum,inline 替换 #define 假如有这样的语句: #d ...

  4. Item 20: Prefer pass-by-reference-to-const to pass-by-value(Effective C++)

    Prefer pass-by-reference-to-const over pass-by-value. It's typically more efficient and it avoids th ...

  5. 《Effective Morden C++》Item 8: Prefer nullptr to 0 and NULL.

    引子 这一条目就比较简单了,就是宣传用nullptr来指代空指针,而不是之前的0或者NULL. 正文 在老式C++中,显然0是int类型,而NULL也是一个整数类型(int或者long).总的来说,这 ...

  6. Effective JavaScript Item 37 认识this的隐式指向

    本系列作为Effective JavaScript的读书笔记. CSV数据通常都会被某种分隔符进行分隔.所以在实现CSV Reader时,须要支持不同的分隔符.那么,非常自然的一种实现就是将分隔符作为 ...

  7. Effective JavaScript Item 23 永远不要修改arguments对象

    本系列作为Effective JavaScript的读书笔记. arguments对象只是一个类似数组的对象,但是它并没有数组对象提供的方法,比如shift,push等.因此调用诸如:argument ...

  8. More Effective C++ Item 附2:一个auto_ptr的实现实例

    More Effective C++的前言.导读和附1(侯捷译),以及在 "C++ 中计算物件个数"和"为智能指标实作 operator->*"(陈崴译, ...

  9. Effective Java - Item 1: Consider static factory methods instead of constructors

    考虑使用静态工厂方法来替代构造方法, 这样的做的好处有四点. 1. 更好的表意 有的构造方法实际上有特殊的含义, 使用静态工厂方法能更好的表达出他的意思. 例如 BigInteger(int, int ...

最新文章

  1. 推荐一个不错的开源在线HTML编辑器
  2. putty和Xming server 结合完美在windows下显示linux GUI程序
  3. spring源码分析之context
  4. 计算机控制实验教程,新)《计算机控制技术》实验教程.doc
  5. 探究Jvm源码实现-MarkWord
  6. Git的多人协作和分支处理测试
  7. HDFS查看文件的前几行-后几行-行数
  8. Linux 环境下 vi/vim 编辑器常用命令
  9. 交通信息工程 实验四:交通仿真实验(二)
  10. android仿ios录音动画,仿IOS录音机
  11. Java8(JDK1.8)新特性
  12. Unity任意版本Vuforia插件下载
  13. 联想主板怎么进入bios
  14. 谈一谈url实现文件下载
  15. iOS好用的第三方框架/插件
  16. ORACLE内核参数
  17. Golang的Ticker使用姿势
  18. Python--变量
  19. sql --Acess
  20. 数据库系统概念-第六版 - charter 1 - 笔记

热门文章

  1. 两种删除internal table entry的性能比较
  2. Java Spring实现原理研究之Servlet initialization初始化过程
  3. 为什么Kubernetes要引入pod的概念,而不直接操作Docker容器
  4. Linux系统里让vim支持markdown格式的语法高亮
  5. MongoDB最简单的入门教程之五-通过Restful API访问MongoDB
  6. 如何将iso文件安装到VirtualBox里的ubuntu去
  7. 计算机组成原理中wr是什么,计算机组成原理复习例题.doc
  8. python 量化交易 框架 开源_Hikyuu首页、文档和下载 - 基于 C++/Python 的开源量化交易研究框架 - OSCHINA - 中文开源技术交流社区...
  9. tstringlist怎么查看是否存在该数据_财务报表审计该如何进行?
  10. dll已加载但找不到入口点dllregisterserver_Java 是如何加载类的?