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>
精彩评论