面試官:C#中的 as 和 is的區(qū)別?
?本文是
《編寫高質(zhì)量代碼改善C#程序的157個建議》第一章基本語言要素之建議3區(qū)別對待強(qiáng)制類型轉(zhuǎn)換與as和is,喜歡本書請到各大商城購買原書,支持正版。
在闡述本建議之前,首先需要明確什么是強(qiáng)制類型轉(zhuǎn)換,以及強(qiáng)制類型轉(zhuǎn)換意味著什么。從語法結(jié)構(gòu)上來看,類似下面的代碼就是強(qiáng)制類型轉(zhuǎn)換。
secondType = (SecondType)firstType;
但是,強(qiáng)制類型轉(zhuǎn)換可能意味著兩件不同的事情:
FirstType和SecondType彼此依靠轉(zhuǎn)換操作符來完成兩個類型之間的類型轉(zhuǎn)換。 FirstType是SecondType的基類。
類型之間如果存在強(qiáng)制類型轉(zhuǎn)換,那么它們之間的關(guān)系,要么是第一種,要么是第二種,不能同時既是繼承的關(guān)系,又提供了類型轉(zhuǎn)換符。
首先看第一種情況,當(dāng)FirstType和SecondType存在轉(zhuǎn)換操作符時的代碼如下:
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 = $"類型轉(zhuǎn)換自:{firstType.Name}" };
return secondType;
}
}
在這種情況下,如果想類型轉(zhuǎn)換成功則必須使用強(qiáng)制類型轉(zhuǎn)換,而不是使用as操作符。
FirstType firstType = new FirstType { Name="First Type"};
SecondType secondType = (SecondType)firstType; // 類型轉(zhuǎn)換成功
// secondType = firstType as SecondType; // 編譯期類型轉(zhuǎn)換失敗,編譯通不過
不過,這里需要討論的不是像以上代碼這樣的簡單應(yīng)用,而是稍微復(fù)雜一點的應(yīng)用。為了滿足更進(jìn)一步的需求,我們需要寫一個通用的方法,需要對FirstType或者SecondType做一些處理,方法看起來應(yīng)該像下面這樣:
static void DoWithSomeType(object obj)
{
SecondType secondType = (SecondType)obj;
}
?注意 是否對這種方法聲明方式有一點熟悉?事實上,如果再加一個參數(shù)EventArgs,上面的方法就可以注冊成為一個典型的CLR事件方法了。
如果運行本段代碼,會帶來一個問題:若在調(diào)用方法的時候,傳入的參數(shù)是一個FirstType對象,那就會引發(fā)異常。你可能會問,在上一段代碼中,有這樣的寫法:
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;
也就是說,這段代碼和上段代碼相比,僅僅多了一層類型轉(zhuǎn)換,實際上obj還是firstType,為什么類型轉(zhuǎn)換就失敗了呢?這是因為編譯器還不夠聰明,或者說我們欺騙了編譯器。針對(SecondType)obj,編譯器首先判斷的是:SecondType和object之間有沒有繼承關(guān)系。因為在C#中,所有的類型繼承自object的,所以上面的代碼編譯起來肯定沒有問題。但是編譯器會自動產(chǎn)生代碼來檢查obj在運行時是不是SecondType,這樣就繞過了類型轉(zhuǎn)換操作符,所以會轉(zhuǎn)換失敗。因此,這里的建議是:
如果類型之間都上溯到了某個共同的基類,那么根據(jù)此基類進(jìn)行的類型轉(zhuǎn)換(即基類類型轉(zhuǎn)換為子類本身)應(yīng)該使用as。子類與子類之間的類型轉(zhuǎn)換,則應(yīng)該提供類型轉(zhuǎn)換操作符,以便進(jìn)行強(qiáng)制類型轉(zhuǎn)換。
?注意 再次強(qiáng)調(diào),類型轉(zhuǎn)換操作符實際上就是一個方法,類型的轉(zhuǎn)換需要手工寫代碼完成。
為了編寫更健壯的DoWithSomeType方法,應(yīng)該按如下方式改造它:
static void DoWithSomeType(object obj)
{
SecondType secondType = obj as SecondType;
if (secondType != null)
{
// 省略
}
}
as操作符永遠(yuǎn)不會拋出異常,如果類型不匹配(被轉(zhuǎn)換對象的運行時類型即不是所轉(zhuǎn)換的目標(biāo)類型,也不是其派生類型),或者類型轉(zhuǎn)換的源對象為null,那么類型轉(zhuǎn)換之后的值也為null。改造前的DoWithSomeType方法會因為引發(fā)異常帶來效率問題,而使用as后,就可以完美地避免這種問題。
現(xiàn)在,再來看第二種情況,即FirstType是SecondType的基類。在這種情況下,即可以使用強(qiáng)制類型轉(zhuǎn)換,也可以使用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
{
}
但是,即使可以使用強(qiáng)制類型轉(zhuǎn)換,從效率的角度來看,也建議大家使用as操作符。
知道了強(qiáng)制類型轉(zhuǎn)換和as之間的區(qū)別,我們再來看一下is操作符。DoWithSomeType的另一個版本,可以這樣來實現(xiàn),代碼如下所示:
static void DoWithSomeType(object obj)
{
if (obj is SecondType)
{
SecondType secondType = obj as SecondType;
// 省略
}
}
這個版本顯然沒有上一個版本的效率高,因為當(dāng)前這個版本進(jìn)行了兩次類型檢測。但是,as操作符有一個問題,即它不能操作基元類型。如果涉及基元類型的算法,就需要通過is類型轉(zhuǎn)換前的類型來進(jìn)行判斷,以免類型轉(zhuǎn)換失敗。


@程序員,這筆錢下個月可以領(lǐng)!

臥槽:微信可以這樣換個字體了!
