C#是强类型的语言。好的编程习惯是当可以避免类型转换就尽量避免。但有时,运行时类型检查是不可避免的。c#中你常常写些带有System.Object类型的参数的方法,因为框架已经为你定义了方法的原型。你可能会将Object类型向下转型,转成其他类型或类或接口。你有两个选择:用as操作符或老式的C强制转型写法。你得对变量保护:你可以用is操作符测试类型转换,然后再进行转换。
   正确的做法总是使用as操作符,因为它不盲目转换,是安全的,而且在运行时效率高。对于as和is操作符,它们并不对所有用户类型有效。在运行时类型匹配目标类型时它们才是有效的,它们从不构造一个新的对象去满足转型。
   看一例子,你可能将任意类型的对象转换成MyType类型,你可能会这样写:
object o = Factory.GetObject( );

// 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语句,所以你避免了性能开销及代码量。注意到强制类型时必须检查null从而捕捉转换时发生的异常。null可以被转换成任何引用类型,但as操作符作用于null引用时只返回null。所以强制类型转换你必须检查null及捕捉异常,用as操作符只需简单的检查下结果是否为null引用。
   强制类型转换与as/is操作符之间最大的区别在于它们对用户类型处理转型的方式。as/is操作符检查目标对象的运行时类型,它们不做任何额外的工作。如果指定的对象不是要求的类型或继承自要求的类型时,转换就会失败。另一方面,强制类型转换总可以把对象转换成目标类型,这也包括一些内置的数据类型转换。把一个long类型转换成short类型会丢失精度。
   同样的问题存在于用户类型转换。考虑下面的类型:
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;
  }
}

   在上面的代码片断中,假设SecondType的一个对象由Factory.GetObject()方法返回:
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.
}

   两者都失败了。虽然我们说强制类型转换可以完成用户自定义类型的转换,强制类型理应成功,你是对的如果如你所想那样。但是事实上失败了,因为编译器产生的代码是基于对象的编译时类型o的。编译器对运行时的o一无所知,它把o看作是System.Obejct类型的实例。编译器认为并没有从System.Object类型到用户类型MyType的转换。它检查System.Obejct类型和MyType类型的定义,缺少用户类型转换,编译器产生检查运行时o的类型的代码并检查它是不是MyType类型。因为o是SecondType类型的,所以转型失败了。编译器并不去检查实际运行时对象o是否可转型成MyType类型。
   如果你用下面的代码片断,那么从SecondType转型至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类型的参数的方法来完成正确的转换:
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.
  }
}

   紧记用户类型转换只发生在编译时而非运行时,在运行时存在o2和MyType类型的转换是允许的,编译器并不知道也不关心这些。这样的语句有不同的行为,这取决于类型st的声明:
= ( MyType ) st;
   但下面的语句返回同样的结果,无论st是什么类型,所以你要尽量使用as避免强制转换。事实上如果类型间并无继承关系,就算存在用户类型的转换操作,下面的语句会产生一个编译时错误:
= st as MyType;
   现在你知道要尽量使用as操作符,那么让我们来讨论下不能使用它的情况。as操作符不能作用于值类型,下面的语句不能通过编译:
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操作符替换之:
object o = Factory.GetValue( );
int i = 0;
if ( o is int )
  i 
= ( int ) o;
   如果o是可以转换为int的其他类型,比如double类型,is操作符也返回false。对于null,它总返回false。
   is操作符只用于不能用as进行转换的情况。否则就是多余的:
// 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进行类型转换,那么is检查是多余的。你只需检查as转换的返回值是否为null,就这么简单。
   现在你清楚了is,as,强制类型转换三者之间的差别,那么你会在foreach循环中用哪个呢,而实际上它是使用的哪个操作呢?
public void UseCollection( IEnumerable theCollection )
{
  
foreach ( MyType t in theCollection )
    t.DoStuff( );
}
   foreach循环中使用的是强制类型转换。foreach产生的代码等效于下面的代码:
public void UseCollection( IEnumerable theCollection )
{
  IEnumerator it 
= theCollection.GetEnumerator( );
  
while ( it.MoveNext( ) )
  {
    MyType t 
= ( MyType ) it.Current;
    t.DoStuff( );
  }
}

   foreach使用强制类型转换同时支持值类型与引用类型。选择使用强制类型转换,foreach语句展现同样的行为,不论目标类型是什么。不管怎样,使用强制类型转换,foreach语句可能产生BadCastException异常。
   因为IEnumerator.Current返回的类型是System.Object,它没有任何转换操作,所以它不符合我们的测试。SecondType对象的集合不能用在前面的方法UseCollection(),因为转换会失败,你已经知道了。foreach语句并不检查集合中的运行时类型对象强制类型转换是否有效,它只检查System.Object类型与循环中定义的类型MyType之间的转换是否有效。
   最后,有时你可能比较关注Object的精确类型,而不是是否可把当前类型转换成目标类型。对任何继承自目标类型的类型as操作符返回true。GetType()方法返回对象的运行时类型,它提供比is/as操作符更严格的测试,它返回对象的类型并可以与指定的类型比较。
   重新考虑方法:
public void UseCollection( IEnumerable theCollection )
{
  
foreach ( MyType t in theCollection )
    t.DoStuff( );
}
   如果你创建一个继承自MyType类型的类NewType,那么NewType对象的集合在这个方法中工作正常:
public class NewType : MyType
{
  
// contents elided.
}
   如果你想写一个方法使所有MyType类型的实例都运作正常,上面那个已经很好了。如果你想写一个方法只有精确的MyType类型运作,你只需做精确的类型比较。这里你必须在foreach循环里面实现。大多数时候在为对象做相等测试时知道其精确类型是比较重要的(参见Item9)。在其他的类型比较中,as/is提供的.isinst比较语义上已经基本正确了。
   好的面向对象实践指出应尽量避免类型转换,但有时这是无法避免的。当无法避免类型转换时,你应尽可能地用语言提供的is/as操作符来明确表达你的意图。不同的强制类型转换有不同的规则。is/as语句大多数情况下是正确的语义,它们只有在对象类型被测试为正确的类型时发生。尽可能地用它们来进行转换操作而不是强制类型转换,它们至少如你所期望的成功或失败,而强制类型转换可能产生意想不到的副作用。