List <IJob> .AddRange(List <Job>)不起作用

Gra*_*ton 5 c# generics

我发现无法将具体对象列表添加到接口对象列表中.

public static void AddJob(List<IJob> masterJobs, List<Job> jobs)
{
    masterJobs.AddRange(jobs);  //fail to compile
}
Run Code Online (Sandbox Code Playgroud)

相反,需要使用以下代码:

public static void AddJob(List<IJob> masterJobs, List<Job> jobs)
{
    masterJobs.AddRange(jobs.Cast<IJob>());  
}
Run Code Online (Sandbox Code Playgroud)

这背后的理性是什么?

Jon*_*eet 10

拉斯是正确的,为什么这不会在C#3中起作用 - 没有转换List<IJob>List<Job>.

在C#4中,它可以工作,不是因为列表是协变的,而是因为它IEnumerable<T>是协变的.换句话说,代码实际上是:

public static void AddJob(List<IJob> masterJobs, List<Job> jobs)
{
    IEnumerable<IJob> jobsTmp = jobs; // This is the covariance working
    masterJobs.AddRange(jobs); // This is now fine
}
Run Code Online (Sandbox Code Playgroud)

jobs实现IEnumerable<Job>,所以有一个IEnumerable<IJob>通过协方差的引用转换,所以一切正常.呼叫Cast<T>在你的C#3解决方法中有效地做了类似的工作 - 你用它来转换为IEnumerable<IJob>.

如果你想了解更多关于泛型差异的信息,可以看一下我的NDC 2010讲座视频,或者阅读Eric Lippert的一系列博客文章.


ang*_*son 8

原因是a List<IJob>不是a List<Job>,虽然是Job工具IJob.

这是共同或反对的变化(我永远不会记得哪个是哪个.)

思路是这样的:

编译器不能保证AddRange只从给定的参数中读取内容,因此无法保证这是安全的,因此无法编译.

例如,对于所有编译器都知道,AddRange可以将另一个对象添加到jobs参数中,该参数实现IJob(因为AddRange期望IJob集合),但不是Job,这是jobs期望的,因此这是不安全的.

在C#4.0中,有一些支持处理这个,但我不确定它会处理你的特定情况,因为必须在接口级指定支持,而不是在方法级指定.

换句话说,您必须在接口类型上指定与T相关的所有内容仅进入集合,永远不会进入集合,然后编译器将允许您执行此操作.但是,您无法阅读的集合将毫无意义.