2010-06-23 77 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#语言经过精心设计,以便在运行时可以违反类型系统的情况是最少的。

相关问题