1.1.1 摘要
C#是一门强类型语言,一般情况下,我们最好避免将一个类型强制转换为其他类型,但有些时候难免要进行类型转换。
先想想究竟哪些操作可以进行类型转换(先不考虑.NET提供的Parse),一般我们都有以下选择:
- 使用as操作符转换,
- 使用传统C风格的强制转型
- 使用is来做一个转换测试,然后再使用as操作符或者强制转
1.1.2 正文
正确的选择应该是尽可能地使用as操作符,因为它比强制转型要安全,而且在运行时层面也有比较好的效率(注意的是as和is操作符都不执行任何用户自定义的转换,只有当运行时类型与目标转换类型匹配时,它们才会转换成功)。
现在我们通过一个简单的例子说明as和强制转换之间的区别,首先我们定义一间获取不同类型对象的工厂,然后我们把未知类型转换为自定义类型。
object o = Factory.GetObject();
MyType t = o as MyType;
if (t == null)
{
//转换成功
}
else
{
//转换失敗
}
object o = Factory.GetObject();
try
{
MyType t = (MyType) o;
if (t != null)
{
////转换成功
}
else
{
////转换失敗
}
}
catch
{
////异常处理
}
通过上述代码我们发现as类型转换失败时值为null不抛出异常,但强制转换如果转换失败会抛出异常所以我们要添加异常处理。
现在我们对as和强制转换有了初步的了解,假设现在我们定义了一个抽象类Foo,然后Foo1继承于它,并且再定义一个基类Logger,在Foo1中定义与Logger类型隐式转换具体如下:
Foo1 myFoo; //// Inherits abstract class.
Logger myFoo; //// base class.
public class Foo1 : Foo
{
private Logger _value;
/// <summary>
/// 隐式自定义类型转换。
/// </summary>
/// <param name="foo1"></param>
/// <returns></returns>
public static implicit operator Logger(Foo1 foo1)
{
return foo1._value;
}
}
现在我们猜猜看以下的类型转换是否成功(提示:从编译和运行时类型转换角度考虑)。
object myFoo = container.Resolve<Foo>(); //获取未Foo1类型
try
{
Logger myFoo1 = (Logger)myFoo;
if (myFoo1 != null)
{
Console.WriteLine("Covert successful.");
}
}
catch
{
Console.WriteLine("Covert failed.");
}
相信聪明的大家已经想出答案了,激动人心的时刻到了现在让我们公布答案:转换失败抛出异常。
图1转换失败结果
首先我们要从编译和运行时角度来分析,在编译时myFoo的类型为System.Object,这时编译器会检测是否存在自定义由Object到Logger的类型转换。如果没有找到合适转换,编译器将生成代码检测myFoo的运行时类型和Logger比较,由于myFoo的运行时类型为Foo1,而且我们自定义了由Foo1到Logger的类型转换,估计这样可以转换成功了吧!然而恰恰没有转换成功,这究竟是什么原因呢?让我们了解一下编译器对于隐式类型转换的原理吧。
图2编译和运行时自定义类型转换
通过上图我们发现用户自定义的转换操作符只作用于对象的编译时类型,而非运行时类型上,OK现在让修改一下代码让我们编译器认识自定义类型中。
using (IUnityContainer container = new UnityContainer())
{
UnityConfigurationSection section = (UnityConfigurationSection)ConfigurationManager.GetSection("unity"); //获取container名称为CfgClass下的配置
section.Containers["CfgClass"].Configure(container);
object tempFoo = container.Resolve<Foo>(); //获取未Foo1类型
Foo1 myFoo = tempFoo as Foo1; //使用as先把object转型为Foo1
try
{
Logger myFoo1 = (Logger)myFoo;
if (myFoo1 != null)
{
Console.WriteLine("Covert successful.");
}
}
catch
{
Console.WriteLine("Covert failed.");
}
Console.ReadKey();
}
图3转换成功结果
现在类型可以转换成功,这是因为编译器使用了我们自定义的隐式转换,由于myFoo这次的编译类型为Foo1,编译器首先查找是否存在Foo1和Logger自定义转换类型,由于我们定义了一种由Foo1到Logger的隐式类型转换所以转换成功。
通过上述我们发现了as给我们带来的好处,但是有一点我们要注意的是as只能用于引用类型不能用于值类型。那我就有个问题了在进行类型转换之前如果我们并不知道要转换的是值类型还是引用类型,那该怎么办呢?现在是is登场的时候了。
bject tempFoo = container.Resolve<Foo>(); //获取未Foo1类型
int myInt = tempFoo as int; //compile error
as不能用于值类型,这是因为值类型不能为null(注意:C#2.0中,微软提供了Nullable类型,允许用它定义包含null值,即空值的数据类型)像这种情况我们应该使用强制类型转换。
object tempFoo = container.Resolve<Foo>(); //获取未Foo1类型
try
{
int myInt = (int)tempFoo; //转换成功
if (myFoo1 != null)
{
Console.WriteLine("Covert successful.");
}
}
catch
{
Console.WriteLine("Covert failed.");
}
大家可以发现和我们之前使用的强制转换类似,而且还有处理异常,现在修改一下我们代码让它更加简洁实现如下:
object tempFoo = container.Resolve<Foo>(); //获取未Foo1类型
int i = 0; //值类型转换
if (tempFoo is int)
{
i = (int) tempFoo;
}
object tempFoo = container.Resolve<Foo>(); //获取未Foo1类型
Logger myFoo1 = null; //引用类型转换
if (tempFoo is Logger)
{
myFoo1 = tempFoo as Logger;
}
1.1.3 总结
as和强制转换之间最大的区别就在于如何处理用户自定义的转换。操作符 as和 is 都只检查被转换对象的运行时类型,并不执行其他的操作。如果被转换对象的运行时类型既不是所转换的目标类型,也不是其派生类型,那么转型将告失败。但是强制转型则会使用转换操作符来执行转型操作,这包括任何内建的数值转换(如:long转int)。
一般情况我们应该先考虑使用as进行类型转换,然后再考虑使用is,最后才考虑使用强制转换。
|
as |
强制转换 |
转换失败是否抛出异常 |
No |
Yes |
支持值类型和引用类型转换 |
只支持引用类型 |
Yes |