Effective-CSharp-3优先考虑is或as运算符,尽量少用强制类型转换

若使用 C#,那么就必须适应静态类型检查机制,该机制在很多情况下都会起到良好的作用。

静态类型检查意味着编译器会把类型不符的用法找出来,这也令应用程序在运行期能够少做一些类型检查。然而有的时候还必须在运行期检查对象的类型,比如,如果所使用的框架已经在方法签名里把参数写成了 Object,那么可能就得先将该参数转成其他类型(例如其他的类或接口),然后才能继续编写代码。有两种办法能实现转换,一是使用 as 运算符,二是通过强制类型转换 (cast) 来绕过编译器的类型检查。在这之前,可以先通过 is 判断该操作是否合理,然后再使用 as 运算符 或执行强制类型转换。

在这两种方法中,应该优先考虑第一种办法,这样做要比盲目地进行类型转换更加安全,且在运行的时候更有效率。as 及 is 运算符不会考虑由用户所定义的转换。只有当运行期的类型与要转换到的类型相符合时,该操作才能顺利地执行。这种类型转换操作很少会为了类型转换而构建新的对象(但若用 as 运算符把装箱的值类型转换成未装箱且可以为 null 的值类型,则会创建新的对象)。

下面来看一个例子。如果需要把 object 对象转换为 MyType 实例,那么可以这样写:

1
2
3
4
5
6
7
8
9
10
11
12
object o = Factory.GetObject();
// Version one:
MyType t = 0 as MyType;

if(t!= null)
{
// work with t, it's a MyType
}
else
{
// report the failure
}

此外,也可以这样写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
object o = Factory.GetObject();
// Version one:
try
{
MyType t;
t = (MyType) o;
if (t != null)
{
// work with t, it's a MyType
}
}
catch(InvalidCastException)
{
// report the conversion failure
}

大家应该会觉得第一种写法比第二种更简单,而且更好理解。由于第一种写法不需要使用 try/catch 结构,因此程序的开销与代码量都比较低。如果采取第二种写法,那么不仅要捕获异常,而且还得判断t是不是 null。强制类型转换在遇到 null 的时候并不抛出异常,这导致开发者必须处理两种情况:一种是 o 本来就为 null,因此强制转换后所得的 t 也是 null;另一种是程序因 o 无法类型转换为 MyType 而抛出异常。如果采用第一种写法,那么由于 as 操作在这两种特殊情况下的结果都是 null,因此只需要用 if (t!= null) 来概括处理就可以了。

as 运算符与强制类型转换之间的最大区别在于如何对待由用户所定义的转换逻辑。as 与 is 运算符只会判断待转换的那个对象在运行期是何种类型,并据此做出相应的处理,除了必要的装箱与取消装箱操作,它们不会执行其他操作。如果待转换的对象既不属于目标类型,也不属于由目标类型所派生出来的类型,那么 as 操作就会失败。反之,强制类型转换操作则有可能使用某些类型转换逻辑来实现类型转换,这不仅包含由用户所定义的类型转换逻辑,而且还包括内置的数值类型之间的转换。例如可能发生从 long 至 short 转换,这种转换可能导致信息丢失。