Effective C# 第二版 中文 之03

andy572633 2011-06-22 09:50:29
原则三:使用is 和 as 而不是用强制类型转换
prefer the is or as operators to casts

投入到C#的怀抱,你就投入到了强类型(strong type)的怀抱(译注:C#是强类型语言)。这在大部分情况下是有好处的。强类型意味着你希望编译器能找出代码中类型不匹配的地方。这也意味着你的应用程序在运行时不用做太多的类型检查。但有些时候,运行时的类型检查是不可避免的。有时你需要写一些使用object作为参数的方法(假设因为框架中定义了这些方法的签名,使你不得不这么做),你很可能需要将这些object转换成其它类型,不管是类(class)还是接口(interface)。你有两个选择:要么使用as操作符,要么使用cast强制转换。你也可以使用一个稳妥的变通方法:先用is对这个类型转换进行测试,再用as或者cast进行转换。

正确的选择是:在所有能使用as操作符的地方尽可能地使用它。因为与盲目的强制转换比起来,它更安全而且更高效。As 和is操作符都不进行任何用户定义的转换。它们仅在运行时的类型与目标类型相匹配的情况下才返回成功。它们不会为了满足功能而去创造一个新的对象。

来看一个例子,你想将任意类型的对象(object)转化成MyType的实例(instance),你可以写成这样:

//Version one
object o = Factory.GetObject();
MyType t = o as MyType;
if(t != null)
{
//用t来处理事务
}
else
{
//报告错误
}

也可以写成下面这个样子:

//Version two
object o = Factory.GetObject();
try
{
MyType t ;
t=(MyType)o;
//用t来处理事务
}
catch(InvalidCastException)
{
//报告错误
}

你不得不承认第一个版本更简单、更易读。它没有使用try/catch,所以你即节省了运行时开销,也精简了代码。注意,在使用强制转换的版本中,为了捕获异常,你必须额外地去检验类型是否为null。使用强制转换可以将null转化成任何引用类型,但是对于as操作,当指向一个空引用的时候,会返回一个null值。所以,使用强制转换,你必须检验是否为null并捕获异常(译注:但是举得例子里并没有体现啊!)。而使用as,你只需将返回的引用与null值进行比较就可以了。
(译注:说了这么一大段,中心思想就是:用as你只要检验是否为null就可以了,而用强制转换你不但要检验是否为null,还有捕获、处理异常)。

As和强制转换最大的区别在于他们对用户定义转换的处理。As和is操作符只是检验要转换的运行时类型,并不做其它操作。如果所检验的类型不是目标类型或从目标类型派生出来的类型,它们检验失败。强制转换则相反,它使用转换操作符将一个对象转换成需要的类型。这包括所有的内建数值类型的转换。将long类型转换成short类型,可能会丢失部分数据。

当你对用户自定义类型进行强制转换的时候,也可能出现问题。看一下下面的类:

public class SecondType
{
private MyType _value;

//转换操作符。将一个SecondType转化为MyType。详见原则9
public static implicit operator MyType(SecondType t)
{
return t._value;
}
}

假设在以下这段代码中,有一个SecondType的对象是由Factory.GetObject()函数返回的:

//version one:
object o = Factory.GetObject();

//o 是 SecondType:
MyType t= o as MyType;//转换失败,o不是MyType
if(t!=null)
{
}
else
{
}

//version two:
object o = Factory.GetObject();
try
{
MyType t1;
t1 = (MyType)o;//转换失败,o不是MyType
}
catch(InvalidCastException)
{
}

两个版本都转换失败。但是,强制类型转换是是会执行了用户自定义的转换。你可能会想这样的话(既然执行了用户自定义的转换)那强制转换应该是能成功转换的。没错——如果按照这种思路来说,它是应该成功的。但实际上它还是失败了,因为编译器产生的代码是基于编译时对象o的类型的。编译器并不知道在运行时对象o的实际类型,它将o当成一个object的实例。编译器认为没有从object类型转到MyType类型的用户自定义转换。它检查了object和MyType的定义,没有任何的用户自定义转换,编译器生成代码来检查o的类型,并检查它是否是MyType类型。因为o是SecondType类型,所以这样的转换失败。编译器并不会去检验运行时o的实际类型是否可以转换成MyType类型。

如果按照以下代码的写法,你可以成功地将SecondType类型转换成MyType类型:

