为什么泛型类型参数中的隐式子转换不可能?

时间:2018-03-25 13:09:40

标签: 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> { }

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

2 个答案:

答案 0 :(得分: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"));

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

现在,假设允许以下内容:

IMyCollection<Teacher> myCollection = personCollection;

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

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

Teacher teacher = personCollection[4];

但是personCollection[4]是学生C,显然这不起作用。

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

您可以将IMyCollection<Teacher>逆变声明为IMyCollection<T>,以确定您的情况,并允许您进行分配,但同时又会阻止您检索IMyCollection<in T>因为它不是协变的,所以反对它。

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

由于您将问题内部的错误更改为“无法将类型'PersonCollection'隐式转换为'IMyCollection'”,我还要解释一下这个案例(将此答案转换为完整的对话框) ;协方差回答 *叹气* ......)。

因此代码如下:

IEnumerable<T>

再次,让我们假设这是有效的并且有效。现在,我们可以使用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,让我们只使用

答案 1 :(得分:0)

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

  

因此,在上面的代码中,虽然类&#39;老师&#39;来自   等级&#39;人员&#39;,IMyCollection<Teacher>无法转换为   IMyCollection<Person>,为什么?

实际错误是

  

无法将PersonCollection<Person>类型隐式转换为   IMyCollection<Teacher>

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

这是因为协方差和反演是相互排斥的。有两种方法可以支持这两种方法,但是你必须将这些方法分成支持Invariance,Covariance和Contravariance的接口。在您的情况下,您需要添加对Contravariance的支持。 E.G。

interface IMyCollection<in T> { }

换句话说,接口(参见in generic modifier keyword)接口可以是T类型。不是什么&#34;出去&#34; (请参阅out generic modifier keyword)或从您的界面返回(这将是协方差)。