views:

498

answers:

4

If I have these two classes:

class A {}
class B : A {}

and I make a List<A> but I want to add a List<B> to it by calling List<A>.AddRange(List<B>) but the compiler refuses:

Argument '1': cannot convert from 'System.Collections.Generic.List<A>'
to 'System.Collections.Generic.IEnumerable<B>

which I completely understand because IEnumerable<B> does not inherit from IEnumerable<A>, its generic type has the inheritance.

My solution is to enumerate through List<B> and individually add items because List<A>.Add(A item) will work with B items:

foreach(B item in listOfBItems)
{
    listOfAItems.Add(item);
}

However, that's rather non-expressive because what I want is just AddRange.

I could use

List<B>.ConvertAll<A>(delegate(B item) {return (A)item;});

but that's unnecessarily convoluted and a misnomer because I'm not converting, I'm casting .

Question: If I were to write my own List-like collection what method would I add to it that would allow me to copy a collection of B's into a collection of A's as a one-liner akin to List<A>.AddRange(List<B>) and retain maximum type-safety. (And by maximum I mean that the argument is both a collection and type inhertance checking.)

+1  A: 

This does unfortnuately not work because generics in .net do not (yet) support covariance.

You can make a small helper method or class to overcome this issue however.

If you implement your own list class, you can add covariance using an additional generic parameter:

class MyList<T> {
    void AddRange<U>(IEnumerable<U> items) where U: T {
        foreach (U item in items) {
            Add(item);
        }
    }
}
Lucero
A: 

The only thing I can come up with is this

public class MyList<T> : List<T>
{
    public void AddRange<Tother>(IEnumerable<Tother> col)
        where Tother: T
    {    
        foreach (Tother item in col)
        {
            this.Add(item);
        }
    }
}

Calling it means doing MyList<A>.AddRange<B>(MyList<B>). This fails if the argument is not enumerable or if the type inheritance doesn't work out so it satisfies my question's maximum type safety requirement.

Colin Burnett
"This fails if the argument is not enumerable or if the type inheritance doesn't work out." - that's expected behavior, at least for me. What would you expect, or how is this a problem?
Lucero
It's exactly what I suspect. I was saying that this satisfies my question requirements of maximum type safety.
Colin Burnett
Also (not sure if you added it in your edit) the cast of "item" to "T" should not be required, since the compiler knows (by generic type constraint) that the types are compatible.
Lucero
Oh yeah! :) I have fixed it.
Colin Burnett
+1  A: 

Can't you just do:

listOfAItems.AddRange(listOfBItems.Cast<A>());
BFree
Um, I don't have a List<T>.Cast<Tother>() method.
Colin Burnett
Are you referencing System.Linq? If you're running against .NET 2.0, I apologize, as Cast<T> is one of the Linq extension methods.
BFree
True, and as such it is only available in .NET version 3.5 (or newer, when it will come out). However, while extension methods are "clean" in the code, they care not always very efficient. In this case, I believe that the approach with the generic parameter is much faster if called often.
Lucero
@Lucero: that's not true. Performance wise they are not slower than any other method. Sometimes opposite!
mhenrixon
+5  A: 

Indeed, generic types are not variant right now. In C# 4.0, IEnumerable<B> will be convertible to IEnumerable<A> if B is convertible to A via a reference conversion. For some details on the design of this feature, see:

http://blogs.msdn.com/ericlippert/archive/tags/Covariance+and+Contravariance/default.aspx

Eric Lippert