注意:我想知道原因,而不是解决方案。
尽管这就是逆变和协变存在的原因,但让我快速给你展示一个以你的例子为例的解释,突显为什么这行不通:
所以让我们假设以下设置代码:
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"));
现在,我有一个包含两个
Teacher
和四个
Student
对象的
PersonCollection<Person>
(这里,
Student
也继承自
Person
)。这是完全有效的,因为任何
Teacher
和
Student
也都是
Person
。所以我可以将元素添加到集合中。
现在,想象一下如果允许以下情况:
IMyCollection<Teacher> myCollection = personCollection
现在,我有一个名为
myCollection
的集合,显然包含了
Teacher
对象。但是由于这只是一个引用赋值,
myCollection
仍然是与
personCollection
完全相同的集合。
所以,
myCollection
将包含四个
Student
对象,尽管它的约定规定它只包含
Teacher
元素。根据接口的约定,以下操作应该是完全允许的:
Teacher teacher = personCollection[4]
但是
personCollection[4]
是学生C,所以显然这是行不通的。
由于编译器无法在此项赋值期间进行验证,并且我们希望类型安全而不是运行时验证,编译器唯一合理的方式是不允许将集合转换为
IMyCollection<Teacher>
。
您可以通过将其声明为
IMyCollection<in T>
来使您的
IMyCollection<T>
逆变,这将修复您的情况并允许您进行该赋值,但同时它将阻止您从中检索出一个
Teacher
对象,因为它不是协变的。
通常,从集合中设置和检索泛型值的唯一方法是使其不变(这是默认值),这也是为什么BCL中的所有泛型集合都是不变的,只有一些接口是逆变或协变的(例如
IEnumerable<T>
是协变的,因为它只涉及
检索值)。
由于您将问题中的错误更改为
“无法隐式转换类型 'PersonCollection' 为 'IMyCollection'”,让我也解释一下这个情况(将这个答案变成一个完整的逆变与协变的答案
*叹气*…)。
所以对于这个问题,代码应该如下所示:
PersonCollection<Teacher> personCollection = new PersonCollection<Teacher>()
IMyCollection<Person> myCollection = personCollection
再次假设这是有效的并且能工作。现在,我们有一个可以使用的 IMyCollection<Person>
集合!那么让我们在这里添加一些人:
myCollection.Add(new Teacher("Teacher A"));
myCollection.Add(new Teacher("Teacher B"));
myCollection.Add(new Student("Student A"));
哎呀!实际的集合仍然是一个 PersonCollection<Teacher>
,它只能接受 Teacher
对象。但是 IMyCollection<Person>
类型允许我们添加也是人的 Student
对象!因此,这将在运行时失败,并且考虑到我们想要在编译时进行类型安全检查,编译器必须禁止在此处进行赋值。
这种赋值只对协变的 IMyCollection<out T>
有效,但这也会禁止我们向其中添加类型为 T
的元素(出于与上述相同的原因)。
myCollection
中的第一个元素,您期望它是一个Teacher
,但实际上它也可能只是一个普通人。 - Caramiriel