Question

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.)

Was it helpful?

Solution

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

OTHER TIPS

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);
        }
    }
}

Can't you just do:

listOfAItems.AddRange(listOfBItems.Cast<A>());

I was able to achieve this using LINQ...

listOfAItems.AddRange(listOfBItems.Cast<A>());

In case you find yourself in a situation where generic types are not variant, the following extension method can make your life easier:

public static void AddRange<TList,TOther>(this List<TList> list, IEnumerable<TOther> collection) where TOther: TList {
    foreach(TOther e in collection) {
        list.Add(e);
    }
}

Instead of having to derive from List<T> or having this method in some utility class, using it as an extension method simplifies usage. You can also profit from inference, so this formerly invalid call will become valid without any modification:

List<Animal> animals;
List<Dog> dogs;
animals.AddRange(dogs);

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top