Convert List<DerivedClass> to List<BaseClass>

132,460

Solution 1

The way to make this work is to iterate over the list and cast the elements. This can be done using ConvertAll:

List<A> listOfA = new List<C>().ConvertAll(x => (A)x);

You could also use Linq:

List<A> listOfA = new List<C>().Cast<A>().ToList();

Solution 2

First of all, stop using impossible-to-understand class names like A, B, C. Use Animal, Mammal, Giraffe, or Food, Fruit, Orange or something where the relationships are clear.

Your question then is "why can I not assign a list of giraffes to a variable of type list of animal, since I can assign a giraffe to a variable of type animal?"

The answer is: suppose you could. What could then go wrong?

Well, you can add a Tiger to a list of animals. Suppose we allow you to put a list of giraffes in a variable that holds a list of animals. Then you try to add a tiger to that list. What happens? Do you want the list of giraffes to contain a tiger? Do you want a crash? or do you want the compiler to protect you from the crash by making the assignment illegal in the first place?

We choose the latter.

This kind of conversion is called a "covariant" conversion. In C# 4 we will allow you to make covariant conversions on interfaces and delegates when the conversion is known to be always safe. See my blog articles on covariance and contravariance for details. (There will be a fresh one on this topic on both Monday and Thursday of this week.)

Solution 3

To quote the great explanation of Eric

What happens? Do you want the list of giraffes to contain a tiger? Do you want a crash? or do you want the compiler to protect you from the crash by making the assignment illegal in the first place? We choose the latter.

But what if you want to choose for a runtime crash instead of a compile error? You would normally use Cast<> or ConvertAll<> but then you will have 2 problems: It will create a copy of the list. If you add or remove something in the new list, this won't be reflected in the original list. And secondly, there is a big performance and memory penalty since it creates a new list with the existing objects.

I had the same problem and therefore I created a wrapper class that can cast a generic list without creating an entirely new list.

In the original question you could then use:

class Test
{
    static void Main(string[] args)
    {
        A a = new C(); // OK
        IList<A> listOfA = new List<C>().CastList<C,A>(); // now ok!
    }
}

and here the wrapper class (+ an extention method CastList for easy use)

public class CastedList<TTo, TFrom> : IList<TTo>
{
    public IList<TFrom> BaseList;

    public CastedList(IList<TFrom> baseList)
    {
        BaseList = baseList;
    }

    // IEnumerable
    IEnumerator IEnumerable.GetEnumerator() { return BaseList.GetEnumerator(); }

    // IEnumerable<>
    public IEnumerator<TTo> GetEnumerator() { return new CastedEnumerator<TTo, TFrom>(BaseList.GetEnumerator()); }

    // ICollection
    public int Count { get { return BaseList.Count; } }
    public bool IsReadOnly { get { return BaseList.IsReadOnly; } }
    public void Add(TTo item) { BaseList.Add((TFrom)(object)item); }
    public void Clear() { BaseList.Clear(); }
    public bool Contains(TTo item) { return BaseList.Contains((TFrom)(object)item); }
    public void CopyTo(TTo[] array, int arrayIndex) { BaseList.CopyTo((TFrom[])(object)array, arrayIndex); }
    public bool Remove(TTo item) { return BaseList.Remove((TFrom)(object)item); }

    // IList
    public TTo this[int index]
    {
        get { return (TTo)(object)BaseList[index]; }
        set { BaseList[index] = (TFrom)(object)value; }
    }

    public int IndexOf(TTo item) { return BaseList.IndexOf((TFrom)(object)item); }
    public void Insert(int index, TTo item) { BaseList.Insert(index, (TFrom)(object)item); }
    public void RemoveAt(int index) { BaseList.RemoveAt(index); }
}

public class CastedEnumerator<TTo, TFrom> : IEnumerator<TTo>
{
    public IEnumerator<TFrom> BaseEnumerator;

    public CastedEnumerator(IEnumerator<TFrom> baseEnumerator)
    {
        BaseEnumerator = baseEnumerator;
    }

