5.3 as和is运算符
我们知道,隐式转换是安全的,而显式转换往往是不安全的,有可能造成精度损失,甚至会抛出异常。但类型转换又是不可避免的,例如对于某些集合类型,常常会用到System.Object类型的变量(使用泛型可以避免这种情况,关于泛型请参阅第17章),对于非泛型集合,在将数据放入集合时将发生“向上转型”,即当前类型信息丢失,数据的类型成了object类型;而当需要把数据从集合取出时,因为需要恢复数据的本来类型,因此也就需要执行“向下转型”到它本来的类型。因此,如何更安全地进行类型转换就是一个值得探讨的问题了。幸好,C#已经为我们提供了解决方案,我们有两种选择:
❑使用as运算符进行类型转换;
❑先使用is运算符判断类型是否可以转换,再使用()运算符进行显式类型转换。
那么,我们先来介绍一下as和is运算符:
as运算符用于在两个引用类型之间进行转换,如果转换失败则返回null,并不抛出异常,因此转换是否成功可以通过结果是否为null进行判断,并且只能在运行时才能判断。
1 using System;
2
3 namespace ProgrammingCSharp4
4{
5 class Class1
6{
7}
8
9 class Class2
10{
11}
12
13 class TypeConvert
14{
15 static void Main(string[]args)
16{
17 object[]objArray=new object[6];
18 objArray[0]=new Class1();
19 objArray[1]=new Class2();
20 objArray[2]="hello";
21 objArray[3]=123;
22 objArray[4]=123.4;
23 objArray[5]=null;
24
25 for(int i=0;i<objArray.Length;++i)
26{
27 string s=objArray[i]as string;
28 Console.Write("{0}:",i);
29 if(s!=null)
30{
31 Console.WriteLine(“是string类型,其值为:'"+s+"'");
32}
33 else
34{
35 Console.WriteLine(“不是string类型”);
36}
37}
38}
39}
40}
这段代码用到了前面讲过的知识:数组、Console对象、命名空间、类;也有在后面的章节会讲的知识,如for循环、if判断等,这里可以先简单了解,具体用法请参考相关章节。
编译运行上述代码,输出结果为:
0:不是string类型
1:不是string类型
2:是string类型,其值为:'hello'
3:不是string类型
4:不是string类型5:不是string类型
特别要注意的是,as运算符有一定的适用范围,它只适用于引用类型或可以为null的类型,而无法执行其他转换,如值类型的转换以及用户自定义的类型转换,这类转换应使用强制转换表达式来执行。
is运算符用于检查对象是否与给定类型兼容,并不执行真正的转换。如果判断的对象引用为null,则返回false。由于仅仅判断是否兼容,因此它并不会抛出异常。用法如下:
if(obj is MyObject)
{
}
上述代码可以确定obj变量是否是MyObject类型的实例,或者是MyObject类的派生类。
同样,也要注意is的适用范围,它只适用于引用类型转换、装箱转换和拆箱转换。而不支持其他的类型转换,如值类型的转换。
现在,我们已经了解了as和is运算符,在实际工作中建议尽量使用as运算符,而少使用()运算符显式转换。理由如下:
❑无论是as还是is运算符,都比直接使用()运算符强制转换更安全;
❑不会抛出异常,免除了使用try……catch进行异常捕获的必要和系统开销,只需要判断是否为null;
❑使用as比使用is性能上更好,这一点可以通过代码清单5-9来说明。
代码清单5-9 as和is运算符的性能对比
1 using System;
2 using System.Diagnostics;
3
4 namespace ProgrammingCSharp4
5{
6 class Class1{}
7
8 class AsIsSample
9{
10 private Class1 c1=new Class1();
11
12 public static void Main()
13{
14 AsIsSample aiSample=new AsIsSample();
15 Stopwatch timer=new Stopwatch();
16 timer.Start();
17 for(int i=0;i<10000;i++)
18{
19 aiSample.DoSomething1();
20}
21 timer.Stop();
22 decimal micro=timer.Elapsed.Ticks/10m;
23 Console.WriteLine(“执行DoSomething1()10000次的时间:{0:F1}微秒.”,micro);
24
25 timer=new Stopwatch();
26 timer.Start();
27 for(int i=0;i<10000;i++)
28{
29 aiSample.DoSomething2();
30}
31 timer.Stop();
32 micro=timer.Elapsed.Ticks/10m;
33 Console.WriteLine(“执行DoSomething2()10000次的时间:{0:F1}微秒.”,micro);
34}
35
36 public void DoSomething1()
37{
38 object c2=c1;
39 if(c2 is Class1)
40{
41 Class1 c=(Class1)c2;
42}
43}
44
45 public void DoSomething2()
46{
47 object c2=c1;
48 Class1 c=c2 as Class1;
49 if(c!=null)
50{
51//其他操作……
52}
53}
54}
55}
输出为:
执行DoSomething1()10000次的时间:288.9微秒.
执行DoSomething2()10000次的时间:258.6微秒.
从第36行开始,声明并定义了两个方法:DoSmothing1和DoSmothing2,其中分别使用is和as运算符进行类型转换。在第17行和第27行对每个方法分别连续调用10 000次,通过使用BCL中的Stopwatch对象对两者的调用时间进行统计。从结果可以看出,DoSomething2()的性能比DoSomething1()要好。至于原因,可以通过查看DoSomething1和DoSomething2两个方法的CIL代码来一探究竟。
方法DoSomething1的CIL代码如代码清单5-10所示。
代码清单5-10 方法DoSomething1的CIL代码
1.method public hidebysig instance void DoSomething1()cil managed
2{
3//Code size 34(0x22)
4.maxstack 2
5.locals init([0]object c2,
6[1]class ProgrammingCSharp4.Class1 c,
7[2]bool CS$4$0000)
8 IL_0000:nop
9 IL_0001:ldarg.0
10 IL_0002:ldfld class ProgrammingCSharp4.Class1
ProgrammingCSharp4.AsIsSample:c1
11 IL_0007:stloc.0
12 IL_0008:ldloc.0
13 IL_0009:isinst ProgrammingCSharp4.Class1
14 IL_000e:ldnull
15 IL_000f:cgt.un
16 IL_0011:ldc.i4.0
17 IL_0012:ceq
18 IL_0014:stloc.2
19 IL_0015:ldloc.2
20 IL_0016:brtrue.s IL_0021
21 IL_0018:nop
22 IL_0019:ldloc.0
23 IL_001a:castclass ProgrammingCSharp4.Class1
24 IL_001f:stloc.1
25 IL_0020:nop
26 IL_0021:ret
27}//end of method AsIsSample:DoSomething1
代码清单5-10 的第13行首先测试了是否能转换到Class1类型,如果可以则进行转换;第23行再次测试能否转换到Class1类型,如果测试成功则进行转换。
方法DoSomething2的CIL代码如代码清单5-11所示。
代码清单5-11 方法DoSomething2的CIL代码
1.method public hidebysig instance void DoSomething2()cil managed
2{
3//Code size 26(0x1a)
4.maxstack 2
5.locals init([0]object c2,
6[1]class ProgrammingCSharp4.Class1 c,
7[2]bool CS$4$0000)
8 IL_0000:nop
9 IL_0001:ldarg.0
10 IL_0002:ldfld class ProgrammingCSharp4.Class1
ProgrammingCSharp4.AsIsSample:c1
11 IL_0007:stloc.0
12 IL_0008:ldloc.0
13 IL_0009:isinst ProgrammingCSharp4.Class1
14 IL_000e:stloc.1
15 IL_000f:ldloc.1
16 IL_0010:ldnull
17 IL_0011:ceq
18 IL_0013:stloc.2
19 IL_0014:ldloc.2
20 IL_0015:brtrue.s IL_0019
21 IL_0017:nop
22 IL_0018:nop
23 IL_0019:ret
24}//end of method AsIsSample:DoSomething2
代码清单5-11 的第13行同样测试了能否转换到Class1类型,如果可以则进行转换。
由此可见,前者进行了两次测试和检查,而后者只进行了一次测试,这是造成两者之间性能差异的原因。
现在我们总结下,什么场合该使用is,什么场合该使用as:如果测试对象的目的是确定它是否属于所需类型,并且如果测试结果为真,就要立即进行转换,这种情况下使用as操作符的效率更高;但有时仅仅只是测试,并不想立即转换,也可能根本就不会转换,只是在对象实现了接口时,要将它加到一个列表中,这时is操作符就是一种更好的选择。