在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
Effective C# Item 3:Prefer the is or as Operators to Casts C#是一种强类型语言。好的编程习惯意味着我们应该尽量避免进行强制的类型转换。但是有些时候这是难以避免的。转换的方式有两种:一是使用as,二是C语言风格的显式类型转换(cast)。更保险的办法是先使用is来判断转换是否成立再使用as或显式类型转换。 正确的选择应当是使用as进行转换,因为它比强制转换更为安全和高效。as和is操作符不能对用户自定义的转换进行操作。它们只是简单的比较运行时的被转换类型同目标类型是否相匹配。它们永远不会为转换而新建object。 在下面的代码中,我们要将一个object型实例转换为MyType型。
object o = Factory.GetObject();
//版本1 MyType t = o as MyType; if(t != null) } 很明显第一种方式要更简单清晰,也避免了try/catch的消耗。要注意的是显式类型转换后还必须再一次检验,如果转换后的实例为null还要抛出空引用的异常。而对于as来说这种情况同转换失败一样均返回null值,只要简单的判断其是否为null就可以避免可能遇到的所有异常。 二者最大的不同在于对待用户自定义转换的方式。is和as在运行时检验类型是否可以转换,它们不会做任何其他的操作。如果被转换的类型不是特定的类型或其子类则转换失败。而显式类型转换则会进行转换操作将其强制转换为要求类型。这经常会造成数据丢失,例如数据类型之间的转换。 当使用自定义类型转换进行显式类型转换时也会出现问题。下例中定义了一个类及其自定义转换
public class SecondType
} 下例中的Factory.GetObject()会返回一个SecondType型的对象,我们要将它转换为MyType型
object o = Factory.GetObject();
//版本1 MyType t = o as MyType; //在这里会失败 if (t != null) } 上例中两种转换都是失败的。但是显式类型转换进行了用户自定义的转换。你觉得它应该可以成功,可事实是转换失败。因为对于o来说,编译器在编译时是基于object型来生成中间代码的,至于在运行时o到底是什么类型编译器并不清楚,在它看来o就是System.Object的一个实例。编译器会检查System.Object和MyType之间是否存在用户自定义转换。由于自定义转换不存在,编译器将生成在运行中检查o是否为MyType型的代码。在上例中因为o是SencondType型的,所以转换失败。编译器并不会检验o在运行时的类型能否转换为MyType型。 如果修改代码为下例则可成功转换SecondType型到MyType型。
object o = Factory.GetObject();
SecondType st = o as SecondType; try } 我们永远不要写出这样丑陋的代码,这里只是举例说明一下问题。你可以将System.Object型的实例作为函数参数来进行类型转换,尽管你永远不应当这样写。
object o = Factory.GetObject();
DoStuffWithObject(o); private void DoStuffWithObject(object o2) } 应当记住用户自定义转换只作用于编译时的类型,而不是运行时的类型。上例中o2和MyType之间的类型编译器并不清楚也不关心。当被st声明不同类型时,下例中的代码有着不同的行为。
t = (MyType)st;
而下例中不论st被声明为什么类型,都能返回同样的结果,因此同显式类型转换相比,使用as是更合理的。事实上,如果两个类型之间不是继承关系,只有自定义转换存在的时候,下例的代码会在编译时报错。
t = st as MyType;
现在我们清楚了应当尽量使用as来进行类型转换,下面我们来说一下什么时候不应该使用它。as不适用于值类型。下例中的代码就不能通过编译。
object o = Factory.GetValue();
int i = o as int; //不能通过编译 这是因为int是值类型而且永远不可能为null。如果o不是int型的,那么i里面应该放什么值呢?不论你放入什么,它都会被理解为一个合理的值。因此不能在这里使用as。这里应当使用显式类型转换。
object o = Factory.GetValue();
int i = 0; try } 但是你不必这样进行类型转换。我们可以使用is来排除异常转换的可能行
object o = Factory.GetValue();
int i = 0; if (o is int) i = (int) o; 如果o是不能被转换为int的类型,例如double,那么is操作就会返回false。如果被转换对象为null则is操作总是返回null。 只有当不能使用as进行转换的时候使用is才是有意义的,否则只是多余的。
object o = Factory.GetObject();
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就是不需要的了,检查返回是否为null就可以了。 现在我们知道了is,as和显式类型转换的差别,那么在foreach中发挥作用的是它们中的哪一种呢?
public void UseCollection(IEnumerable theCollection)
} foreach用的是显式类型转换来将对象转换为循环中应用的类型。下例中的代码相当于foreach的手写版本
public void UseCollection(IEnumerable theCollection)
} 由于foreach既需要应对值类型,也需要应对引用类型,因此它使用显式类型转换。由于应用了显式类型转换,不论目标类型为什么,foreach都声明同样的操作行为。另外由于使用的显式转换,foreach循环可能会抛出转换失败的异常。 IEnumerator.Current返回一个System.Object类型,而在上例中并没有适用的自定义转换。因为类型转换失败,SecondType类型的集合不能用在UseCollection()函数中。foreach并不会在运行时检验对象集合中的对象类型,它只检验被转换目标的类型(上例中为IEnumerator.Current的返回类型)和循环变量的目的类型(上例中为MyType型)之间的转换是否成立。 有的时候我们不仅要知道某个对象可以转换为什么类型,还想要知道它的确切类型。as操作符对于继承自目标类型的所有类型都会返回true。GetType()方法可以获得对象在运行时的类型,它的检查比is和as要严格。GetType()返回对象的确切类型,还可以同特定类型进行比较。 如果你创建一个名为NewType的继承自MyType的子类,UseCollection函数就可以接受NewType型的参数正常工作。
public class NewType : MyType
如果你向创建一个可以适用于所有MyType及其继承型参数的函数,那么这样就可以了。但是如果你想让你的函数只接受MyText型,就必须进行确切的类型比较。在上例中我们就应当在foreach循环中做这种工作。一般来说在运行时了解其类型在进行比较时是非常重要的。在大部分情况下,使用is和as在语法上是正确的。 在oo设计中我们应当尽量避免类型转换,但是有时候这是无法避免的。这时应当尽量使用is和as来将你的意图表示清晰。不同的强制类型转换有不同的规则。is和as在大部分的情况下都是正确的选择,而且它们只在目标类型转换正确的时候才起作用。相比之下显式类型转换可能会在你意想不到的地方成功或者失败,这将为你的程序带来一些负作用。 译自 Effective C#:50 Specific Ways to Improve Your C# Bill Wagner著 |
请发表评论