c#Linq`List <interface> .AddRange` Method Not Working </interface>

时间:2010-07-14 09:42:45

标签: c# linq addrange

我的界面定义如下:

public interface TestInterface{
    int id { get; set; }
}

实现该接口的两个Linq-to-SQL类:

public class tblTestA : TestInterface{
    public int id { get; set; }
}

public class tblTestB : TestInterface{
    public int id { get; set; }
}

我有来自tblTestA和tblTestB的数据库记录填充的IEnumerable列表a和b

IEnumerable<tblTestA> a = db.tblTestAs.AsEnumerable();
IEnumerable<tblTestB> b = db.tblTestBs.AsEnumerable();

但是,不允许以下内容:

List<TestInterface> list = new List<TestInterface>();
list.AddRange(a);
list.AddRange(b);

我必须做如下:

foreach(tblTestA item in a)
    list.Add(item)

foreach(tblTestB item in b)
    list.Add(item)

我做错了吗?谢谢你的帮助

4 个答案:

答案 0 :(得分:8)

由于通用协方差,这适用于C#4。与以前版本的C#不同,有从IEnumerable<tblTestA>IEnumerable<TestInterface>的转换。

该功能已经在v2中的CLR中,但它只在C#4中公开(并且框架类型在.NET 4之前也没有利用它)。 适用于通用接口和委托(不是类),仅适用于引用类型(例如,没有从IEnumerable<int>IEnumerable<object>的转换。)它也只适用于这是有道理的 - IEnumerable<T>是协变的,因为对象只是“超出”API,而IList<T>不变,因为你也可以使用该API添加值。

也支持通用逆转,在另一个方向工作 - 例如,您可以从IComparer<object>转换为IComparer<string>

如果您没有使用C#4,那么Tim建议使用Enumerable.Cast<T>是一个很好的建议 - 您会失去一点效率,但它会起作用。

如果您想了解有关泛型差异的更多信息,Eric Lippert有一个long series of blog posts about it,我在NDC 2010上发表了一篇关于它的讨论,您可以在NDC video page上观看。

答案 1 :(得分:6)

你没有做错任何事:List<TestInterface>.AddRange期待IEnumerable<TestInterface>。它不接受IEnumerable<tblTestA>IEnumerable<tblTestB>

您的foreach循环有效。或者,您可以使用Cast更改类型:

List<TestInterface> list = new List<TestInterface>();
list.AddRange(a.Cast<TestInterface>());
list.AddRange(b.Cast<TestInterface>());

答案 2 :(得分:1)

AddRange需要一个接口对象列表,而你的“a”和“b”变量被定义为派生类对象的列表。显然,.NET可以在逻辑上进行跳转并将它们视为接口对象的列表似乎是合理的,因为它们确实实现了接口,而逻辑只是通过3.5构建到.NET中。

但是,这种能力(称为“协方差”)已被添加到.NET 4.0中,但在升级到此之前,您将陷入循环,或者尝试调用ToArray()然后将结果转换为TaskInterface [],或者可能是一个LINQ查询来判断每个项目并创建一个新列表等等。

答案 3 :(得分:0)

ab的类型为IEnumerable<tblTestA>IEnumerable<tblTestB> 虽然list.AddRange要求参数的类型为IEnumerable<TestInterface>