Why Can't I Assign a List<Derived> to a List<Base>

Why can't I assign a ListDerived to a ListBase?

The simplest way to understand why this is not allowed is the following example:

abstract class Fruit
{
}

class Apple : Fruit
{
}

class Banana : Fruit
{
}

// This should intuitively compile right? Cause an Apple is Fruit.
List<Fruit> fruits = new List<Apple>();

// But what if I do this? Adding a Banana to a list of Apples
fruits.Add(new Banana());

The last statement would ruin the type safety of .NET.

Arrays however, do allow this:

Fruit[] fruits = new Apple[10]; // This is perfectly fine

However, putting a Banana into fruits would still break type safety, so therefor .NET has to do a type check on every array insertion and throw an exception if it's not actually an Apple. This is potentially a (small) performance hit, but this can be circumvented by creating a struct wrapper around either type as this check does not happen for value types (because they can't inherit from anything). At first, I didn't understand why this decision was made, but you'll encounter quite often why this can be useful. Most common is String.Format, which takes params object[] and any array can be passed into this.

In .NET 4 though, there's type safe covariance/contravariance, which allows you to make some assignments like these, but only if they're provably safe. What's provably safe?

IEnumerable<Fruit> fruits = new List<Apple>();

The above works in .NET 4, because IEnumerable<T> became IEnumerable<out T>. The out means that T can only ever come out of fruits and that there's no method at all on IEnumerable<out T> that ever takes T as a parameter, so you can never incorrectly pass a Banana into IEnumerable<Fruit>.

Contravariance is much the same but I always forget the exact details on it. Unsurprisingly, for that there's now the in keyword on type parameters.

Cannot add a derived class object to a list of it's base class type

One possible solution would be to introduce a covariant interface:

public interface IBase<out T> where T : Model
{
T Data { get; }
}

public abstract class BaseClass<T> : IBase<T> where T : Model

Then use the interface for your list:

var myBaseList = new List<IBase<Model>>();
myBaseList.Add(new BaseClassA(new ModelA()));
myBaseList.Add(new BaseClassB(new ModelB()));

Because IBase declares Data as get-only, this ensures type-safety.

And here you still have access to Data:

foreach (var item in myBaseList)
{
if (item.Data.Id > 0)
{
//do stuff
}
}

Cannot convert from ListDerivedClass to ListBaseClass

It is because List<T> is invariant, not covariant, so you should change to IEnumerable<T> which supports covariant:

IEnumerable<BaseClass> bcl = new List<DerivedClass>();

public void doSomething(IEnumerable<BaseClass> bc)
{
// do something with bc
}

Information about covariant in generic.

Convert ListDerivedClass to ListBaseClass

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

Convert List of Base Class to a List of Derived Class - possible?

Cast<T> method applies cast operation to all elements of the input sequence. It works only if you can do the following to each element of the sequence without causing an exception:

ParentClass p = ...
ChildClass c = (ChildClass)p;

This will not work unless p is assigned an instance of ChildClass or one of its subclasses. It appears that in your case the data returned from the server API contains objects of ParentClass or one of its subclasses other than ChildClass.

You can fix this problem by constructing ChildClass instances, assuming that you have enough information from the server:

List<ChildClass> childList = parentList
.Select(parent => new ChildClass(parent.Name, ... /* the remaining fields */))
.ToList();

Casting List of Derived class to List of base class

The reason you cannot do this is because a list is writable. Suppose it were legal, and see what goes wrong:

List<Cat> cats = new List<Cat>();
List<Animal> animals = cats; // Trouble brewing...
animals.Add(new Dog()); // hey, we just added a dog to a list of cats...
cats[0].Speak(); // Woof!

Well dog my cats, that is badness.

The feature you want is called "generic covariance" and it is supported in C# 4 for interfaces that are known to be safe. IEnumerable<T> does not have any way to write to the sequence, so it is safe.

class Animal    
{
public virtual void Play(IEnumerable<Animal> animals) { }
}
class Cat : Animal
{
public override void Play(IEnumerable<Animal> animals) { }
}
class Program
{
static void Main()
{
Cat cat = new Cat();
cat.Play(new List<Cat>());
}
}

That will work in C# 4 because List<Cat> is convertible to IEnumerable<Cat>, which is convertible to IEnumerable<Animal>. There is no way that Play can use IEnumerable<Animal> to add a dog to something that is actually a list of cats.

C# create list of generic items to pass to function that takes base class

Since DerivedItem1 inherits from IBaseItemClass but myList.Add() expects List of IBaseItemClass.

And you are giving it a List of DerivedItem1 that doesn't inherits from List of IBaseItemClass anyways.

Here is a solution, not an ideal one but a workaround, try adding like this, it should work:

myList.Add(derivedItemList.Select(item => item as IBaseItemClass).ToList());

Or simply use the IEnumerable extension method Cast as suggested by @juharr in the comments as:

myList.Add(derivedItemList.Cast<IBaseItemClass>().ToList());


Related Topics



Leave a reply



Submit