2010-08-25 67 views
9

我在使用泛型時如何理解多態性是如何工作的問題。作爲一個例子,我已經定義了以下程序:請幫我理解在c#中使用泛型時的多態性問題#

public interface IMyInterface 
{ 
    void MyMethod(); 
} 

public class MyClass : IMyInterface 
{ 
    public void MyMethod() 
    { 
    } 
} 

public class MyContainer<T> where T : IMyInterface 
{ 
    public IList<T> Contents; 
} 

然後我就可以做到這一點,它工作得很好:

MyContainer<MyClass> container = new MyContainer<MyClass>(); 
container.Contents.Add(new MyClass()); 

我有實現MyInterface很多類。我想寫一個可以接受所有MyContainer對象的方法:

public void CallAllMethodsInContainer(MyContainer<IMyInterface> container) 
{ 
    foreach (IMyInterface myClass in container.Contents) 
    { 
     myClass.MyMethod(); 
    } 
} 

現在,我想要調用此方法。

MyContainer<MyClass> container = new MyContainer<MyClass>(); 
container.Contents.Add(new MyClass()); 
this.CallAllMethodsInContainer(container); 

這沒有奏效。當然,因爲MyClass實現了IMyInterface,我應該能夠投它嗎?

MyContainer<IMyInterface> newContainer = (MyContainer<IMyInterface>)container; 

這也沒有效果。我可以肯定地蒙上了正常MyClass的到IMyInterface的:

MyClass newClass = new MyClass(); 
IMyInterface myInterface = (IMyInterface)newClass; 

所以,至少我還沒有完全誤解了。我不確定我是如何編寫一個接受符合相同接口的類的通用集合的方法。

我有一個計劃,要徹底解決這個問題,如果需要的話,但我真的更喜歡正確地做。

預先感謝您。

+2

這就是人們跑出像協變和逆變這樣的恐怖詞語的地方。 – Greg 2010-08-25 12:27:08

+0

@Greg:從好的方面來說,我覺得自己對這些概念的理解最近已經真正充實了,因爲這樣的問題已經出現過多! – 2010-08-25 12:28:20

+0

這個概念很好,但名字很可怕。 :) – Greg 2010-08-25 12:59:01

回答

4

注:在任何情況下,你必須將Contents場初始化當你把通用約束實現IList<?>

一個具體的對象,你可以這樣做:

public IList<T> Contents = new List<T>(); 

當你不「T,你可以這樣做:

public IList<MyInterface> Contents = new List<MyInterface>(); 

方法1:

變化的方法進行:

public void CallAllMethodsInContainer<T>(MyContainer<T> container) where T : IMyInterface 
{ 
    foreach (T myClass in container.Contents) 
    { 
     myClass.MyMethod(); 
    } 
} 

和代碼段:

MyContainer<MyClass> container = new MyContainer<MyClass>(); 
container.Contents.Add(new MyClass()); 
this.CallAllMethodsInContainer(container); 

方法2:

或者,將CallAllMethodsInContainer方法將MyContainer<T>類這樣的:

public void CallAllMyMethodsInContents() 
    { 
     foreach (T myClass in Contents) 
     { 
      myClass.MyMethod(); 
     } 
    } 

並更改代碼段:

MyContainer<MyClass> container = new MyContainer<MyClass>(); 
container.Contents.Add(new MyClass()); 
container.CallAllMyMethodsInContents(); 

方法3:

編輯:另一種替代方案是從MyContainer類中刪除通用約束這樣的:

public class MyContainer 
{ 
    public IList<MyInterface> Contents; 
} 

並將方法簽名更改爲

public void CallAllMethodsInContainer(MyContainer container) 

然後片斷應該工作:

MyContainer container = new MyContainer(); 
container.Contents.Add(new MyClass()); 
this.CallAllMethodsInContainer(container); 

請注意,這個替代方案,容器的Contents名單將接受實施MyInterface對象的任意組合。

3

哇,這個問題一直在最近來了很多。

簡答:不,這是不可能的。下面是可能:

public void CallAllMethodsInContainer<T>(MyContainer<T> container) where T : IMyInterface 
{ 
    foreach (IMyInterface myClass in container.Contents) 
    { 
     myClass.MyMethod(); 
    } 
} 

這裏的原因是什麼,你試過可能性(this recent answer of mine拍攝):

考慮List<T>類型。假設你有一個List<string>和一個List<object>。字符串來自對象,但並不遵循List<string>派生自List<object>;如果沒有,那麼你可以有這樣的代碼:

var strings = new List<string>(); 

// If this cast were possible... 
var objects = (List<object>)strings; 

// ...crap! then you could add a DateTime to a List<string>! 
objects.Add(new DateTime(2010, 8, 23));23)); 

上面的代碼說明了什麼它的意思是(而不是定)一covariant type。需要注意的是鑄造型T<D>到另一種類型,其中T<B>D派生B是可能的(在.NET 4.0)如果T協變;如果泛型類型參數只以輸出的形式出現 - 即只讀屬性和函數返回值,泛型類型是協變的。

認爲它是這樣的:如果某種類型T<B>總是供應B,然後一個總供應DT<D>)就能爲T<B>因爲所有D s爲B s到操作。

順便說一句,如果一個類型的泛型類型參數只以輸入的形式出現 - 即方法參數,則其類型爲逆變型。如果一個T<B>類型是逆變的,那麼它可以投射到T<D>,看起來很奇怪。

認爲它是這樣的:如果某種類型T<B>總是需要B,那麼就可以介入了一個總是需要D以來,再次,所有D s爲B秒。

MyContainer類既不是協變也不逆變因爲它的類型參數出現在兩種情況下 - 輸入(經由Contents.Add)和作爲輸出(通過Contents屬性本身)。