C#中 As 和強制轉換的總結
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 |