我发现无法将具体对象列表添加到接口对象列表中。
public static void AddJob(List<IJob> masterJobs, List<Job> jobs)
{
masterJobs.AddRange(jobs); //fail to compile
}
相反,需要使用以下代码:
public static void AddJob(List<IJob> masterJobs, List<Job> jobs)
{
masterJobs.AddRange(jobs.Cast<IJob>());
}
这背后的理性是什么?
答案 0 :(得分:10)
Lasse对于为什么这在C#3中不起作用是正确的 - 没有从List<IJob>
转换为List<Job>
。
在C#4中它可以工作,不是因为 list 是协变的,而是因为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
}
jobs
实现IEnumerable<Job>
,因此通过协方差将引用转换为IEnumerable<IJob>
,因此一切正常。对Cast<T>
的调用实际上是在您的C#3解决方法中执行类似的工作 - 您正在使用它来转换为IEnumerable<IJob>
。
如果您想了解更多关于通用方差的信息,可以使用video of my NDC 2010 talk,或者阅读Eric Lippert的series of blog posts。
答案 1 :(得分:8)
原因是List<IJob>
不是List<Job>
,尽管Job
实现了IJob
。
这是共同或反对的变化(我永远不会记得哪个是哪个。)
思维是这样的:
编译器不能保证AddRange
只能从给定的参数中读取内容,因此无法保证这是安全的,因此无法编译。
例如,对于所有编译器都知道,AddRange可以将另一个对象添加到jobs
参数中,该参数实现IJob
(因为AddRange需要IJob
个集合),但不是{ {1}},这是Job
所期望的,因此不安全。
在C#4.0中,有一些支持处理这个,但我不确定它是否会处理你的特定情况,因为必须在接口级指定支持,而不是在方法级指定。
换句话说,您必须在接口类型上指定与T相关的所有内容仅进入集合,永远不会出现,然后编译器将允许您执行此操作。但是,你无法读取的集合将毫无意义。