[英]Why won't this cast work?
我有以下代码:
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.
因为这需要CommitmentItem<CapitalCall>
是协变的,因此它可以分配给它目前不支持的CommitmentItem<ITransaction>
。
C#4增加了对接口中的共同和逆变的支持,但不支持类。
因此,如果您正在使用C#4并且可以使用诸如ICommitmentItem<>
类的接口而不是CommitmentItem<>
,那么您可以通过使用C#4的新功能获得所需的内容。
让我们缩短这些名字。
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#语言经过精心设计,因此在运行时可以违反类型系统的情况数量最少。
编辑 - Lucero的链接更好,因为它描述了C#4.0中接口的协同和反演机制。 这些链接来自2007年,但我觉得它们仍然非常有启发性。
因为C#3.0不支持泛型参数的协方差或逆变。 (而且C#4.0仅对接口提供有限的支持。)请参阅此处了解协方差和逆变的解释,以及对C#团队将此功能纳入C#4.0时所采取的思路的一些见解:
实际上,他只是在写作和写作! 这是他用“协方差和逆变”标记的所有内容:
http://blogs.msdn.com/b/ericlippert/archive/tags/covariance+and+contravariance/
因为“ A
是B
子类型” 并不意味着“ X<A>
是X<B>
的子类型”。
让我给你举个例子。 假设CommitmentItem<T>
有一个方法Commit(T t)
,并考虑以下函数:
void DoSomething(CommitmentItem<ITransaction> item) {
item.Commit(new SomethingElseCall());
}
这应该有效,因为SomethingElseCall
是ITransaction
的子类型,就像CapitalCall
一样。
现在假设CommitmentItem<CapitalCall>
是CommitmentItem<ITransaction>
的子类型。 然后你可以做以下事情:
DoSomething(new CommitmentItem<CapitalCall>());
会发生什么? 你会在DoSomething
遇到类型错误,因为SomethingElseCall
会在需要CapitalCall
地方传递。 因此, CommitmentItem<CapitalCall>
不是 CommitmentItem<ITransaction>
的子类型。
在Java中,这个问题可以通过使用extends
和super
关键字来解决,参见 问题2575363 。 不幸的是,C#缺少这样的关键字。
理解为什么这不起作用可能有点棘手,所以这是一个类似的例子,用框架中的一些着名类替换代码中的类来充当占位符,并(希望)说明这种所需功能的潜在缺陷:
// 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());
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.