Adding a subclass collection using AddRange

If I have these two classes:

class A {}
class B : A {}

      

and I am doing List <A> but I want to add 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 fully understand because IEnumerable <B> does not inherit from IEnumerable <A>, its generic type is inheritance.

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

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

      

However, this is rather not expressive, because I just want AddRange.

I could use

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

      

but this is unnecessarily confusing and wrong because I am not converting, I am 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 collection B to collection A as a one-liner similar to List <A> .AddRange (List <B>) and keep the maximum type safety. (And to the max, I mean that the argument is a collection and type integrity check.)

+1


source to share


6 answers


Indeed, generic types are not options right now. In C # 4.0 IEnumerable <B

> will be convertible to IEnumerable <A

> if B can be converted to via reference conversion. For details on the design of this feature, see



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

+7


source


This fails unsuccessfully because generics in .net do not support covariance.

You can make a small helper method or class to solve this problem.



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

      

+1


source


Can't you just do:

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

      

+1


source


I was able to achieve this using LINQ ...

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

      

+1


source


If you find yourself in a situation where generic types are not variants, 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 retrieving from List<T>

or having this method in some utility class, using it as an extension method makes it easier to use. You can also profit from the withdrawal, so this previously invalid call will become valid without any changes:

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

      

+1


source


The only thing I can think of is

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 means using MyList <A> .AddRange <B> (MyList <B>). It fails if the argument is not enumerable, or if type inheritance doesn't work, so it meets my requirement for maximum security.

0


source







All Articles