//version three:
object o = Factory.GetObject();
SecondType st = o as SecondType;
try
{
MyType t;
t= (MyType)st;
}
catch(InvalidCastException)
{
}

你永远都不应该写出如此丑陋的代码,但它确实解决了一个常见的问题。你可以将一个object对象作为函数的参数并希望它能够进行适当的类型转换,尽管如此,你还是尽量不要这么做:

object o = Factory.GetObject();
DoStuffWithObject(o);

private static void DoStuffWithObject(Object o)
{
try
{
MyType t;
t = (MyType)o;//失败,o不是MyType类型。
}
catch
{

}
}

记住,用户自定义转换操作只对于编译时类型有效,而对于运行时类型无效。运行时有一个从o类型向MyType类型转换也没用,编译器不知道或者不在乎。下面的这个语句,当st声明不同的类型的时候它会产生不同的行为:

t= (MyType)st;

而下面这个语句,不管st声明型是什么,它都返回相同的结果。所以,比起强制转换你应该更喜欢as——它一致性更好。事实上,如果它们的类型关系不是继承,并且存在它们间的用户自定义转换操作,下面这个语句将会产生一个编译错误:

t= st as MyType;

现在,你知道了在能使用as的地方应该尽可能地使用as,我们再来讨论一下什么时候不能使用as。As操作符不能对值类型进行操作,下面这段代码是无法通过编译的:

object o = Factory.GetObject();
int i = o as int;

这是因为int是值类型,值类型永远都不能为null。所以,如果当o不是一个整形数的时候,i应该存储什么值呢?不管你选什么数,它都是一个有效的整形。所以,as在这种情况下就不能使用。你又挣扎于使用强制转换,实际上,这将是一个装箱\拆箱的转换(详见原则45):

object o = Factory.GetValue();
int i = 0;
try
{
i = (int)o;
}
catch(InvalidCastException)
{
i=0;
}

使用异常机制是一种不好的习惯(译注:当然,在你没得选的时候,用异常机制总比不用的好)。你可以使用is来消除可能因类型转换而引发的异常,这样你就可以不使用异常机制了:

object o = Factory.GetValue();
int i = 0;
if(o is int)
i= (int)o;

如果o是那些可以转化成int的数据类型的时候,例如double,is操作将返回true,否则返回false。对于null参数,它也是返回false。

只有当你不能使用as来做类型转换的时候,才能使用is。否则,它将产生冗余:

//正确,但是冗余
MyType t = null;
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循环中,你应该使用哪个呢?foreach循环可以对非泛型的IEnumerable序列进行操作,它拥有内建的强制转换。(如果可能的话,应该尽量使用安全类型的泛型版本,但这些非泛型版本的存在是有历史原因的,他们是用来支持一些晚绑定的情况)

public void UseCollection(IEnumerable theCollection)
{
foreach(MyType t in theCollection)
t.DoStuff();
}

foreach通过强制转换将对象转换成目标类型。上面这段由foreach产生的代码大致与以下这段代码相同:

public void UseCollection(IEnumerable theCollection)
{
Enumerator it = theCollection.GetEnumerator();
while(it.MoveNext)
{
MyType t = (MyType)it.Current;
t.DoStuff();
}
}

为了同时支持值类型和引用类型,foreach必须使用强制转换。通过使用强制转换,不管目标类型是什么,foreach都可以表现出同样的行为。尽管如此,因为使用的是强制转换,foreach循环可能会抛出一个InvalidCastException(无效的类型转换)错误。

IEnumerator.Current返回的是一个System.Object类型的对象,而System.Object又没有强制转换操作符,所以以下这些尝试都是不能成功的。一个SecondType的对象的集合是不能使用前面那个UseCollection函数的,因为它会导致类型转换失败,这个前面我们已经讨论过了。Foreach语句(它使用的是强制转换)并不会在运行时检验集合中的类型可否转换成目标类型(它是在编译时做的)。它只是检查是否可以由(从IEnumerator.Current返回的)System.Object类型转换成循环中定义的变量类型(本例中是MyType)。

有时候,你想要知道一个对象的确切类型,而不仅仅是它能否从当前类型转换成目标类型。如果当前类型是派生自目标类型的话,is操作符将返回true。GetType()方法能够获取一个对象的运行时的类型。这样它就比is和as更加精确。GetType()返回一个对象的类型,并且可以和另一个具体的类型进行比较。

