Effective C# 3:Prefer the is or as Operators to cast

rel="File-List" href="file:///C:%5CDOCUME%7E1%5CHelios%5CLOCALS%7E1%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_filelist.xml"> rel="themeData" href="file:///C:%5CDOCUME%7E1%5CHelios%5CLOCALS%7E1%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_themedata.thmx"> rel="colorSchemeMapping" href="file:///C:%5CDOCUME%7E1%5CHelios%5CLOCALS%7E1%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_colorschememapping.xml">

Item 3Prefer the is or as Operators to cast

优先使用is 或者as操作符,而不是强制转换

C# is a strongly typed language. Good programming practice means that we all try to avoid coercing one type into another when we can avoid it. But sometimes, runtime type checking is simply unavoidable. Many times in C#, you write functions that take System.Object parameters because the framework defines the method signature for you. You likely need to attempt to downcast those objects to other types, either classes or interfaces. You've got two choices: Use the as operator or use that old C standby, the cast. You also have a defensive variant: You can test a conversion with is and then use as or casts to convert it.

C#是一种强类型语言。良好的编程实践意味着,只要我们能够做到,就要尽量避免将一个类型强制转换成另一个类型。但是有时,运行时类型检查是不可避免的。在C#里面多数情况下,你编写使用System.Object作为参数的方法,因为框架为你定义好了方法签名。你很可能需要尝试将这些对象向下装换为其他的类型,要么是类要么是接口。你有两种选择:使用as操作符或者传统的C风格的强制转换。你还有一个保险的做法——先使用is测试一个转换,然后再使用as或者强制转换符号来转换它。

The correct choice is to use the as operator whenever you can because it is safer than blindly casting and is more efficient at runtime. The as and is operators do not perform any user-defined conversions. They succeed only if the runtime type matches the sought type; they never construct a new object to satisfy a request.

正确的选择是:只要可以,就使用as操作符,因为它比盲目的转换更安全,在运行时更高效。Asis操作符不执行任何用户定义的转换。只有当运行时类型与目标类型相匹配时,转换才会成功。它们从不会为了满足一个请求而构建一个新的对象。

Take a look at an example. You write a piece of code that needs to convert an arbitrary object into an instance of MyType. You could write it this way:

让我们来看一个例子。你写下了如下的一段代码:需要将一个任意对象转化成MyType类型的一个实例。你可以这样写:

  1.         object o = Factory.GetObject();
  2.         //Version one
  3.         MyType t = o as MyType;
  4.         if (t != null)
  5.         {
  6.             //使用t,t是MyType类型的对象
  7.         }
  8.         else
  9.         {
  10.             //报告失败信息
  11.        }

Or, you could write this:

或者,你也可以这样写:

   

  1.     object o = Factory.GetObject();
  2.         //Version two
  3.         try
  4.         {
  5.             MyType t;
  6.             t = (MyType)o;
  7.             if (t != null)
  8.             {
  9.                 //使用t,t是MyType类型的对象
  10.             }
  11.         }
  12.         catch
  13.         {
  14.             //报告转换失败
  15.     }

You'll agree that the first version is simpler and easier to read. It does not have the try/catch clause, so you avoid both the overhead and the code. Notice that the cast version must check null in addition to catching exceptions. null can be converted to any reference type using a cast, but the as operator returns null when used on a null reference. So, with casts, you need to check null and catch exceptions. Using as, you simply check the returned reference against null.

你会承认,第一个版本更加简单也更易读,它不含有try/catch子句,因此可以避免额外的花销和代码。注意,强制转换的版本不仅要捕捉异常,而且必须检查t是否是空,因为null可以被强制转换成任何引用类型;但是,当使用as操作符时,如果原来就是一个空引用的话,将会返回null。因此,使用强制转换时,不仅需要检查被转换对象是否为null并且要捕获异常。使用as时,只需要简单的检查返回的引用是否为null就可以了。

The biggest difference between the as operator and the cast operator is how user-defined conversions are treated. The as and is operators examine the runtime type of the object being converted; they do not perform any other operations. If a particular object is not the requested type or is derived from the requested type, they fail. Casts, on the other hand, can use conversion operators to convert an object to the requested type. This includes any built-in numeric conversions. Casting a long to a short can lose information.

