[英]Cast vs 'as' operator revisited
我知道有幾個帖子已經涉及了強制轉換和as
運算符之間的區別。 他們都大多重申相同的事實:
as
運算符不會拋出,但如果強制轉換失敗則返回null
as
運算符僅適用於引用類型 as
運算符不會使用用戶定義的轉換運算符 然后,答案傾向於無休止地討論如何使用或不使用其中一個或每個的利弊,甚至是他們的表現(我根本不感興趣)。
但是這里還有更多工作要做。 考慮:
static void MyGenericMethod<T>(T foo)
{
var myBar1 = foo as Bar; // compiles
var myBar2 = (Bar)foo; // does not compile ('Cannot cast expression of
// type 'T' to type 'Bar')
}
請不要介意這個明顯懊悔的例子是否是好的做法。 我在這里關注的是兩者之間非常有趣的差異,因為演員不會編譯,而as
則會編譯。 我真的想知道是否有人可以對此有所了解。
正如經常提到的那樣, as
運算符忽略了用戶定義的轉換,但在上面的例子中,顯然兩者的能力更強。 需要注意的是as
遠在編譯器而言,有(在編譯時未知)之間沒有任何已知的連接類型T和酒吧。 演員陣容完全是“運行時”。 我們是否應該懷疑演員陣容在編譯時是全部還是部分解決而且as
運算符不是?
順便說一下,添加類型約束毫不奇怪地修復了強制轉換,因此:
static void MyGenericMethod<T>(T foo) where T : Bar
{
var myBar1 = foo as Bar; // compiles
var myBar2 = (Bar)foo; // now also compiles
}
為什么as
運算符編譯和轉換不?
解決您的第一個問題:不僅僅是as
運算符忽略了用戶定義的轉換,盡管這是相關的。 更重要的是,演員操作員做了兩件相互矛盾的事情。 演員經營者意味着:
我知道編譯時類型Foo的這個表達式實際上是運行時類型Bar的對象。 編譯器,我現在告訴你這個事實,以便你可以利用它。 假設我是正確的,請生成代碼; 如果我不正確,那么你可以在運行時拋出異常。
我知道編譯時類型Foo的這個表達式實際上是運行時類型Foo。 有一種將Foo的部分或全部實例轉換為Bar實例的標准方法。 編譯器,請生成這樣的轉換,如果在運行時發現轉換的值不可轉換,則在運行時拋出異常。
那些是對立的 。 巧妙的技巧,讓一個運營商做相反的事情。
相比之下, as
運算符只具有第一感。 僅as
裝箱 , 拆箱和保留表示的轉換。 演員可以完成所有這些以及更改表示更改的轉換。 例如,將int轉換為short會將表示形式從四字節整數更改為兩字節整數。
這就是為什么“原始”演員在無約束的仿制品上不合法的原因; 因為編譯器沒有足夠的信息來確定它是什么類型的轉換:裝箱,拆箱,表示保留或表示改變。 用戶的期望是通用代碼中的強制轉換具有更強類型代碼中的強制轉換的所有語義,並且我們無法有效地生成該代碼。
考慮:
void M<T, U>(T t, out U u)
{
u = (U)t;
}
你希望它能起作用嗎? 我們生成哪些代碼可以處理:
M<object, string>(...); // explicit reference conversion
M<string, object>(...); // implicit reference conversion
M<int, short>(...); // explicit numeric conversion
M<short, int>(...); // implicit numeric conversion
M<int, object>(...); // boxing conversion
M<object, int>(...); // unboxing conversion
M<decimal?, int?>(...); // lifted conversion calling runtime helper method
// and so on; I could give you literally hundreds of different cases.
基本上我們必須為再次啟動編譯器的測試發出代碼,對表達式進行全面分析,然后發出新代碼。 我們在C#4中實現了這個功能; 它被稱為“動態”,如果這是你想要的行為,你可以隨意使用它。
我們有沒有這些問題as
,因為as
唯一要做三兩件事。 它可以進行裝箱轉換,拆箱轉換和類型測試,我們可以輕松生成執行這三項操作的代碼。
我們是否應該懷疑演員陣容在編譯時是全部還是部分解決而且作為運算符不是?
你自己給出了答案,在你的問題開始:“as運算符不使用用戶定義的轉換操作符” -同時,中投確實 ,這意味着它需要找到在編譯時的經營者( 或缺席 )。
請注意,就編譯器而言,類型T和Bar之間沒有已知的連接(編譯時未知)。
的T型是未知的事實意味着,編譯器無法知道是否有它和酒吧之間沒有任何聯系。
請注意, (Bar)(object)foo
確實有效,因為沒有類型可以將轉換運算符賦予Object [因為它是所有內容的基類],並且已知從對象到Bar的轉換不必處理轉換運營商。
這是類型安全的問題。
任何T
都不能轉換為Bar
,但任何T
都可以“看到” as
Bar
因為即使沒有從T
到Bar
轉換,行為也很明確。
第一個編譯只是因為as
關鍵字是如何定義的。 如果無法強制轉換,則返回null
。 它的安全性因為as
關鍵字本身不會導致任何運行時問題。 您可能已經或可能沒有檢查變量為null的事實是另一回事。
可以將as
視為TryCast方法。
編譯器不知道如何生成適用於所有情況的代碼。
考慮這兩個電話:
MyGenericMethod(new Foo1());
MyGenericMethod(new Foo2());
現在假設Foo1
包含一個可以將其轉換為Bar
實例的Foo2
轉換運算符,而Foo2
則從Bar
Foo2
而來。 顯然,所涉及的代碼在很大程度上取決於你傳入的實際T
在您的特定情況下,您說該類型已經是Bar
類型,因此顯然編譯器可以只進行引用轉換,因為它知道這是安全的,沒有進行轉換或需要轉換。
現在, as
轉換更具“探索性”,它不僅不考慮用戶轉換,而且明確允許轉換無意義,因此編譯器允許轉換。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.