为什么无法在通用类型参数中进行隐式的子级到父级转换?

Ami*_*adi 4 c# generics inheritance

我一直在学习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> { }
Run Code Online (Sandbox Code Playgroud)

众所周知,我们可以将派生类的实例隐式转换为基类。因此,在上面的代码中,虽然“老师”类是从“人”类派生的,但IMyCollection<Teacher>不能转换为IMyCollection<Person>,为什么?(注意:我想知道原因,而不是解决方法)

pok*_*oke 5

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

而这样做的原因正是为什么禁忌和协方差存在,让我迅速告诉你与解释你的例子亮点为什么这不起作用:

因此,我们假设以下安装代码:

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"));
Run Code Online (Sandbox Code Playgroud)

因此,现在,我有了一个PersonCollection<Person>包含两个Teacher和四个Student对象的对象(在这里,Student也继承自Person)。这是因为任何完全有效的Teacher并且Student也是Person。因此,我可以将元素添加到集合中。

现在,假设允许以下情况:

IMyCollection<Teacher> myCollection = personCollection;
Run Code Online (Sandbox Code Playgroud)

现在,我有一个myCollection显然包含Teacher对象的对象。但是,由于这仅仅是一个参考的分配,myCollection仍然是完全相同的集合作为personCollection

因此myCollection将包含四个Student对象,尽管其协定定义它仅包含Teacher元素。接口的合同应完全允许执行以下操作:

Teacher teacher = personCollection[4];
Run Code Online (Sandbox Code Playgroud)

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

由于编译器无法在此项目分配期间进行此验证,并且由于我们希望使用类型安全性而不是运行时验证,因此,编译器可以防止这种情况的唯一明智的方法是不允许您将集合转换为IMyCollection<Teacher>

您可以IMyCollection<T>通过声明它来改变对数,IMyCollection<in T>这样可以解决您的情况并允许您进行分配,但同时Teacher由于它不是协变的,因此它会阻止您从中检索出对象。

通常,从集合中设置和检索通用值的唯一方法是使其不变(这是默认设置),这也是为什么BCL中所有通用集合都是不变的,并且只有某些接口是对变或协变的原因(例如IEnumerable<T>是协变的,因为它仅与获取值有关)。


由于您将问题内的错误更改为“无法将类型'PersonCollection'隐式转换为'IMyCollection'”,因此让我也解释一下这种情况(将此答案转换为完整的对数和协方差答案* sigh * …)。

因此,代码如下所示:

PersonCollection<Teacher> personCollection = new PersonCollection<Teacher>();
IMyCollection<Person> myCollection = personCollection;
Run Code Online (Sandbox Code Playgroud)

再次,让我们假设这是有效且可行的。所以现在,我们有一个IMyCollection<Person>可以合作的!因此,我们在这里添加一些人:

myCollection.Add(new Teacher("Teacher A"));
myCollection.Add(new Teacher("Teacher B"));
myCollection.Add(new Student("Student A"));
Run Code Online (Sandbox Code Playgroud)

哎呀!实际的集合仍然PersonCollection<Teacher>只能容纳Teacher对象。但是IMyCollection<Person>类型允许我们添加Student也是人的对象!因此,这将在运行时失败,并且再次,由于我们希望在编译时进行类型安全,因此编译器必须在此处禁止分配。

这种分配仅对协变有效,IMyCollection<out T>但也不允许我们T向其添加类型的元素(与上述相同的原因)。

现在,我们不要将其添加到PersonCollection<Teacher>此处,而是使用