As操作符和强制转换之间最大的区别就是:如何处理用户自定义的转换。Asis操作符检查被转换对象的运行时类型,不执行任何其他的操作。如果一个特定的对象不是要求的类型或者是其派生类,就会失败。从另一方面来说呢,强制转换,能够将一个对象转换成要求的类型,包括任何内建的数字类的转换。但是,将一个long类型转换成short会丢失信息的。

The same problems are lurking when you cast user-defined types. Consider this type:

当对用户自定义类型进行转换时,潜在着同样的问题。考虑下面的类型:

  1. public class SecondType
  2. {
  3.     private MyType value;
  4.     public static implicit operator MyType(SecondType t)
  5.     {
  6.         return t.value;
  7.     }
  8. }

Suppose an object of SecondType is returned by the Factory.GetObject() function in the first code snippet:

假设SecondType类型的一个对象由Factory.GetObject()返回:

  1. object o = Factory.GetObject();
  2.  
  3. //o is a SecondType
  4. MyType t = o as MyType;//Fail. o is not MyType
  5. if (t != null)
  6. {
  7.     //work with t ,it's a MyType
  8. }
  9. else
  10. {
  11.     //report the failure
  12. }
  13.  
  14. //VersionTwo
  15. object o = Factory.GetObject();
  16. try
  17. {
  18.     MyType t1;
  19.     t1 = (MyType)o;//fail
  20.     if (t1 != null)
  21.     {
  22.          //work with t1 ,it's a MyType
  23.     }
  24.     else
  25.     {
  26.         //report a null reference failure
  27.     }
  28. }
  29. catch
  30. {
  31.     //report the conversion failure
  32. }

Both versions fail. But I told you that casts will perform user-defined conversions. You'd think the cast would succeed. You're right it should succeed if you think that way. But it fails because your compiler is generating code based on the compile-time type of the object, o. The compiler knows nothing about the runtime type of o; it views o as an instance of System.Object. The compiler sees that there is no user-defined conversion from System.Object to MyType. It checks the definitions of System.Object and MyType. Lacking any user-defined conversion, the compiler generates the code to examine the runtime type of o and checks whether that type is a MyType. Because o is a SecondType object, that fails. The compiler does not check to see whether the actual runtime type of o can be converted to a MyType object.

两个版本都会失败。但是我曾经告诉过你,强制转换可以执行用户自定义的转换,所以你可能认为强制转换的版本会成功。你是对的:那样想的话,它就应该是成功的。但是,它失败了,因为:编译器是基于对象o的运行时类型来生成代码的。编译器对o的运行时类型一无所知;它将o看做是System.Object类型的实例。编译器看不到从System.Object类型到用户自定义类型MyType的转换。它检查System.ObjectMyType类型。由于缺少任何用户自定义的转换,因此编译器生成代码来检查o的运行时类型,判断它是不是MyType类型。因为oSecondType类型,所以失败了。编译器并不检查o实际的运行时类型能否被转换成MyType对象。

You could make the conversion from SecondType to MyType succeed if you wrote the code snippet like this:

如果你曾写下了这样的代码段,就能使从SecondTypeMyType的转换成功:

  1. object o = Factory.GetObject();
  2. //Version three:
  3. SecondType st = o as SecondType;
  4. try
  5. {
  6.     MyType t;
  7.     t = (MyType)st;//fail
  8.     if (t != null)
  9.     {
  10.          //work with t ,it's a MyType
  11.     }
  12.     else
  13.     {
  14.         //report a null reference failure
  15.     }
  16. }
  17. catch
  18. {
  19.      //report the conversion failure
  20. }

You should never write this ugly code, but it does illustrate a common problem. Although you would never write this, you can use a System.Object parameter to a function that expects the proper conversions:

你不应该写下这么丑陋的代码,但是它确实列举了一个常见的问题。尽管你可能从不会写这些,但是你可以将System.Object作为一个方法的参数,在函数的内部来进行的适当的转换:

  1. object o = Factory.GetObject();
  2.  
  3. DoStuffWithObject(o);
  4.  
  5. public void DoStuffWithObject(object o2)
  6. {
  7.     //Version three:
  8.     try
  9.     {
  10.         MyType t;
  11.         t = (MyType)o2;//fail
  12.         if (t != null)
  13.         {
  14.              //work with t ,it's a MyType
  15.         }
  16.         else
  17.         {
  18.             //report a null reference failure
  19.         }
  20.     }
  21.     catch
  22.     {
  23.       //report the conversion failure
  24.     }
  25. }
  26. }