再次考虑一下这个函数:
public void UseCollectionV3(IEnumerable theCollection)
{
foreach(MyType t in theCollection)
t.DoStuff();
}

如果你定义一个派生自MyType的类型NewType,NewType的集合就可以正常地使用UseCollection()函数了:

public class NewType:MyType
{
}

如果你想要写一个函数,它能够对所有MyType实例都有效(包括派生自MyType的),上面的写法没问题。但是,如果你想写一个函数,它只对类型确切为MyType的对象(而不是MyType的子类对象)有效,你应该使用精确的类型比较。在这,你可以在循环的内部去实现。大多数时候,当做相等性检查的时候,运行时的确切的类型是非常重要的(详见原则六)。 而在其它比较的时候,由is和as提供的.isinst(译注:IL指令)比较,从语法上已经正确的了。

.NET的基础类库(BCL:Base Class Library)包含了一个方法,这个方法能够使队列中的元素使用相同的类型操作:Enumerable.Cast<T>()能够转换所有支持典型IEnumerable接口的队列的所有元素。

IEnumerable collection = new List<int>(){1,2,3,4,5,6,7,8,9,10};

var small = from int item in collection
while item <5
select itme;
var small2 = collection.Cast<int>().Where(item => item <5 ).select(n => n)

(译注:以上这段代码是什么?我还真看不懂,有哪位同学看懂了的,麻烦解释一下。)

上面的查询产生了与最后一行一样的代码调用。在上面的两种情况中,Cast<T>方法将每一个item都转化成目标类型队列中的一个成员。Enumerable.Cast<T>是用旧的强制转换,而不是as操作符。使用旧的强制转换就意味着Cast<T>不必具有一个类型约束。如果使用as操作符就受到约束,相比较于实现多种不同的Cast<T>方法,BCL团队宁愿选择创建一个单一的只使用强制转换的方法。你在自己的代码中也要做这样的权衡。在一些场合,你需要转换一些泛型参数对象的类型,你就需要权衡一下对类型的必要的一些约束与采用不同处理方法进行转换的利弊了。(有约束会更安全,但同时就需要提供不同的转换方法。你需要在安全和方便之间做一个选择)

在C#4.0,通过使用动态和运行时类型检查,能够规避类型系统。这是第五章——Dynamic programming in C#——中所要讨论的问题。(译注:原书的结构是每几个原则会组成一个章,比如原则1-11是第一章:C# Language Idioms。我会在所有原则翻译完后,重新整理一下书的结构,发一个完整版的)。很少有途径去处理一个对象是基于希望了解它的行为而不是了解它详细的类型或是接口支持。你需要知道什么时候要使用这个技术而什么时候要避免。

好的面向对象编程思想告诉我们应该尽量避免类型转换,但是,总是会有一些异类存在。当你不可避免的要使用类型转换的时候,应该用as和is来更清晰地表达你的意图。不同的类型转换方法有着不同的规则,is和as操作符几乎总是拥有正确的语义,他们只有在类型被证明是正确的时候才会转换成功。更喜欢那些强制转换语句,它会拥有一些意外的影响,不管成功或是失败,你都不会对它抱有太大希望。(即不确定性更强)




小结:前段时间一直忙于原公司的离职和新公司的入职的手续,很久没有更新,现在终于告一段落,可以抽出时间来翻译了。

其它章节可以到我的博客去看一看:http://blog.csdn.net/andy572633/archive/2011/06/22/6562129.aspx
...全文
119 13 打赏 收藏 转发到动态 举报
写回复
用AI写文章
13 条回复
切换为时间正序
请发表友善的回复…
发表回复
alongliving 2011-06-23
  • 打赏
  • 举报
回复
顶一下
andy572633 2011-06-23
  • 打赏
  • 举报
回复
[Quote=引用 8 楼 sp1234 的回复:]
真有意思,既然我需要在为一行强制类型转换代码就要写一个try.....catch,那么我何必还写强制类型转换啊?反之当我根本不写try....catch,也就是说我知道应该不会抛出异常时,我有干嘛要用is判断呢?

正常地编程,就是要写

C# code
t=(MyType)o;
根本不写try...catch,否则如果不正常地写,那么我才会使用as。所以我认为这本书在这个地方至少跟我……
[/Quote]
同感,其实在翻译的时候,我也觉得至少80%以上的情况,我们是知道它一定能转成功的。
所以说,尽信书不如无书!
  • 打赏
  • 举报
