【问题标题】:Why is implicit child to parent conversion in generic type parameters not possible?为什么泛型类型参数中的隐式子到父转换是不可能的?
【发布时间】:2018-03-25 13:09:40
【问题描述】:

我一直在学习 C# 中的协变和逆变,但有些东西我看不懂:

class Program
{
    static void Main(string[] args)
    {
        PersonCollection<Person> personCollection = new PersonCollection<Person>();
        IMyCollection<Teacher> myCollection = personCollection; // Here's the error:
        // Cannot implicitly convert type 'PersonCollection<Teacher>' to 'IMyCollection<Person>'
    }
}
class Person { }
class Teacher : Person { }
interface IMyCollection<T> { }
class PersonCollection<T> : IMyCollection<T> { }

众所周知,我们可以将派生类的实例隐式转换为基类。所以,在上面的代码中,虽然 'Teacher' 类是从 'Person' 类派生的,但 IMyCollection&lt;Teacher&gt; 不能转换为 IMyCollection&lt;Person&gt;,为什么?

注意:我想知道原因,而不是解决方案。

【问题讨论】:

  • 继续阅读协变和逆变。这个问题就是这样
  • 这就是泛型中的协变和逆变的意思。如果您在 IMyColletion 中将 T 声明为 Covariance,则可以将具有类型参数的类分配给具有基类类型参数声明的类
  • 请参阅docs.microsoft.com/en-us/dotnet/standard/generics/… 中的列表。它的定义很好。
  • @JPVenson 我知道。我只是想知道我们不能正常做的原因
  • 因为如果你会得到myCollection 中的第一个项目,你希望它是Teacher,但它也可能是一个人。

标签: c# generics inheritance


【解决方案1】:

将您的源代码作为事实的来源,您实际上误读了错误。

因此,在上面的代码中,“教师”类派生自 'Person' 类,IMyCollection&lt;Teacher&gt; 无法转换为 IMyCollection&lt;Person&gt;,为什么?

实际错误是

无法将类型 PersonCollection&lt;Person&gt; 隐式转换为 IMyCollection&lt;Teacher&gt;.

至少从 OO 的角度来看,这是预期的行为。请务必了解默认 T 是不变。这就是你一开始就遇到这个问题的原因。这意味着如果 T 是 Teacher,那么 T 只能是 Teacher 而不是 Person。同样,如果 T 是 Person,那么 T 只能是 Person 而不能是 Teacher

这是因为协方差和逆变是相互排斥的。有两种方法可以同时支持这两种方法,但您必须将它们拆分为分别支持不变性、协变和逆变的接口。在您的情况下,您需要添加对逆变的支持。例如

interface IMyCollection&lt;in T&gt; { }

换句话说,进入(见ingeneric modifier keyword)接口可以是T类型。不是“出去”(见outgeneric modifier keyword)或从你的接口返回(即将是协方差)。

【讨论】:

  • 请注意,OP的错误信息也与代码不匹配,因此实际上有三个不同的示例情况。
  • 修改了问题以指出我认为的事实来源。
【解决方案2】:

注意:我想知道原因,而不是解决方案

虽然这正是为什么存在逆方差和协方差的原因,但让我快速向您展示 您的示例的解释,突出说明为什么这不起作用:

让我们假设以下设置代码:

PersonCollection<Person> personCollection = new PersonCollection<Person>();

personCollection.Add(new Teacher("Teacher A"));
personCollection.Add(new Teacher("Teacher B"));
personCollection.Add(new Student("Student A"));
personCollection.Add(new Student("Student B"));
personCollection.Add(new Student("Student C"));
personCollection.Add(new Student("Student D"));

所以现在,我有一个 PersonCollection&lt;Person&gt; 和两个 Teacher 和四个 Student 对象(这里,Student 也继承自 Person)。这是完全有效的,因为任何TeacherStudent 也是Person。所以我可以将元素添加到集合中。

现在,假设以下是允许的:

IMyCollection<Teacher> myCollection = personCollection;

现在,我有一个 myCollection,它显然包含 Teacher 对象。但由于这只是一个引用分配,myCollection 仍然是与personCollection 完全相同的集合。

所以myCollection 将包含四个Student 对象,尽管它的合约定义它只包含Teacher 元素。接口的约定应该完全允许执行以下操作:

Teacher teacher = personCollection[4];

但是personCollection[4] 是学生 C,所以显然这行不通。

由于编译器无法在此项目分配期间进行此验证,并且由于我们想要类型安全而不是运行时验证,因此编译器可以防止这种情况的唯一合理方法是不允许您将集合转换为 IMyCollection&lt;Teacher&gt;

您可以通过将 IMyCollection&lt;T&gt; 声明为 IMyCollection&lt;in T&gt; 来使您的 IMyCollection&lt;T&gt; 逆变,这将解决您的情况并允许您进行该分配,但同时它会阻止您从中检索 Teacher 对象,因为它不是协变的。

通常,从集合中设置和检索泛型值的唯一方法是使其保持不变(这是默认设置),这也是为什么 BCL 中的所有泛型集合都是不变的并且只有一些接口是相反的原因协变(例如 IEnumerable&lt;T&gt; 是协变的,因为它只是关于检索值)。


由于您将问题中的错误更改为 “无法将类型 'PersonCollection' 隐式转换为 'IMyCollection'”,让我也解释一下这种情况(将这个答案变成完全相反的 - &协方差答案*sigh*...)。

所以代码如下:

PersonCollection<Teacher> personCollection = new PersonCollection<Teacher>();
IMyCollection<Person> myCollection = personCollection;

再一次,让我们假设这是有效且有效的。所以现在,我们有一个可以使用的IMyCollection&lt;Person&gt;!所以让我们在这里添加一些人:

myCollection.Add(new Teacher("Teacher A"));
myCollection.Add(new Teacher("Teacher B"));
myCollection.Add(new Student("Student A"));

哎呀!实际的集合仍然是一个PersonCollection&lt;Teacher&gt;,它只能取Teacher 对象。但是IMyCollection&lt;Person&gt; 类型允许我们添加同样是人的Student 对象!所以这会在运行时失败,而且由于我们希望在编译时类型安全,编译器必须在此处禁止赋值。

这种赋值只对协变 IMyCollection&lt;out T&gt; 有效,但也不允许我们向其中添加 T 类型的元素(原因同上)。

现在, 而不是在这里添加PersonCollection&lt;Teacher&gt;,让我们使用

【讨论】:

  • 我没看懂,这个例子中包含了一个收集机制,如果没有'Add(new Object())'方法之类的怎么办?或者(作为我的示例)如果我想将 'IMyCollection' 转换为 'IMyCollection' 怎么办?
  • @AmirHosseinAhmadi 对不起,当您的类型被称为 IMyCollection 时,我只是假设了集合语义,而您没有提供更多信息。无论如何,正如我在回答中解释的那样,您必须决定您的类型是相反的、共同的还是不变的。并且取决于您所做的事情,这对您可以包含哪些操作具有上述含义。将IMyCollection&lt;Teacher&gt; 转换为IMyCollection&lt;Person&gt; 与从PersonCollection&lt;Teacher&gt; 转换相同,因此同样适用。
猜你喜欢
  • 1970-01-01
  • 2016-01-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多