Remember that user-defined conversion operators operate only on the compile-time type of an object, not on the runtime type. It does not matter that a conversion between the runtime type of o2 and MyType exists. The compiler just doesn't know or care. This statement has different behavior, depending on the declared type of st:

记住:用户自定义的转换操作符,只能够在一个对象的编译时类型上进行操作,而不能在运行时类型上操作。它并不关心o2的运行时类型与已经存在的MyType类型之间存在的转换关系。编译器就是不知道、不关心。下边这个表述,由于st的声明时类型不同,而有不同的表现行为:

  1. t = (MyType)st;

This next statement returns the same result, no matter what the declared type of st is. So, you should prefer as to castsit's more consistent. In fact, if the types are not related by inheritance, but a user-defined conversion operator exists, the following statement will generate a compiler error:

下面的表述返回同样的结果,而不管st的声明类型是什么。因此,你应该优先考虑as而不是强制转换——它的兼容性更好。事实上,如果一个类型不是和继承相关的话,即使用户自定义类型存在,下面的语句也将会生成一个编译时错误:

  1. t = st as MyType;

Now that you know to use as when possible, let's discuss when you can't use it. The as operator does not work on value types. This statement won't compile:

既然你已经知道了尽可能的使用as,下面就让我们来讨论什么时候不能使用它。As操作符不能应用于值类型。下面的语句不会通过编译:

  1. object o = Factory.GetObject();
  2. int i = o as int;//Does not compile

That's because ints are value types and can never be null. What value of int should be stored in i if o is not an integer? Any value you pick might also be a valid integer. Therefore, as can't be used. You're stuck with a cast:

那是因为int是值类型,从不可能为空。如果o不是一个整型的话,什么样的整型值应该被存储在i中呢?你使用的任何值也都应该是有效的整型。因此,不能使用as。你只能使用强制转换;

  1. object o = Factory.GetObject();
  2. int i = 0;
  3. try
  4. {            
  5.        i = (int) o;
  6. }
  7. catch (Exception)
  8. {
  9.       
  10.        i = 0;
  11. }

But you're not necessarily stuck with the behaviors of casts. You can use the is statement to remove the chance of exceptions or conversions:

但是你没有必要跟强制转换较劲儿。你可以使用is语句来移除异常或者转换:

  1. object o = Factory.GetObject();
  2. int i = 0;
  3. if (o is int)
  4. i = (int)o;

If o is some other type that can be converted to an int, such as a double, the is operator returns false. The is operator always returns false for null arguments.

如果o是其他可以被转换为int型的类型,比如double,is操作符将返回false.对于null参数,is操作符总是返回false.

The is operator should be used only when you cannot convert the type using as. Otherwise, it's simply redundant:

只有当as操作符不能执行转换时,才应该使用is。否则很多余。

  1.     //correct,but redundant
  2.     object o = Factory.GetObject();
  3.     MyType t = null;
  4.     if(o is MyType)
  5.       t = o as MyType;

The previous code is the same as if you had written the following:

前面的代码和下面的代码是一样的:

  1.     //correct,but redundant
  2.     object o = Factory.GetObject();
  3.     MyType t = null;
  4.     if((o as MyType)!=null)
  5.       t = o as MyType;

That's inefficient and redundant. If you're about to convert a type using as, the is check is simply not necessary. Check the return from as against null; it's simpler.

这是效率低下而且多余的。如果你打算使用as来进行类型转换,就没有必要使用is来进行检查了。直接将as的结果和null来比较就可以了,这样简简单单。

Now that you know the difference among is, as, and casts, which operator do you suppose the foreach loop uses?

既然你已经知道了as  is 和强制转换之间的差别,那么在foreach循环中,你会选择使用哪个呢?

  1. public voiud UseCollection(IEnumerable theCollection)
  2. {
  3.     foreach(MyType t in theCollection)
  4.         t.DoStuff();
  5. }

foreach uses a cast operation to perform conversions from an object to the type used in the loop. The code generated by the foreach statement equates to this hand-coded version:

