开发者

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

I have an interface defined as below:

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

And 开发者_开发知识库two Linq-to-SQL classes implementing that interface:

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

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

I have IEnumerable lists a and b populated by the database records from tblTestA and tblTestB

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

However, the following is not permitted:

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

I have to do as follows:

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

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

Is there something I am doing wrong? Thanks for any help


This works in C# 4, due to generic covariance. Unlike previous versions of C#, there is a conversion from IEnumerable<tblTestA> to IEnumerable<TestInterface>.

The functionality has been in the CLR from v2, but it's only been exposed in C# 4 (and the framework types didn't take advantage of it before .NET 4 either). It only applies to generic interfaces and delegates (not classes) and only for reference types (so there's no conversion from IEnumerable<int> to IEnumerable<object> for example.) It also only works where it makes sense - IEnumerable<T> is covariant as objects only come "out" of the API, whereas IList<T> is invariant because you can add values with that API too.

Generic contravariance is also supported, working in the other direction - so for example you can convert from IComparer<object> to IComparer<string>.

If you're not using C# 4, then Tim's suggestion of using Enumerable.Cast<T> is a good one - you lose a little efficiency, but it will work.

If you want to learn more about generic variance, Eric Lippert has a long series of blog posts about it, and I gave a talk about it at NDC 2010 which you can watch on the NDC video page.


You're not doing anything wrong: List<TestInterface>.AddRange expects an IEnumerable<TestInterface>. It won't accept an IEnumerable<tblTestA> or IEnumerable<tblTestB>.

Your foreach loops work. Alternatively, you could use Cast to change the types:

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


The AddRange is expecting a list of interface objects, and your "a" and "b" varaibles are defined to be a list of derived class objects. Obviously, it seems reasonable that .NET could logically make that jump and treat them as lists of the interface objects because they do indeed implement the interface, that logic was just not build into .NET up through 3.5.

However, this ability (called "covariance") has been added to .NET 4.0, but until you upgrade to that, you'll be stuck with looping, or maybe try calling ToArray() and then casting the result to a TaskInterface[], or maybe a LINQ query to case each item and create a new list, etc.


a and b are of type IEnumerable<tblTestA> and IEnumerable<tblTestB>
While list.AddRange require the parameter to be of type IEnumerable<TestInterface>

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