2010-06-23 76 views
2

我有以下代碼:爲什麼不能投射?

var commitmentItems = new List<CommitmentItem<ITransaction>>(); 
    commitmentItems.Add(new CapitalCallCommitmentItem()); 

而且我得到以下錯誤:

Argument '1': cannot convert from 'Models.CapitalCallCommitmentItem' to 
'Models.CommitmentItem<Models.ITransaction>' 

然而,從CommitmentItem<CapitalCall>CapitalCallCommitmentItem繼承和CapitalCall工具ITransaction。那麼爲什麼錯誤?

這裏是一個更好的例子:

CapitalCall實現ITransaction

  var test = new List<ITransaction>(); 
      test.Add(new CapitalCall()); 
      var test2 = new List<List<ITransaction>>(); 
      test.Add(new List<CapitalCall>()); // error. 
+0

通讀共同/反對常見問題。對於不熟悉這個主題的人來說,這是一個非常好的起點,因爲在你習慣之前,它有點像一個腦筋急轉彎。 http://blogs.msdn.com/b/csharpfaq/archive/2010/02/16/covariance-and-contravariance-faq.aspx – Lucero 2010-06-23 21:28:23

回答

1

瞭解爲什麼這不起作用可能有些棘手,所以這裏有一個類似的例子,用代碼中的一些知名類代替代碼中的類,充當佔位符並(希望)說明潛在的陷阱的這種期望的功能:

// Note: replacing CommitmentItem<T> in your example with ICollection<T> 
// and ITransaction with object. 
var list = new List<ICollection<object>>(); 

// If the behavior you wanted were possible, then this should be possible, since: 
// 1. List<string> implements ICollection<string>; and 
// 2. string inherits from object. 
list.Add(new List<string>()); 

// Now, since list is typed as List<ICollection<object>>, our innerList variable 
// should be accessible as an ICollection<object>. 
ICollection<object> innerList = list[0]; 

// But innerList is REALLY a List<string>, so although this SHOULD be 
// possible based on innerList's supposed type (ICollection<object>), 
// it is NOT legal due to innerList's actual type (List<string>). 
// This would constitute undefined behavior. 
innerList.Add(new object()); 
6

,因爲這將需要CommitmentItem<CapitalCall>被協變,以便它可以分配給CommitmentItem<ITransaction>,它目前不支持。

C#4增加了對接口中的協變和逆變的支持,但不支持類。

因此,如果您正在使用C#4,你可以使用一個接口,如ICommitmentItem<>代替CommitmentItem<>,你也許可以得到你想要的東西通過使用C#4

+1

有關其他參考,請參閱此MSDN文章,它幾乎正確地描述了幾種工作方式圍繞此:http://msdn.microsoft.com/en-us/library/ms228359%28VS.80%29.aspx – jdmichal 2010-06-23 21:19:41

+0

@jdmichal,感謝張貼鏈接!然而,它在某些部分已經過時了。對於目前的協同和反對狀態,請查看常見問題解答:http://blogs.msdn.com/b/csharpfaq/archive/2010/02/16/covariance-and-contravariance-faq.aspx – Lucero 2010-06-23 21:21:38

2

編輯的新功能 - Lucero的鏈接更好,因爲它描述了C#4.0中接口的協變和反轉機制。這些鏈接來自2007年,但我覺得它們仍然非常有啓發性。

因爲C#3.0不支持泛型參數的協變或逆變。 (而C#4.0對接口的支持有限。)請參閱此處以瞭解協方差和逆變的解釋,以及C#團隊將此功能放入C#4.0時的思路:

http://blogs.msdn.com/b/ericlippert/archive/2007/10/16/covariance-and-contravariance-in-c-part-one.aspx

http://blogs.msdn.com/b/ericlippert/archive/2007/10/17/covariance-and-contravariance-in-c-part-two-array-covariance.aspx

http://blogs.msdn.com/b/ericlippert/archive/2007/10/19/covariance-and-contravariance-in-c-part-three-member-group-conversion-variance.aspx

http://blogs.msdn.com/b/ericlippert/archive/2007/10/22/covariance-and-contravariance-in-c-part-four-real-delegate-variance.aspx

http://blogs.msdn.com/b/ericlippert/archive/2007/10/24/covariance-and-contravariance-in-c-part-five-higher-order-functions-hurt-my-brain.aspx

其實,他只是不斷寫作和寫作!這裏的一切他用「協變和逆變」:

http://blogs.msdn.com/b/ericlippert/archive/tags/covariance+and+contravariance/

+0

有趣的,但是這篇文章並沒有提到泛型參數中的協變性? – Shawn 2010-06-23 21:21:45

+0

啊,這是系列中的第一個。我沒有意識到他們之間沒有聯繫。我會添加其他... – Weeble 2010-06-23 21:27:03

+0

小心不要發佈過時的鏈接。 .NET 4/VS2010引入了通用接口(和委託)的共同點和反例。 – Lucero 2010-06-23 21:30:29

1

因爲「AB亞型」 暗示「X<A>X<B>子類型」。

讓我給你舉個例子。假設CommitmentItem<T>有一個方法Commit(T t),並考慮以下功能:

void DoSomething(CommitmentItem<ITransaction> item) { 
    item.Commit(new SomethingElseCall()); 
} 

這應該工作,因爲SomethingElseCallITransaction一個亞型,就像CapitalCall

現在假設CommitmentItem<CapitalCall>CommitmentItem<ITransaction>的子類型。然後,您可以執行以下操作:

DoSomething(new CommitmentItem<CapitalCall>()); 

會發生什麼情況?您會在DoSomething中獲得一個類型錯誤,因爲SomethingElseCall傳遞給CapitalCall。因此,CommitmentItem<CapitalCall>而不是CommitmentItem<ITransaction>的子類型。

在Java中,可以通過使用extendssuper關鍵字來解決此問題,請參閱參考資料。 question 2575363。不幸的是,C#缺少這樣一個關鍵字。

6

讓我們縮短這些名字。

C = CapitalCallCommentItem 
D = CommitmentItem 
E = CapitalCall 
I = ITransaction 

所以你的問題是,你必須:「這是爲什麼非法」

interface I { } 
class D<T> 
{ 
    public M(T t) { } 
} 
class C : D<E> { } 
class E : I { } 

而且你的問題是

D<E> c = new C(); // legal 
D<I> d = c; // illegal 

假設這是合法的並推斷出錯誤。 c有一個方法M,它採用E.現在你說

class F : I { } 

假設將c分配給d是合法的。那麼調用dM(new F())也是合法的,因爲F實現了I.但是dM是一個採用E的方法,而不是F.

允許使用此功能可以編寫乾淨地編譯然後在運行時違反類型安全性。 C#語言經過精心設計,以便在運行時可以違反類型系統的情況是最少的。

相關問題