列表<ijob> .AddRange(列表<作业>)不起作用</job> </ijob>

时间:2010-07-27 11:45:40

标签: c# generics

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

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>());  
}

这背后的理性是什么?

2 个答案:

答案 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相关的所有内容仅进入集合,永远不会出现,然后编译器将允许您执行此操作。但是,你无法读取的集合将毫无意义。