回复
我引申一点点,昨天晚上(或者今天早上)我在csdn随便写了三行代码作为demo:
var a=new 主语("我");
var b=new 谓语("编程");
var c=a+b;
由于在“主语、谓语、句子”这三个类中某一个上定义了一个对+运算符重载,于是编译器就知道c是个句子对象。

我想这本书的作者就根本不会去想这种编程,因为它先天地排斥熟练运用类型自动转换功能来编程的风格。
  • 打赏
  • 举报
回复
否则如果不正常地写 --> 否则如果不能正常地写

当我明知道(按照严密的设计逻辑可以知道)我写 t=(MyType)o; 是顺理成章的代码的时候,这个书作者想当然地命令我必须写上一个try...catch,这是我不能接受的。假设他说服我写上try...catch,那么我也就不用前置类型转换了而用as了。所以我认为这个作者也许知道什么是否该用as,但是并不知道什么时候该用强制类型转换。
  • 打赏
  • 举报
回复
真有意思,既然我需要在为一行强制类型转换代码就要写一个try.....catch,那么我何必还写强制类型转换啊?反之当我根本不写try....catch,也就是说我知道应该不会抛出异常时,我有干嘛要用is判断呢?

正常地编程,就是要写
t=(MyType)o;
根本不写try...catch,否则如果不正常地写,那么我才会使用as。所以我认为这本书在这个地方至少跟我的编程知识完全是阻抗不匹配的。
老鼠爱上猫 2011-06-22
  • 打赏
  • 举报
回复
此帖收藏,需要多读几次!
老鼠爱上猫 2011-06-22
  • 打赏
  • 举报
回复
谢谢,受教了!顶你没商量!
matrixcl 2011-06-22
  • 打赏
  • 举报
回复
原来是广告贴啊,哈哈。支持技术广告。
porschev 2011-06-22
  • 打赏
  • 举报
回复

排版差了。。。看不下去
weike021996 2011-06-22
  • 打赏
  • 举报
回复
翻译的?吗
Maa 2011-06-22
  • 打赏
  • 举报
回复
帮顶~
暖枫无敌 2011-06-22
  • 打赏
  • 举报
回复
有人说c++程序员可以分成两类,读过effective c++的和没读过的。世界顶级c++大师scott meyers成名之作的第三版的确当得起这样的评价。当您读过这本书之后,就获得了迅速提升自己c++功力的一个契机。.(2-1)   在国际上,本书所引起的反响,波及整个计算技术出版领域,余音至今未绝。几乎在所有c++书籍的推荐名单上,本书都会位于前三名。作者高超的技术把握力、独特的视角。诙谐轻松的写作风格、独具匠心的内容组织,都受到极大的推崇和仿效。这种奇特的现象,只能解释为人们对这本书衷心的赞美和推祟。《effective c++》前两个版本的确抓住了全世界无数程序员的目光。原因十分显明:scott meyers 极富实践意义的c++ 研讨方式,描述出专家用以产出干净、正确、高效代码的经验法则和行事法则——也就是他们几乎总是做或不做的某些事。   这本书不是读完一遍就可以束之高阁的快餐读物,也不是用以解决手边问题的参考手册,而是需要您去反复阅读体会的,c++是真正程序员的语言,背后有着精深的思想与无与伦比的表达能力,这使得它具有类似宗教般的魅力。希望这本书自瞄帮助您跨越c抖的重重险阻,领略高处才有的壮美风光,做—个成功而快乐的c++程序员。...      本书一共组织 55 个准则,每一条准则描述一个编写出更好的 c++ 的方式。每一个条款的背后都有具体范例支撑。第三版有一半以上的篇幅是崭新内容,包括讨论资源管理和模板(templates)运用的两个新章。为反映出现代设计考虑,对第二版论题做了广泛的修订,包括异常(exceptions)、设计模式(design patterns)和多线程(multithreading)。      《effective c++》的重要特征包括:    * 高效的 classes、functions、templates 和inheritance hierarchies(继承体系)方面的专家级指导。    * 崭新的 "tr1" 标准程序库功能应用,以及与既有标准程序库组件的比较。    * 洞察 c++和其他语言(例如java、c#、c)之间的不同。此举有助于那些来自其他语言阵营的开发人员消化吸收 c++ 式的各种解法。(2-1)

110,526

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • Web++
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