Foreach使用强制转换来实现从object到循环里使用的类型的转换。Foreach语句生成的代码和下面的硬编码是等效的:

  1. public voiud UseCollection(IEnumerable theCollection)
  2. {
  3.     IEnumerable it = theCollection.GetEnumerator();
  4.     while(it.MoveNext())
  5.     {
  6.         MyType t = (MyType) it.Current;
  7.         t.DoStuff();
  8.     }
  9. }

foreach needs to use casts to support both value types and reference types. By choosing the cast operator, the foreach statement exhibits the same behavior, no matter what the destination type is. However, because a cast is used, foreach loops can generateBadCastExceptions.

Foreach需要使用强制转换来同时支持值类型和引用类型。通过选择使用强制转换符,不管目的类型是什么,foreach语句都展示出了同样的行为。然而,由于使用了强制转换,foreeach会产生BadCastException

Because IEnumerator.Current returns a System.Object, which has no conversion operators, none is eligible for this test. A collection of SecondType objects cannot be used in the previous UseCollection() function because the conversion fails, as you already saw. The foreach statement (which uses a cast) does not examine the casts that are available in the runtime type of the objects in the collection. It examines only the conversions available in the System.Object class (the type returned by IEnumerator.Current) and the declared type of the loop variable (in this case, MyType).

由于IEnumerator.Current返回的是不含有转换操作的System.Object,在这个测试中没有任何一个是合格的。SecondType对象的集合不能在前面的UseCollection方法里面使用,因为转换会失败,因为foreach语句使用的是强制转型,而强制转型并不关心集合元素的运行时类型。它只检查在System.Object类(由IEnumerator.Current返回的类型)和循环变量的声明类型MyType之间是否存在转换。

Finally, sometimes you want to know the exact type of an object, not just whether the current type can be converted to a target type. The as operator returns TRue for any type derived from the target type. The GetType() method gets the runtime type of an object. It is a more strict test than the is or as statement provides. GetType() returns the type of the object and can be compared to a specific type.

最后,有时你需要知道一个对象的精确类型,而不仅仅是当前类型是否可以被转换成目标类型。如果一个类型派生自目标类型,as操作符总是返回trueGetType()方法返回一个对象的运行时类型。它提供了比is或者as语句更加严格的测试。GetType()返回一个对象的类型并能和指定的类型进行比较。

Consider this function again:

让我们再次考虑这个方法:

  1. public voiud UseCollection(IEnumerable theCollection)
  2. {
  3.     foreach(MyType t in theCollection)
  4.         t.DoStuff();
  5. }

If you made a create a NewType class derived from MyType, a collection of NewType objects would work just fine in the UseCollection function:

如果你创建一个继承自MyType类型的NewType类,那么在UseCollection方法中,NewType类型对象的集合就能很好的工作:

  1. public class NewType:MyType
  2. {
  3.     //contents elided
  4. }

If you mean to write a function that works with all objects that are instances of MyType, that's fine. If you mean to write a function that works only with MyType objects exactly, you should use the exact type for comparison. Here, you would do that inside the foreach loop. The most common time when the exact runtime type is important is when doing equality tests (see Item 9). In most other comparisons, the .isinst comparisons provided by as and is are semantically correct.

如果你想写一个能够支持所有MyType类型(含派生类)的对象实例的方法,上面的方法就可以了。如果你想写一个只能精确支持MyType类型对象实例的方法,就应该使用精确的类型来进行比较(使用GetType())。应该将这种比较放在foreach循环的内部。精确的类型测试最常用的地方就是相等判断。在其他多数的比较情况下,asis提供的.isinst比较在语法上都是正确的。

Good object-oriented practice says that you should avoid converting types, but sometimes there are no alternatives. When you can't avoid the conversions, use the language's as and is operators to express your intent more clearly. Different ways of coercing types have different rules. The is and as statements are almost always the correct semantics, and they succeed only when the object being tested is the correct type. Prefer those statements to cast operators, which can have unintended side effects and succeed or fail when you least expect it.

好的面向对象的实践告诉我们:应该避免类型转换,但是有时没得选择。当你不可避免的要进行转换时,请使用C#语言的asis操作符来更加清晰的表达你的意图。不同的强制类型转换有不同的规则。Isas语句在大多数情况下都是语法上正确的,只有当被测试的类型是正确的类型时,转换才能够成功。优先使用这些操作符,而不是强制转换,因为它可能会带来意想不到效应,而且成功或者失败也不在我们的预料之中。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值