tags:

views:

65

answers:

4

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

+6  A: 

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>());
Tim Robinson
+1 Thanks for the fix, much appreciated
Jimbo
A: 

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

Itay
Well, they require the argument to be *convertible* to `IEnumerable<TestInterface>`. Whether that's the case or not depends on the version of C# you're using...
Jon Skeet
I Guess you are right - I am not familiar with C#4, Thanks :)
Itay
+6  A: 

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.

Jon Skeet
+1  A: 

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.

Mike Mooney