文章目錄
- 1.1.1 摘要
- 1.1.2 本文
- 1.1.3 總結
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 |