2011-11-01 98 views
18

在閱讀微軟的文檔,我無意中發現了這樣一個有趣的代碼示例:爲什麼顯式將泛型轉換爲類類型有限制,但是將泛型轉換爲接口類型沒有限制?

interface ISomeInterface 
{...} 
class SomeClass 
{...} 
class MyClass<T> 
{ 
    void SomeMethod(T t) 
    { 
     ISomeInterface obj1 = (ISomeInterface)t;//Compiles 
     SomeClass  obj2 = (SomeClass)t;  //Does not compile 
    } 
} 

這意味着你可以明確地但不投你的通用的接口類,除非你有一個約束。那麼,我仍然無法理解這個決定背後的邏輯,因爲接口和類的類型轉換都在拋出異常,那麼爲什麼只能保護這些異常呢?

BTW-周圍有編譯錯誤的方式,但是這並不在我的腦海中刪除邏輯混亂:

class MyOtherClass 
{...} 

class MyClass<T> 
{ 

    void SomeMethod(T t) 

    { 
     object temp = t; 
     MyOtherClass obj = (MyOtherClass)temp; 

    } 
} 
+0

出於好奇:你能輸入「SomeClass obj2 =(SomeClass)(object)t;」? –

+0

是的,這是由第二個片段 –

+1

完成檢查這個http://philipm.at/2011/1014/,發現它時,試圖找到解釋。擾亂警報 - 可能會讓你更加困惑!;) –

回答

5

這正是你在正常情況下取得 - 沒有泛型 - 當您嘗試投類沒有繼承關係之間:

public interface IA 
{ 
} 

public class B 
{ 
} 

public class C 
{ 
} 

public void SomeMethod(B b) 
{ 
    IA o1 = (IA) b; <-- will compile 
    C o2 = (C)b; <-- won't compile 
} 

因此,沒有約束,泛型類的行爲就好像有類之間沒有任何關係。

繼續...

好吧,讓我們說有人做這樣的:

public class D : B, IA 
{ 
} 

,然後調用:

SomeMethod(new D()); 

現在,你就會明白爲什麼編譯器讓接口投通。它在編譯時確實無法知道接口是否被實現。

請記住,D類很可能是編寫它的人使用你的程序集編寫的。所以編譯器不可能拒絕編譯它。它必須在運行時檢查。

+0

好點,但它仍然混淆爲什麼他們選擇允許一個演員,並禁止第二個? –

+0

更新了我的答案以包含該內容。 –

+0

如果你嘗試'D o3 =(D)b;'SomeMethod(B b)''會編譯?這讓我感到同樣的觀點也適用這就是你傳遞可能是一個後裔,其有效期爲演員,但可能不會是......(是的,我是懶惰,而不是隻是想它自己的時刻)。 – Chris

1

沒有問題。唯一的區別是,在第一種情況下,編譯器可以在編譯時檢測到,那裏沒有可能的強制轉換,但是他不能對接口進行如此「確定」,因此在這種情況下,錯誤只會在運行時。所以,

// Compiles 
ISomeInterface obj1 = (ISomeInterface)t; 

// Сompiles too! 
SomeClass obj2 = (SomeClass)(object)t;  

將在運行時產生相同的錯誤。

所以原因可能是:編譯器不知道類實現了哪些接口,但它知道類繼承(因此(SomeClass)(object)t方法有效)。換句話說:在CLR中禁止無效投射,唯一的區別是在某些情況下,它可以在編譯時檢測到,而在某些情況下則不能。這背後的主要原因是,即使編譯器知道所有類的接口,它也不知道它的後代,它可以實現它,並且對於T有效。請考慮以下情形:

namespace ConsoleApplication2 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      MyClass<SomeClass> mc = new MyClass<SomeClass>(); 

      mc.SomeMethod(new SomeClassNested()); 

     } 
    } 

    public interface ISomeInterface 
    { 
    } 

    public class SomeClass 
    { 

    } 

    public class SomeClassNested : SomeClass, ISomeInterface 
    { 

    } 

    public class MyClass<T> 
    { 
     public void SomeMethod(T t) 
     { 
      // Compiles, no errors at runtime 
      ISomeInterface obj1 = (ISomeInterface)t; 
     } 
    } 
} 
+0

爲什麼你覺得他不知道嗎?所有內容都寫入元數據表中的程序集中。 –

+0

@衣服好,一切都在組裝,這是肯定的,但這並不意味着編譯器知道它。編譯器是比較「愚蠢」的東西。 –

+0

你能否鏈接一些對編譯器的引用,而不能找出類實現的接口?這聽起來不對,但我很樂意接受有關這方面的教育。 – Chris

0

我覺得鑄件之間的差異的接口,並鑄造 一類在於,C#支持多個「繼承」 只對接口的事實。那是什麼意思?編譯器只能在編譯時確定 是否對類 有效,因爲C#不允許對類進行多重繼承。

另一方面,編譯器在編譯時不知道是否你的類實現了在轉換中使用的接口。爲什麼? 有人可以從你的班級繼承並實施你演員中使用的界面 。所以,編譯器在編譯時並不知道這一點。 (見下面的SomeMethod4())。

但是編譯器能夠確定如果您的類是密封的,則向接口轉換的 是否有效。

請看下面的例子:

interface ISomeInterface 
{} 
class SomeClass 
{} 

sealed class SealedClass 
{ 
} 

class OtherClass 
{ 
} 

class DerivedClass : SomeClass, ISomeInterface 
{ 
} 

class MyClass 
{ 
    void OtherMethod(SomeClass s) 
    { 
    ISomeInterface t = (ISomeInterface)s; // Compiles! 
    } 

    void OtherMethod2(SealedClass sc) 
    { 
    ISomeInterface t = (ISomeInterface)sc; // Does not compile! 
    } 

    void OtherMethod3(SomeClass c) 
    { 
    OtherClass oc = (OtherClass)c; // Does not compile because compiler knows 
    }        // that SomeClass does not inherit from OtherClass! 

    void OtherMethod4() 
    { 
    OtherMethod(new DerivedClass()); // In this case the cast to ISomeInterface inside 
    }         // the OtherMethod is valid! 
} 

這同樣適用於仿製藥真的。

希望,這有助於。

2

最大的區別是接口保證是一個引用類型。價值類型是麻煩製造者。它是在C#語言規範,章節6.2.6中明確提到,與演示該問題一個極好的例子:


上述規則不允許從不受約束的類型參數的直接顯式轉換到非接口類型,這可能令人驚訝。這條規則的原因是爲了防止混淆,並明確這種轉換的語義。例如,請考慮下面的聲明:

class X<T> 
{ 
    public static long F(T t) { 
     return (long)t;    // Error 
    } 
} 

如果T的直接顯式轉換爲int被允許,一個可能很容易想到的是X.F(7)將返回7L。但是,它不會,因爲只有在編譯時已知數字類型時才考慮標準數字轉換。爲了使語義更清楚,上面的例子必須改爲被寫成:

class X<T> 
{ 
    public static long F(T t) { 
     return (long)(object)t;  // Ok, but will only work when T is long 
    } 
} 

此代碼現在將編譯,但執行XF(7)隨後將拋出異常在運行時,由於裝箱的int不能被直接轉換很長。