    // IDisposable
    public void Dispose() { BaseEnumerator.Dispose(); }

    // IEnumerator
    object IEnumerator.Current { get { return BaseEnumerator.Current; } }
    public bool MoveNext() { return BaseEnumerator.MoveNext(); }
    public void Reset() { BaseEnumerator.Reset(); }

    // IEnumerator<>
    public TTo Current { get { return (TTo)(object)BaseEnumerator.Current; } }
}

public static class ListExtensions
{
    public static IList<TTo> CastList<TFrom, TTo>(this IList<TFrom> list)
    {
        return new CastedList<TTo, TFrom>(list);
    }
}

Solution 4

If you use IEnumerable instead, it will work (at least in C# 4.0, I have not tried previous versions). This is just a cast, of course, it will still be a list.

Instead of -

List<A> listOfA = new List<C>(); // compiler Error

In the original code of the question, use -

IEnumerable<A> listOfA = new List<C>(); // compiler error - no more! :)

Solution 5

As far as why it doesn't work, it might be helpful to understand covariance and contravariance.

Just to show why this shouldn't work, here is a change to the code you provided:

void DoesThisWork()
{
     List<C> DerivedList = new List<C>();
     List<A> BaseList = DerivedList;
     BaseList.Add(new B());

     C FirstItem = DerivedList.First();
}

Should this work? The First item in the list is of Type "B", but the type of the DerivedList item is C.

Now, assume that we really just want to make a generic function that operates on a list of some type which implements A, but we don't care what type that is:

void ThisWorks<T>(List<T> GenericList) where T:A
{

}

void Test()
{
     ThisWorks(new List<B>());
     ThisWorks(new List<C>());
}
Share:
132,460

Related videos on Youtube

Asad
Author by

Asad

Adventures on github and writings on Quoracreative

Updated on April 16, 2021

Comments

  • Asad
    Asad about 3 years

    While we can inherit from base class/interface, why can't we declare a List<> using same class/interface?

    interface A
    { }
    
    class B : A
    { }
    
    class C : B
    { }
    
    class Test
    {
        static void Main(string[] args)
        {
            A a = new C(); // OK
            List<A> listOfA = new List<C>(); // compiler Error
        }
    }
    

    Is there a way around?

  • Eric Lippert
    Eric Lippert over 14 years
    First, this is a question of convertibility, not of inheritance. Second, covariance of generic types will not work on class types, only on interface and delegate types.
  • Noon Silk
    Noon Silk over 14 years
    Well, I can hardly argue with you.
  • supercat
    supercat almost 13 years
    Would there be anything unsafe about an IList<T> or ICollection<T> implementing non-generic IList, but implementing having the non-generic Ilist/ICollection return True for IsReadOnly, and throw NotSupportedException for any methods or properties that would modify it?
  • ahaliav fox
    ahaliav fox almost 10 years
    another option: List<A> listOfA = listOfC.ConvertAll(x => (A)x);
  • Martin Braun
    Martin Braun almost 10 years
    Which one is faster? ConvertAll or Cast?
  • Bigjim
    Bigjim about 9 years
    This will create a copy of the list. If you add or remove something in the new list, this won't be reflected in the original list. And secondly, there is a big performance and memory penalty since it creates a new list with the existing objects. See my answer below for a solution without these problems.
  • Vladius
    Vladius over 8 years
    How to use the IEnumerable in that case exactly?
  • PhistucK
    PhistucK over 8 years
    Instead of List<A> listOfA = new List<C>(); // compiler Error in the original code of the question, enter IEnumerable<A> listOfA = new List<C>(); // compiler error - no more! :)
  • beauXjames
    beauXjames over 8 years
    The method taking the IEnumerable<BaseClass> as a parameter will allow for the inherited class to be passed in as a List. So there's very little to do but change the parameter type.
  • Paul Coldrey
    Paul Coldrey over 8 years
    Whilst this answer contains quite acceptable reasoning it is not really "true". The simple answer is that C# doesn't support this. The idea of having a list of animals that contains giraffes and tigers is perfectly valid. The only issue comes when you want to access the higher level classes. In reality this is no different to passing a parent class as a parameter to a function and then trying to cast it to a different sibling class. There may be technical issues with implementing the cast but the explanation above does not provide any reason why it would be a bad idea.
  • Paul Coldrey
    Paul Coldrey over 8 years
    "Should this work?" - I would yes and no. I see no reason why you shouldn't be able to write the code and have it fail at compile time since it is actually doing an invalid conversion at the time you access FirstItem as type 'C'. There are many analogous ways to blow up C# that are supported. IF you actually want to achieve this functionality for a good reason (and there are many) then bigjim's answer below is awesome.
  • Stefan Cebulak
    Stefan Cebulak over 7 years
    I've just used it as MVC view model and got nice universal razor partial view. Amazing idea! I'm so happy that i read this.
  • Wolfzoon
    Wolfzoon over 7 years
    I would only add a "where TTo : TFrom" at the class declaration, so the compiler can warn against incorrect usage. Making a CastedList of unrelated types is nonsensical anyway, and making a "CastedList<TBase, TDerived>" would be useless: You can't add regular TBase objects to it, and any TDerived you get from the original List can already be used as a TBase.
  • Wolfzoon
    Wolfzoon over 7 years
    @PaulColdrey Alas, a six year head start is to blame.
  • Bigjim
    Bigjim about 7 years
    @Wolfzoon: the standard .Cast<T>() hasn't this restriction either. I wanted to make the behaviour the same as .Cast so it is possible to cast a list of Animals to a list of Tigers, and have an exception when it would contain a Giraffe for example. Just as it would be with Cast...
  • Jéf Bueno
    Jéf Bueno about 7 years
    @EricLippert Why can we do this conversion using an IEnumerable instead of a List? i.e.: List<Animal> listAnimals = listGiraffes as List<Animal>; is not possible, but IEnumerable<Animal> eAnimals = listGiraffes as IEnumerable<Animal> works.
  • Eric Lippert
    Eric Lippert about 7 years
    @jbueno: Read the last paragraph of my answer. The conversion there is known to be safe. Why? Because it is impossible to turn a sequence of giraffes into a sequence of animals and then put a tiger into the sequence of animals. IEnumerable<T> and IEnumerator<T> are both marked as safe for covariance, and the compiler has verified that.
  • bendtherules
    bendtherules over 6 years
    This one was the most understandable answer to me. Specially because it mentions that there is something called IReadOnlyList, which will work because it guarantees that no more element will be added.
  • Alastair Maw
    Alastair Maw over 6 years
    It's a shame that you can't do similar for IReadOnlyDictionary<TKey, TValue>. Why is that?
  • Denis Vitez
    Denis Vitez about 6 years
    Hi @Bigjim, I'm having problems understanding how to use your wrapper class, to cast an existing array of Giraffes into an array of Animals (base class). Any tips will be appriciated :)
  • Edward
    Edward almost 6 years
    Answer to modiX: ConvertAll is a method on List<T> , so it will only work on a generic list; it won't work on IEnumerable or IEnumerable<T>; ConvertAll can also do custom conversions not just casting, e.g. ConvertAll(inches => inches * 25.4). Cast<A> is a LINQ extension method, so works on any IEnumerable<T> (and also works for non-generic IEnumerable), and like of most of LINQ it uses deferred execution, that is, only converts as many items as are retrieved. Read more about it here: codeblog.jonskeet.uk/2011/01/13/…
  • Rick Love
    Rick Love over 5 years
    This is a great suggestion. I use List<> everywhere for convenience, but most of the time I want it to be readonly. Nice suggestion as this will improve my code in 2 ways by allowing this cast and improving where I specify readonly.
  • IgorZhurbenko
    IgorZhurbenko almost 3 years
    Long, but no real argument. Bunch of apples is a bunch of fruits. Zoo of tigers is the a zoo of animals. And no compiler can make it incorrect.