为什么这个演员不会工作?

时间:2010-06-23 21:10:50

标签: c# generics covariance

我有以下代码:

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

我收到以下错误:

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

但是,CapitalCallCommitmentItem继承自CommitmentItem<CapitalCall>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.

5 个答案:

答案 0 :(得分:6)

因为这需要CommitmentItem<CapitalCall>协变,以便可以分配给CommitmentItem<ITransaction>,目前不支持。{/ p>

C#4增加了对接口中的共同和逆变的支持,但不支持类。

因此,如果您使用的是C#4并且可以使用ICommitmentItem<>而不是CommitmentItem<>这样的界面,那么您可以通过使用C#4的新功能获得所需的内容

答案 1 :(得分: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是合法的。那么调用d.M(new F())也是合法的,因为F实现了I.但是d.M是一个采用E而不是F的方法。

允许此功能使您可以编写干净编译的程序,然后在运行时违反类型安全性。 C#语言经过精心设计,因此在运行时可以违反类型系统的情况数量最少。

答案 2 :(得分: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/

答案 3 :(得分: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#缺少这样的关键字。

答案 4 :(得分: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());