简体   繁体   English

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

[英]Why won't this cast work?

I have the following code: 我有以下代码:

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

And I get the following error: 我收到以下错误:

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

However, CapitalCallCommitmentItem inherits from CommitmentItem<CapitalCall> , and CapitalCall implements ITransaction . 但是, CapitalCallCommitmentItem继承自CommitmentItem<CapitalCall> ,而CapitalCall实现ITransaction So why the error? 那么为什么会出错呢?

Here is a better example: 这是一个更好的例子:

CapitalCall implements ITransaction CapitalCall实现ITransaction

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

Because that would need CommitmentItem<CapitalCall> to be covariant so that it is assignable to CommitmentItem<ITransaction> , which it currently not supported. 因为这需要CommitmentItem<CapitalCall>是协变的,因此它可以分配给它目前不支持的CommitmentItem<ITransaction>

C# 4 added support for co- and contravariance in interfaces, but not for classes. C#4增加了对接口中的共同和逆变的支持,但不支持类。

Therefore, if you're using C# 4 and you can use an interface such as ICommitmentItem<> instead of CommitmentItem<> , you might be able to get what you want by using the new features of C# 4. 因此,如果您正在使用C#4并且可以使用诸如ICommitmentItem<>类的接口而不是CommitmentItem<> ,那么您可以通过使用C#4的新功能获得所需的内容。

Let's shorten these names. 让我们缩短这些名字。

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

So your question is that you have: 所以你的问题是你有:

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

And your question is "why is this illegal?" 你的问题是“为什么这是非法的?”

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

Suppose that was legal and deduce an error. 假设这是合法的并且推断出错误。 c has a method M which takes an E. Now you say c有一个方法M,它取一个E.现在你说

class F : I { }

Suppose it was legal to assign c to d. 假设将c分配给d是合法的。 Then it would also be legal to call dM(new F()) because F implements I. But dM is a method that takes an E, not an F. 那么调用dM(new F())也是合法的,因为F实现了I.但是dM是一种采用E而不是F的方法。

Allowing this feature enables you to write programs that compile cleanly and then violate type safety at runtime. 允许此功能使您可以编写干净编译的程序,然后在运行时违反类型安全性。 The C# language has been carefully designed so that the number of situations in which the type system can be violated at runtime are at a minimum. C#语言经过精心设计,因此在运行时可以违反类型系统的情况数量最少。

EDIT - Lucero's link is better because it describes the co- and contravariance mechanism for interfaces that is in C# 4.0. 编辑 - Lucero的链接更好,因为它描述了C#4.0中接口的协同和反演机制。 These links are from 2007, but I feel they're still extremely illuminating. 这些链接来自2007年,但我觉得它们仍然非常有启发性。

Because C# 3.0 doesn't support covariance or contravariance of generic arguments. 因为C#3.0不支持泛型参数的协方差或逆变。 (And C# 4.0 has limited support for interfaces only.) See here for an explanation of covariance and contravariance, and some insight into the thinking that went on as the C# team were looking at putting this features into C# 4.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/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/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/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/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/2007/10/24/covariance-and-contravariance-in-c-part-five-higher-order-functions-hurt-my-brain.aspx

Actually, he just keeps writing and writing! 实际上,他只是在写作和写作! Here's everything he's tagged with "covariance and contravariance": 这是他用“协方差和逆变”标记的所有内容:

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

Because " A is subtype of B " does not imply that " X<A> is a subtype of X<B> ". 因为“ AB子类型” 并不意味着“ X<A>X<B>的子类型”。

Let me give you an example. 让我给你举个例子。 Assume that CommitmentItem<T> has a method Commit(T t) , and consider the following function: 假设CommitmentItem<T>有一个方法Commit(T t) ,并考虑以下函数:

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

This should work, since SomethingElseCall is a subtype of ITransaction , just like CapitalCall . 这应该有效,因为SomethingElseCallITransaction的子类型,就像CapitalCall一样。

Now assume that CommitmentItem<CapitalCall> were a subtype of CommitmentItem<ITransaction> . 现在假设CommitmentItem<CapitalCall>CommitmentItem<ITransaction>的子类型。 Then you could do the following: 然后你可以做以下事情:

DoSomething(new CommitmentItem<CapitalCall>());

What would happen? 会发生什么? You'd get a type error in the middle of DoSomething , because a SomethingElseCall is passed where a CapitalCall was expected. 你会在DoSomething遇到类型错误,因为SomethingElseCall会在需要CapitalCall地方传递。 Thus, CommitmentItem<CapitalCall> is not a subtype of CommitmentItem<ITransaction> . 因此, CommitmentItem<CapitalCall> 不是 CommitmentItem<ITransaction>的子类型。

In Java, this problem can be solved by using the extends and super keywords, cf. 在Java中,这个问题可以通过使用extendssuper关键字来解决,参见 question 2575363 . 问题2575363 Unfortunately, C# lacks such a keyword. 不幸的是,C#缺少这样的关键字。

Understanding why this doesn't work can be kind of tricky, so here's an analogous example, replacing the classes in your code with some well-known classes from the framework to act as placeholders and (hopefully) illustrate the potential pitfalls of such desired functionality: 理解为什么这不起作用可能有点棘手,所以这是一个类似的例子,用框架中的一些着名类替换代码中的类来充当占位符,并(希望)说明这种所需功能的潜在缺陷:

// 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM