面试官:C#中的 as 和 is的区别?
❝本文是
《编写高质量代码改善C#程序的157个建议》
第一章基本语言要素
之建议3区别对待强制类型转换与as和is
,喜欢本书请到各大商城购买原书,支持正版。
在阐述本建议之前,首先需要明确什么是强制类型转换,以及强制类型转换意味着什么。从语法结构上来看,类似下面的代码就是强制类型转换。
secondType = (SecondType)firstType;
但是,强制类型转换可能意味着两件不同的事情:
FirstType和SecondType彼此依靠转换操作符来完成两个类型之间的类型转换。 FirstType是SecondType的基类。
类型之间如果存在强制类型转换,那么它们之间的关系,要么是第一种,要么是第二种,不能同时既是继承的关系,又提供了类型转换符。
首先看第一种情况,当FirstType和SecondType存在转换操作符时的代码如下:
class FirstType
{
public string Name { get; set; }
}
class SecondType
{
public string Name { get; set; }
public static explicit operator SecondType(FirstType firstType)
{
SecondType secondType = new SecondType() { Name = $"类型转换自:{firstType.Name}" };
return secondType;
}
}
在这种情况下,如果想类型转换成功则必须使用强制类型转换,而不是使用as操作符。
FirstType firstType = new FirstType { Name="First Type"};
SecondType secondType = (SecondType)firstType; // 类型转换成功
// secondType = firstType as SecondType; // 编译期类型转换失败,编译通不过
不过,这里需要讨论的不是像以上代码这样的简单应用,而是稍微复杂一点的应用。为了满足更进一步的需求,我们需要写一个通用的方法,需要对FirstType或者SecondType做一些处理,方法看起来应该像下面这样:
static void DoWithSomeType(object obj)
{
SecondType secondType = (SecondType)obj;
}
❝注意 是否对这种方法声明方式有一点熟悉?事实上,如果再加一个参数EventArgs,上面的方法就可以注册成为一个典型的CLR事件方法了。
如果运行本段代码,会带来一个问题:若在调用方法的时候,传入的参数是一个FirstType对象,那就会引发异常。你可能会问,在上一段代码中,有这样的写法:
FirstType firstType = new FirstType() { Name = "First Type" };
SecondType secondType = (SecondType)firstType;
而DoWithSomeType方法提供的代码,看起来无非像下面这样:
FirstType firstType = new FirstType() { Name = "First Type" };
object obj = firstType;
SecondType secondType = (SecondType) obj;
也就是说,这段代码和上段代码相比,仅仅多了一层类型转换,实际上obj还是firstType,为什么类型转换就失败了呢?这是因为编译器还不够聪明,或者说我们欺骗了编译器。针对(SecondType)obj,编译器首先判断的是:SecondType和object之间有没有继承关系。因为在C#中,所有的类型继承自object的,所以上面的代码编译起来肯定没有问题。但是编译器会自动产生代码来检查obj在运行时是不是SecondType,这样就绕过了类型转换操作符,所以会转换失败。因此,这里的建议是:
如果类型之间都上溯到了某个共同的基类,那么根据此基类进行的类型转换(即基类类型转换为子类本身)应该使用as。子类与子类之间的类型转换,则应该提供类型转换操作符,以便进行强制类型转换。
❝注意 再次强调,类型转换操作符实际上就是一个方法,类型的转换需要手工写代码完成。
为了编写更健壮的DoWithSomeType方法,应该按如下方式改造它:
static void DoWithSomeType(object obj)
{
SecondType secondType = obj as SecondType;
if (secondType != null)
{
// 省略
}
}
as操作符永远不会抛出异常,如果类型不匹配(被转换对象的运行时类型即不是所转换的目标类型,也不是其派生类型),或者类型转换的源对象为null,那么类型转换之后的值也为null。改造前的DoWithSomeType方法会因为引发异常带来效率问题,而使用as后,就可以完美地避免这种问题。
现在,再来看第二种情况,即FirstType是SecondType的基类。在这种情况下,即可以使用强制类型转换,也可以使用as操作符,代码如下所示:
class Program
{
static void Main(string[] args)
{
SecondType secondType = new SecondType() { Name = "Second Type" };
FirstType firstType1 = (FirstType)secondType;
FirstType firstType2 = secondType as FirstType;
}
}
class FirstType
{
public string Name { get; set; }
}
class SecondType : FirstType
{
}
但是,即使可以使用强制类型转换,从效率的角度来看,也建议大家使用as操作符。
知道了强制类型转换和as之间的区别,我们再来看一下is操作符。DoWithSomeType的另一个版本,可以这样来实现,代码如下所示:
static void DoWithSomeType(object obj)
{
if (obj is SecondType)
{
SecondType secondType = obj as SecondType;
// 省略
}
}
这个版本显然没有上一个版本的效率高,因为当前这个版本进行了两次类型检测。但是,as操作符有一个问题,即它不能操作基元类型。如果涉及基元类型的算法,就需要通过is类型转换前的类型来进行判断,以免类型转换失败。
@程序员,这笔钱下个月可以领!
卧槽:微信可以这样换个字体了!